PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/java/org/apache/catalina/realm/JNDIRealm.java

https://github.com/dblevins/tomcat
Java | 2398 lines | 1027 code | 505 blank | 866 comment | 256 complexity | c4e8437cd8c3e624f7dc5cc76532fe12 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.catalina.realm;
  18. import java.net.URI;
  19. import java.net.URISyntaxException;
  20. import java.security.Principal;
  21. import java.text.MessageFormat;
  22. import java.util.ArrayList;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.Hashtable;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Map.Entry;
  30. import java.util.Set;
  31. import javax.naming.AuthenticationException;
  32. import javax.naming.CommunicationException;
  33. import javax.naming.CompositeName;
  34. import javax.naming.Context;
  35. import javax.naming.InvalidNameException;
  36. import javax.naming.Name;
  37. import javax.naming.NameNotFoundException;
  38. import javax.naming.NameParser;
  39. import javax.naming.NamingEnumeration;
  40. import javax.naming.NamingException;
  41. import javax.naming.PartialResultException;
  42. import javax.naming.ServiceUnavailableException;
  43. import javax.naming.directory.Attribute;
  44. import javax.naming.directory.Attributes;
  45. import javax.naming.directory.DirContext;
  46. import javax.naming.directory.InitialDirContext;
  47. import javax.naming.directory.SearchControls;
  48. import javax.naming.directory.SearchResult;
  49. import org.apache.catalina.LifecycleException;
  50. import org.ietf.jgss.GSSCredential;
  51. /**
  52. * <p>Implementation of <strong>Realm</strong> that works with a directory
  53. * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
  54. * The following constraints are imposed on the data structure in the
  55. * underlying directory server:</p>
  56. * <ul>
  57. *
  58. * <li>Each user that can be authenticated is represented by an individual
  59. * element in the top level <code>DirContext</code> that is accessed
  60. * via the <code>connectionURL</code> property.</li>
  61. *
  62. * <li>If a socket connection can not be made to the <code>connectURL</code>
  63. * an attempt will be made to use the <code>alternateURL</code> if it
  64. * exists.</li>
  65. *
  66. * <li>Each user element has a distinguished name that can be formed by
  67. * substituting the presented username into a pattern configured by the
  68. * <code>userPattern</code> property.</li>
  69. *
  70. * <li>Alternatively, if the <code>userPattern</code> property is not
  71. * specified, a unique element can be located by searching the directory
  72. * context. In this case:
  73. * <ul>
  74. * <li>The <code>userSearch</code> pattern specifies the search filter
  75. * after substitution of the username.</li>
  76. * <li>The <code>userBase</code> property can be set to the element that
  77. * is the base of the subtree containing users. If not specified,
  78. * the search base is the top-level context.</li>
  79. * <li>The <code>userSubtree</code> property can be set to
  80. * <code>true</code> if you wish to search the entire subtree of the
  81. * directory context. The default value of <code>false</code>
  82. * requests a search of only the current level.</li>
  83. * </ul>
  84. * </li>
  85. *
  86. * <li>The user may be authenticated by binding to the directory with the
  87. * username and password presented. This method is used when the
  88. * <code>userPassword</code> property is not specified.</li>
  89. *
  90. * <li>The user may be authenticated by retrieving the value of an attribute
  91. * from the directory and comparing it explicitly with the value presented
  92. * by the user. This method is used when the <code>userPassword</code>
  93. * property is specified, in which case:
  94. * <ul>
  95. * <li>The element for this user must contain an attribute named by the
  96. * <code>userPassword</code> property.
  97. * <li>The value of the user password attribute is either a cleartext
  98. * String, or the result of passing a cleartext String through the
  99. * <code>RealmBase.digest()</code> method (using the standard digest
  100. * support included in <code>RealmBase</code>).
  101. * <li>The user is considered to be authenticated if the presented
  102. * credentials (after being passed through
  103. * <code>RealmBase.digest()</code>) are equal to the retrieved value
  104. * for the user password attribute.</li>
  105. * </ul></li>
  106. *
  107. * <li>Each group of users that has been assigned a particular role may be
  108. * represented by an individual element in the top level
  109. * <code>DirContext</code> that is accessed via the
  110. * <code>connectionURL</code> property. This element has the following
  111. * characteristics:
  112. * <ul>
  113. * <li>The set of all possible groups of interest can be selected by a
  114. * search pattern configured by the <code>roleSearch</code>
  115. * property.</li>
  116. * <li>The <code>roleSearch</code> pattern optionally includes pattern
  117. * replacements "{0}" for the distinguished name, and/or "{1}" for
  118. * the username, and/or "{2}" the value of an attribute from the
  119. * user's directory entry (the attribute is specified by the
  120. * <code>userRoleAttribute</code> property), of the authenticated user
  121. * for which roles will be retrieved.</li>
  122. * <li>The <code>roleBase</code> property can be set to the element that
  123. * is the base of the search for matching roles. If not specified,
  124. * the entire context will be searched.</li>
  125. * <li>The <code>roleSubtree</code> property can be set to
  126. * <code>true</code> if you wish to search the entire subtree of the
  127. * directory context. The default value of <code>false</code>
  128. * requests a search of only the current level.</li>
  129. * <li>The element includes an attribute (whose name is configured by
  130. * the <code>roleName</code> property) containing the name of the
  131. * role represented by this element.</li>
  132. * </ul></li>
  133. *
  134. * <li>In addition, roles may be represented by the values of an attribute
  135. * in the user's element whose name is configured by the
  136. * <code>userRoleName</code> property.</li>
  137. *
  138. * <li>A default role can be assigned to each user that was successfully
  139. * authenticated by setting the <code>commonRole</code> property to the
  140. * name of this role. The role doesn't have to exist in the directory.</li>
  141. *
  142. * <li>If the directory server contains nested roles, you can search for them
  143. * by setting <code>roleNested</code> to <code>true</code>.
  144. * The default value is <code>false</code>, so role searches will not find
  145. * nested roles.</li>
  146. *
  147. * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
  148. * the web application deployment descriptor allows applications to refer
  149. * to roles programmatically by names other than those used in the
  150. * directory server itself.</li>
  151. * </ul>
  152. *
  153. * <p><strong>TODO</strong> - Support connection pooling (including message
  154. * format objects) so that <code>authenticate()</code> does not have to be
  155. * synchronized.</p>
  156. *
  157. * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
  158. * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
  159. * successfully authenticated a non-existing user. The
  160. * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
  161. * With luck, Netscape has updated their provider code and this is not an
  162. * issue. </p>
  163. *
  164. * @author John Holman
  165. * @author Craig R. McClanahan
  166. */
  167. public class JNDIRealm extends RealmBase {
  168. // ----------------------------------------------------- Instance Variables
  169. /**
  170. * The type of authentication to use
  171. */
  172. protected String authentication = null;
  173. /**
  174. * The connection username for the server we will contact.
  175. */
  176. protected String connectionName = null;
  177. /**
  178. * The connection password for the server we will contact.
  179. */
  180. protected String connectionPassword = null;
  181. /**
  182. * The connection URL for the server we will contact.
  183. */
  184. protected String connectionURL = null;
  185. /**
  186. * The directory context linking us to our directory server.
  187. */
  188. protected DirContext context = null;
  189. /**
  190. * The JNDI context factory used to acquire our InitialContext. By
  191. * default, assumes use of an LDAP server using the standard JNDI LDAP
  192. * provider.
  193. */
  194. protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
  195. /**
  196. * How aliases should be dereferenced during search operations.
  197. */
  198. protected String derefAliases = null;
  199. /**
  200. * Constant that holds the name of the environment property for specifying
  201. * the manner in which aliases should be dereferenced.
  202. */
  203. public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
  204. /**
  205. * Descriptive information about this Realm implementation.
  206. */
  207. protected static final String name = "JNDIRealm";
  208. /**
  209. * The protocol that will be used in the communication with the
  210. * directory server.
  211. */
  212. protected String protocol = null;
  213. /**
  214. * Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
  215. * Microsoft Active Directory often returns referrals, which lead
  216. * to PartialResultExceptions. Unfortunately there's no stable way to detect,
  217. * if the Exceptions really come from an AD referral.
  218. * Set to true to ignore PartialResultExceptions.
  219. */
  220. protected boolean adCompat = false;
  221. /**
  222. * How should we handle referrals? Microsoft Active Directory often returns
  223. * referrals. If you need to follow them set referrals to "follow".
  224. * Caution: if your DNS is not part of AD, the LDAP client lib might try
  225. * to resolve your domain name in DNS to find another LDAP server.
  226. */
  227. protected String referrals = null;
  228. /**
  229. * The base element for user searches.
  230. */
  231. protected String userBase = "";
  232. /**
  233. * The message format used to search for a user, with "{0}" marking
  234. * the spot where the username goes.
  235. */
  236. protected String userSearch = null;
  237. /**
  238. * The MessageFormat object associated with the current
  239. * <code>userSearch</code>.
  240. */
  241. protected MessageFormat userSearchFormat = null;
  242. /**
  243. * Should we search the entire subtree for matching users?
  244. */
  245. protected boolean userSubtree = false;
  246. /**
  247. * The attribute name used to retrieve the user password.
  248. */
  249. protected String userPassword = null;
  250. /**
  251. * The name of the attribute inside the users
  252. * directory entry where the value will be
  253. * taken to search for roles
  254. * This attribute is not used during a nested search
  255. */
  256. protected String userRoleAttribute = null;
  257. /**
  258. * A string of LDAP user patterns or paths, ":"-separated
  259. * These will be used to form the distinguished name of a
  260. * user, with "{0}" marking the spot where the specified username
  261. * goes.
  262. * This is similar to userPattern, but allows for multiple searches
  263. * for a user.
  264. */
  265. protected String[] userPatternArray = null;
  266. /**
  267. * The message format used to form the distinguished name of a
  268. * user, with "{0}" marking the spot where the specified username
  269. * goes.
  270. */
  271. protected String userPattern = null;
  272. /**
  273. * An array of MessageFormat objects associated with the current
  274. * <code>userPatternArray</code>.
  275. */
  276. protected MessageFormat[] userPatternFormatArray = null;
  277. /**
  278. * The base element for role searches.
  279. */
  280. protected String roleBase = "";
  281. /**
  282. * The MessageFormat object associated with the current
  283. * <code>roleBase</code>.
  284. */
  285. protected MessageFormat roleBaseFormat = null;
  286. /**
  287. * The MessageFormat object associated with the current
  288. * <code>roleSearch</code>.
  289. */
  290. protected MessageFormat roleFormat = null;
  291. /**
  292. * The name of an attribute in the user's entry containing
  293. * roles for that user
  294. */
  295. protected String userRoleName = null;
  296. /**
  297. * The name of the attribute containing roles held elsewhere
  298. */
  299. protected String roleName = null;
  300. /**
  301. * The message format used to select roles for a user, with "{0}" marking
  302. * the spot where the distinguished name of the user goes. The "{1}"
  303. * and "{2}" are described in the Configuration Reference.
  304. */
  305. protected String roleSearch = null;
  306. /**
  307. * Should we search the entire subtree for matching memberships?
  308. */
  309. protected boolean roleSubtree = false;
  310. /**
  311. * Should we look for nested group in order to determine roles?
  312. */
  313. protected boolean roleNested = false;
  314. /**
  315. * When searching for user roles, should the search be performed as the user
  316. * currently being authenticated? If false, {@link #connectionName} and
  317. * {@link #connectionPassword} will be used if specified, else an anonymous
  318. * connection will be used.
  319. */
  320. protected boolean roleSearchAsUser = false;
  321. /**
  322. * An alternate URL, to which, we should connect if connectionURL fails.
  323. */
  324. protected String alternateURL;
  325. /**
  326. * The number of connection attempts. If greater than zero we use the
  327. * alternate url.
  328. */
  329. protected int connectionAttempt = 0;
  330. /**
  331. * Add this role to every authenticated user
  332. */
  333. protected String commonRole = null;
  334. /**
  335. * The timeout, in milliseconds, to use when trying to create a connection
  336. * to the directory. The default is 5000 (5 seconds).
  337. */
  338. protected String connectionTimeout = "5000";
  339. /**
  340. * The sizeLimit (also known as the countLimit) to use when the realm is
  341. * configured with {@link #userSearch}. Zero for no limit.
  342. */
  343. protected long sizeLimit = 0;
  344. /**
  345. * The timeLimit (in milliseconds) to use when the realm is configured with
  346. * {@link #userSearch}. Zero for no limit.
  347. */
  348. protected int timeLimit = 0;
  349. /**
  350. * Should delegated credentials from the SPNEGO authenticator be used if
  351. * available
  352. */
  353. protected boolean useDelegatedCredential = true;
  354. /**
  355. * The QOP that should be used for the connection to the LDAP server after
  356. * authentication. This value is used to set the
  357. * <code>javax.security.sasl.qop</code> environment property for the LDAP
  358. * connection.
  359. */
  360. protected String spnegoDelegationQop = "auth-conf";
  361. // ------------------------------------------------------------- Properties
  362. /**
  363. * Return the type of authentication to use.
  364. */
  365. public String getAuthentication() {
  366. return authentication;
  367. }
  368. /**
  369. * Set the type of authentication to use.
  370. *
  371. * @param authentication The authentication
  372. */
  373. public void setAuthentication(String authentication) {
  374. this.authentication = authentication;
  375. }
  376. /**
  377. * Return the connection username for this Realm.
  378. */
  379. public String getConnectionName() {
  380. return (this.connectionName);
  381. }
  382. /**
  383. * Set the connection username for this Realm.
  384. *
  385. * @param connectionName The new connection username
  386. */
  387. public void setConnectionName(String connectionName) {
  388. this.connectionName = connectionName;
  389. }
  390. /**
  391. * Return the connection password for this Realm.
  392. */
  393. public String getConnectionPassword() {
  394. return (this.connectionPassword);
  395. }
  396. /**
  397. * Set the connection password for this Realm.
  398. *
  399. * @param connectionPassword The new connection password
  400. */
  401. public void setConnectionPassword(String connectionPassword) {
  402. this.connectionPassword = connectionPassword;
  403. }
  404. /**
  405. * Return the connection URL for this Realm.
  406. */
  407. public String getConnectionURL() {
  408. return (this.connectionURL);
  409. }
  410. /**
  411. * Set the connection URL for this Realm.
  412. *
  413. * @param connectionURL The new connection URL
  414. */
  415. public void setConnectionURL(String connectionURL) {
  416. this.connectionURL = connectionURL;
  417. }
  418. /**
  419. * Return the JNDI context factory for this Realm.
  420. */
  421. public String getContextFactory() {
  422. return (this.contextFactory);
  423. }
  424. /**
  425. * Set the JNDI context factory for this Realm.
  426. *
  427. * @param contextFactory The new context factory
  428. */
  429. public void setContextFactory(String contextFactory) {
  430. this.contextFactory = contextFactory;
  431. }
  432. /**
  433. * Return the derefAliases setting to be used.
  434. */
  435. public java.lang.String getDerefAliases() {
  436. return derefAliases;
  437. }
  438. /**
  439. * Set the value for derefAliases to be used when searching the directory.
  440. *
  441. * @param derefAliases New value of property derefAliases.
  442. */
  443. public void setDerefAliases(java.lang.String derefAliases) {
  444. this.derefAliases = derefAliases;
  445. }
  446. /**
  447. * Return the protocol to be used.
  448. */
  449. public String getProtocol() {
  450. return protocol;
  451. }
  452. /**
  453. * Set the protocol for this Realm.
  454. *
  455. * @param protocol The new protocol.
  456. */
  457. public void setProtocol(String protocol) {
  458. this.protocol = protocol;
  459. }
  460. /**
  461. * Returns the current settings for handling PartialResultExceptions
  462. */
  463. public boolean getAdCompat () {
  464. return adCompat;
  465. }
  466. /**
  467. * How do we handle PartialResultExceptions?
  468. * True: ignore all PartialResultExceptions.
  469. */
  470. public void setAdCompat (boolean adCompat) {
  471. this.adCompat = adCompat;
  472. }
  473. /**
  474. * Returns the current settings for handling JNDI referrals.
  475. */
  476. public String getReferrals () {
  477. return referrals;
  478. }
  479. /**
  480. * How do we handle JNDI referrals? ignore, follow, or throw
  481. * (see javax.naming.Context.REFERRAL for more information).
  482. */
  483. public void setReferrals (String referrals) {
  484. this.referrals = referrals;
  485. }
  486. /**
  487. * Return the base element for user searches.
  488. */
  489. public String getUserBase() {
  490. return (this.userBase);
  491. }
  492. /**
  493. * Set the base element for user searches.
  494. *
  495. * @param userBase The new base element
  496. */
  497. public void setUserBase(String userBase) {
  498. this.userBase = userBase;
  499. }
  500. /**
  501. * Return the message format pattern for selecting users in this Realm.
  502. */
  503. public String getUserSearch() {
  504. return (this.userSearch);
  505. }
  506. /**
  507. * Set the message format pattern for selecting users in this Realm.
  508. *
  509. * @param userSearch The new user search pattern
  510. */
  511. public void setUserSearch(String userSearch) {
  512. this.userSearch = userSearch;
  513. if (userSearch == null)
  514. userSearchFormat = null;
  515. else
  516. userSearchFormat = new MessageFormat(userSearch);
  517. }
  518. /**
  519. * Return the "search subtree for users" flag.
  520. */
  521. public boolean getUserSubtree() {
  522. return (this.userSubtree);
  523. }
  524. /**
  525. * Set the "search subtree for users" flag.
  526. *
  527. * @param userSubtree The new search flag
  528. */
  529. public void setUserSubtree(boolean userSubtree) {
  530. this.userSubtree = userSubtree;
  531. }
  532. /**
  533. * Return the user role name attribute name for this Realm.
  534. */
  535. public String getUserRoleName() {
  536. return userRoleName;
  537. }
  538. /**
  539. * Set the user role name attribute name for this Realm.
  540. *
  541. * @param userRoleName The new userRole name attribute name
  542. */
  543. public void setUserRoleName(String userRoleName) {
  544. this.userRoleName = userRoleName;
  545. }
  546. /**
  547. * Return the base element for role searches.
  548. */
  549. public String getRoleBase() {
  550. return (this.roleBase);
  551. }
  552. /**
  553. * Set the base element for role searches.
  554. *
  555. * @param roleBase The new base element
  556. */
  557. public void setRoleBase(String roleBase) {
  558. this.roleBase = roleBase;
  559. if (roleBase == null)
  560. roleBaseFormat = null;
  561. else
  562. roleBaseFormat = new MessageFormat(roleBase);
  563. }
  564. /**
  565. * Return the role name attribute name for this Realm.
  566. */
  567. public String getRoleName() {
  568. return (this.roleName);
  569. }
  570. /**
  571. * Set the role name attribute name for this Realm.
  572. *
  573. * @param roleName The new role name attribute name
  574. */
  575. public void setRoleName(String roleName) {
  576. this.roleName = roleName;
  577. }
  578. /**
  579. * Return the message format pattern for selecting roles in this Realm.
  580. */
  581. public String getRoleSearch() {
  582. return (this.roleSearch);
  583. }
  584. /**
  585. * Set the message format pattern for selecting roles in this Realm.
  586. *
  587. * @param roleSearch The new role search pattern
  588. */
  589. public void setRoleSearch(String roleSearch) {
  590. this.roleSearch = roleSearch;
  591. if (roleSearch == null)
  592. roleFormat = null;
  593. else
  594. roleFormat = new MessageFormat(roleSearch);
  595. }
  596. public boolean isRoleSearchAsUser() {
  597. return roleSearchAsUser;
  598. }
  599. public void setRoleSearchAsUser(boolean roleSearchAsUser) {
  600. this.roleSearchAsUser = roleSearchAsUser;
  601. }
  602. /**
  603. * Return the "search subtree for roles" flag.
  604. */
  605. public boolean getRoleSubtree() {
  606. return (this.roleSubtree);
  607. }
  608. /**
  609. * Set the "search subtree for roles" flag.
  610. *
  611. * @param roleSubtree The new search flag
  612. */
  613. public void setRoleSubtree(boolean roleSubtree) {
  614. this.roleSubtree = roleSubtree;
  615. }
  616. /**
  617. * Return the "The nested group search flag" flag.
  618. */
  619. public boolean getRoleNested() {
  620. return (this.roleNested);
  621. }
  622. /**
  623. * Set the "search subtree for roles" flag.
  624. *
  625. * @param roleNested The nested group search flag
  626. */
  627. public void setRoleNested(boolean roleNested) {
  628. this.roleNested = roleNested;
  629. }
  630. /**
  631. * Return the password attribute used to retrieve the user password.
  632. */
  633. public String getUserPassword() {
  634. return (this.userPassword);
  635. }
  636. /**
  637. * Set the password attribute used to retrieve the user password.
  638. *
  639. * @param userPassword The new password attribute
  640. */
  641. public void setUserPassword(String userPassword) {
  642. this.userPassword = userPassword;
  643. }
  644. public String getUserRoleAttribute() {
  645. return userRoleAttribute;
  646. }
  647. public void setUserRoleAttribute(String userRoleAttribute) {
  648. this.userRoleAttribute = userRoleAttribute;
  649. }
  650. /**
  651. * Return the message format pattern for selecting users in this Realm.
  652. */
  653. public String getUserPattern() {
  654. return (this.userPattern);
  655. }
  656. /**
  657. * Set the message format pattern for selecting users in this Realm.
  658. * This may be one simple pattern, or multiple patterns to be tried,
  659. * separated by parentheses. (for example, either "cn={0}", or
  660. * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
  661. * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
  662. * also valid. Complex search strings with &, etc are NOT supported.
  663. *
  664. * @param userPattern The new user pattern
  665. */
  666. public void setUserPattern(String userPattern) {
  667. this.userPattern = userPattern;
  668. if (userPattern == null)
  669. userPatternArray = null;
  670. else {
  671. userPatternArray = parseUserPatternString(userPattern);
  672. int len = this.userPatternArray.length;
  673. userPatternFormatArray = new MessageFormat[len];
  674. for (int i=0; i < len; i++) {
  675. userPatternFormatArray[i] =
  676. new MessageFormat(userPatternArray[i]);
  677. }
  678. }
  679. }
  680. /**
  681. * Getter for property alternateURL.
  682. *
  683. * @return Value of property alternateURL.
  684. */
  685. public String getAlternateURL() {
  686. return this.alternateURL;
  687. }
  688. /**
  689. * Setter for property alternateURL.
  690. *
  691. * @param alternateURL New value of property alternateURL.
  692. */
  693. public void setAlternateURL(String alternateURL) {
  694. this.alternateURL = alternateURL;
  695. }
  696. /**
  697. * Return the common role
  698. */
  699. public String getCommonRole() {
  700. return commonRole;
  701. }
  702. /**
  703. * Set the common role
  704. *
  705. * @param commonRole The common role
  706. */
  707. public void setCommonRole(String commonRole) {
  708. this.commonRole = commonRole;
  709. }
  710. /**
  711. * Return the connection timeout.
  712. */
  713. public String getConnectionTimeout() {
  714. return connectionTimeout;
  715. }
  716. /**
  717. * Set the connection timeout.
  718. *
  719. * @param timeout The new connection timeout
  720. */
  721. public void setConnectionTimeout(String timeout) {
  722. this.connectionTimeout = timeout;
  723. }
  724. public long getSizeLimit() {
  725. return sizeLimit;
  726. }
  727. public void setSizeLimit(long sizeLimit) {
  728. this.sizeLimit = sizeLimit;
  729. }
  730. public int getTimeLimit() {
  731. return timeLimit;
  732. }
  733. public void setTimeLimit(int timeLimit) {
  734. this.timeLimit = timeLimit;
  735. }
  736. public boolean isUseDelegatedCredential() {
  737. return useDelegatedCredential;
  738. }
  739. public void setUseDelegatedCredential(boolean useDelegatedCredential) {
  740. this.useDelegatedCredential = useDelegatedCredential;
  741. }
  742. public String getSpnegoDelegationQop() {
  743. return spnegoDelegationQop;
  744. }
  745. public void setSpnegoDelegationQop(String spnegoDelegationQop) {
  746. this.spnegoDelegationQop = spnegoDelegationQop;
  747. }
  748. // ---------------------------------------------------------- Realm Methods
  749. /**
  750. * Return the Principal associated with the specified username and
  751. * credentials, if there is one; otherwise return <code>null</code>.
  752. *
  753. * If there are any errors with the JDBC connection, executing
  754. * the query or anything we return null (don't authenticate). This
  755. * event is also logged, and the connection will be closed so that
  756. * a subsequent request will automatically re-open it.
  757. *
  758. * @param username Username of the Principal to look up
  759. * @param credentials Password or other credentials to use in
  760. * authenticating this username
  761. */
  762. @Override
  763. public Principal authenticate(String username, String credentials) {
  764. DirContext context = null;
  765. Principal principal = null;
  766. try {
  767. // Ensure that we have a directory context available
  768. context = open();
  769. // Occassionally the directory context will timeout. Try one more
  770. // time before giving up.
  771. try {
  772. // Authenticate the specified username if possible
  773. principal = authenticate(context, username, credentials);
  774. } catch (NullPointerException e) {
  775. /* BZ 42449 - Kludge Sun's LDAP provider
  776. with broken SSL
  777. */
  778. // log the exception so we know it's there.
  779. containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
  780. // close the connection so we know it will be reopened.
  781. if (context != null)
  782. close(context);
  783. // open a new directory context.
  784. context = open();
  785. // Try the authentication again.
  786. principal = authenticate(context, username, credentials);
  787. } catch (CommunicationException e) {
  788. // log the exception so we know it's there.
  789. containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
  790. // close the connection so we know it will be reopened.
  791. if (context != null)
  792. close(context);
  793. // open a new directory context.
  794. context = open();
  795. // Try the authentication again.
  796. principal = authenticate(context, username, credentials);
  797. } catch (ServiceUnavailableException e) {
  798. // log the exception so we know it's there.
  799. containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
  800. // close the connection so we know it will be reopened.
  801. if (context != null)
  802. close(context);
  803. // open a new directory context.
  804. context = open();
  805. // Try the authentication again.
  806. principal = authenticate(context, username, credentials);
  807. }
  808. // Release this context
  809. release(context);
  810. // Return the authenticated Principal (if any)
  811. return (principal);
  812. } catch (NamingException e) {
  813. // Log the problem for posterity
  814. containerLog.error(sm.getString("jndiRealm.exception"), e);
  815. // Close the connection so that it gets reopened next time
  816. if (context != null)
  817. close(context);
  818. // Return "not authenticated" for this request
  819. if (containerLog.isDebugEnabled())
  820. containerLog.debug("Returning null principal.");
  821. return (null);
  822. }
  823. }
  824. // -------------------------------------------------------- Package Methods
  825. // ------------------------------------------------------ Protected Methods
  826. /**
  827. * Return the Principal associated with the specified username and
  828. * credentials, if there is one; otherwise return <code>null</code>.
  829. *
  830. * @param context The directory context
  831. * @param username Username of the Principal to look up
  832. * @param credentials Password or other credentials to use in
  833. * authenticating this username
  834. *
  835. * @exception NamingException if a directory server error occurs
  836. */
  837. public synchronized Principal authenticate(DirContext context,
  838. String username,
  839. String credentials)
  840. throws NamingException {
  841. if (username == null || username.equals("")
  842. || credentials == null || credentials.equals("")) {
  843. if (containerLog.isDebugEnabled())
  844. containerLog.debug("username null or empty: returning null principal.");
  845. return (null);
  846. }
  847. if (userPatternArray != null) {
  848. for (int curUserPattern = 0;
  849. curUserPattern < userPatternFormatArray.length;
  850. curUserPattern++) {
  851. // Retrieve user information
  852. User user = getUser(context, username, credentials, curUserPattern);
  853. if (user != null) {
  854. try {
  855. // Check the user's credentials
  856. if (checkCredentials(context, user, credentials)) {
  857. // Search for additional roles
  858. List<String> roles = getRoles(context, user);
  859. if (containerLog.isDebugEnabled()) {
  860. Iterator<String> it = roles.iterator();
  861. // TODO: Use a single log message
  862. while (it.hasNext()) {
  863. containerLog.debug("Found role: " + it.next());
  864. }
  865. }
  866. return (new GenericPrincipal(username,
  867. credentials,
  868. roles));
  869. }
  870. } catch (InvalidNameException ine) {
  871. // Log the problem for posterity
  872. containerLog.warn(sm.getString("jndiRealm.exception"), ine);
  873. // ignore; this is probably due to a name not fitting
  874. // the search path format exactly, as in a fully-
  875. // qualified name being munged into a search path
  876. // that already contains cn= or vice-versa
  877. }
  878. }
  879. }
  880. return null;
  881. } else {
  882. // Retrieve user information
  883. User user = getUser(context, username, credentials);
  884. if (user == null)
  885. return (null);
  886. // Check the user's credentials
  887. if (!checkCredentials(context, user, credentials))
  888. return (null);
  889. // Search for additional roles
  890. List<String> roles = getRoles(context, user);
  891. if (containerLog.isDebugEnabled()) {
  892. Iterator<String> it = roles.iterator();
  893. // TODO: Use a single log message
  894. while (it.hasNext()) {
  895. containerLog.debug("Found role: " + it.next());
  896. }
  897. }
  898. // Create and return a suitable Principal for this user
  899. return (new GenericPrincipal(username, credentials, roles));
  900. }
  901. }
  902. /**
  903. * Return a User object containing information about the user
  904. * with the specified username, if found in the directory;
  905. * otherwise return <code>null</code>.
  906. *
  907. * @param context The directory context
  908. * @param username Username to be looked up
  909. *
  910. * @exception NamingException if a directory server error occurs
  911. *
  912. * @see #getUser(DirContext, String, String, int)
  913. */
  914. protected User getUser(DirContext context, String username)
  915. throws NamingException {
  916. return getUser(context, username, null, -1);
  917. }
  918. /**
  919. * Return a User object containing information about the user
  920. * with the specified username, if found in the directory;
  921. * otherwise return <code>null</code>.
  922. *
  923. * @param context The directory context
  924. * @param username Username to be looked up
  925. * @param credentials User credentials (optional)
  926. *
  927. * @exception NamingException if a directory server error occurs
  928. *
  929. * @see #getUser(DirContext, String, String, int)
  930. */
  931. protected User getUser(DirContext context, String username, String credentials)
  932. throws NamingException {
  933. return getUser(context, username, credentials, -1);
  934. }
  935. /**
  936. * Return a User object containing information about the user
  937. * with the specified username, if found in the directory;
  938. * otherwise return <code>null</code>.
  939. *
  940. * If the <code>userPassword</code> configuration attribute is
  941. * specified, the value of that attribute is retrieved from the
  942. * user's directory entry. If the <code>userRoleName</code>
  943. * configuration attribute is specified, all values of that
  944. * attribute are retrieved from the directory entry.
  945. *
  946. * @param context The directory context
  947. * @param username Username to be looked up
  948. * @param credentials User credentials (optional)
  949. * @param curUserPattern Index into userPatternFormatArray
  950. *
  951. * @exception NamingException if a directory server error occurs
  952. */
  953. protected User getUser(DirContext context, String username,
  954. String credentials, int curUserPattern)
  955. throws NamingException {
  956. User user = null;
  957. // Get attributes to retrieve from user entry
  958. ArrayList<String> list = new ArrayList<>();
  959. if (userPassword != null)
  960. list.add(userPassword);
  961. if (userRoleName != null)
  962. list.add(userRoleName);
  963. if (userRoleAttribute != null) {
  964. list.add(userRoleAttribute);
  965. }
  966. String[] attrIds = new String[list.size()];
  967. list.toArray(attrIds);
  968. // Use pattern or search for user entry
  969. if (userPatternFormatArray != null && curUserPattern >= 0) {
  970. user = getUserByPattern(context, username, credentials, attrIds, curUserPattern);
  971. } else {
  972. user = getUserBySearch(context, username, attrIds);
  973. }
  974. return user;
  975. }
  976. /**
  977. * Use the distinguished name to locate the directory
  978. * entry for the user with the specified username and
  979. * return a User object; otherwise return <code>null</code>.
  980. *
  981. * @param context The directory context
  982. * @param username The username
  983. * @param attrIds String[]containing names of attributes to
  984. * @param dn Distinguished name of the user
  985. * retrieve.
  986. *
  987. * @exception NamingException if a directory server error occurs
  988. */
  989. protected User getUserByPattern(DirContext context,
  990. String username,
  991. String[] attrIds,
  992. String dn)
  993. throws NamingException {
  994. // If no attributes are requested, no need to look for them
  995. if (attrIds == null || attrIds.length == 0) {
  996. return new User(username, dn, null, null,null);
  997. }
  998. // Get required attributes from user entry
  999. Attributes attrs = null;
  1000. try {
  1001. attrs = context.getAttributes(dn, attrIds);
  1002. } catch (NameNotFoundException e) {
  1003. return (null);
  1004. }
  1005. if (attrs == null)
  1006. return (null);
  1007. // Retrieve value of userPassword
  1008. String password = null;
  1009. if (userPassword != null)
  1010. password = getAttributeValue(userPassword, attrs);
  1011. String userRoleAttrValue = null;
  1012. if (userRoleAttribute != null) {
  1013. userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
  1014. }
  1015. // Retrieve values of userRoleName attribute
  1016. ArrayList<String> roles = null;
  1017. if (userRoleName != null)
  1018. roles = addAttributeValues(userRoleName, attrs, roles);
  1019. return new User(username, dn, password, roles, userRoleAttrValue);
  1020. }
  1021. /**
  1022. * Use the <code>UserPattern</code> configuration attribute to
  1023. * locate the directory entry for the user with the specified
  1024. * username and return a User object; otherwise return
  1025. * <code>null</code>.
  1026. *
  1027. * @param context The directory context
  1028. * @param username The username
  1029. * @param credentials User credentials (optional)
  1030. * @param attrIds String[]containing names of attributes to
  1031. * @param curUserPattern Index into userPatternFormatArray
  1032. *
  1033. * @exception NamingException if a directory server error occurs
  1034. * @see #getUserByPattern(DirContext, String, String[], String)
  1035. */
  1036. protected User getUserByPattern(DirContext context,
  1037. String username,
  1038. String credentials,
  1039. String[] attrIds,
  1040. int curUserPattern)
  1041. throws NamingException {
  1042. User user = null;
  1043. if (username == null || userPatternFormatArray[curUserPattern] == null)
  1044. return (null);
  1045. // Form the dn from the user pattern
  1046. String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
  1047. try {
  1048. user = getUserByPattern(context, username, attrIds, dn);
  1049. } catch (NameNotFoundException e) {
  1050. return (null);
  1051. } catch (NamingException e) {
  1052. // If the getUserByPattern() call fails, try it again with the
  1053. // credentials of the user that we're searching for
  1054. try {
  1055. userCredentialsAdd(context, dn, credentials);
  1056. user = getUserByPattern(context, username, attrIds, dn);
  1057. } finally {
  1058. userCredentialsRemove(context);
  1059. }
  1060. }
  1061. return user;
  1062. }
  1063. /**
  1064. * Search the directory to return a User object containing
  1065. * information about the user with the specified username, if
  1066. * found in the directory; otherwise return <code>null</code>.
  1067. *
  1068. * @param context The directory context
  1069. * @param username The username
  1070. * @param attrIds String[]containing names of attributes to retrieve.
  1071. *
  1072. * @exception NamingException if a directory server error occurs
  1073. */
  1074. protected User getUserBySearch(DirContext context,
  1075. String username,
  1076. String[] attrIds)
  1077. throws NamingException {
  1078. if (username == null || userSearchFormat == null)
  1079. return (null);
  1080. // Form the search filter
  1081. String filter = userSearchFormat.format(new String[] { username });
  1082. // Set up the search controls
  1083. SearchControls constraints = new SearchControls();
  1084. if (userSubtree) {
  1085. constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
  1086. }
  1087. else {
  1088. constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
  1089. }
  1090. constraints.setCountLimit(sizeLimit);
  1091. constraints.setTimeLimit(timeLimit);
  1092. // Specify the attributes to be retrieved
  1093. if (attrIds == null)
  1094. attrIds = new String[0];
  1095. constraints.setReturningAttributes(attrIds);
  1096. NamingEnumeration<SearchResult> results =
  1097. context.search(userBase, filter, constraints);
  1098. // Fail if no entries found
  1099. try {
  1100. if (results == null || !results.hasMore()) {
  1101. return (null);
  1102. }
  1103. } catch (PartialResultException ex) {
  1104. if (!adCompat)
  1105. throw ex;
  1106. else
  1107. return (null);
  1108. }
  1109. // Get result for the first entry found
  1110. SearchResult result = results.next();
  1111. // Check no further entries were found
  1112. try {
  1113. if (results.hasMore()) {
  1114. if(containerLog.isInfoEnabled())
  1115. containerLog.info("username " + username + " has multiple entries");
  1116. return (null);
  1117. }
  1118. } catch (PartialResultException ex) {
  1119. if (!adCompat)
  1120. throw ex;
  1121. }
  1122. String dn = getDistinguishedName(context, userBase, result);
  1123. if (containerLog.isTraceEnabled())
  1124. containerLog.trace(" entry found for " + username + " with dn " + dn);
  1125. // Get the entry's attributes
  1126. Attributes attrs = result.getAttributes();
  1127. if (attrs == null)
  1128. return null;
  1129. // Retrieve value of userPassword
  1130. String password = null;
  1131. if (userPassword != null)
  1132. password = getAttributeValue(userPassword, attrs);
  1133. String userRoleAttrValue = null;
  1134. if (userRoleAttribute != null) {
  1135. userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
  1136. }
  1137. // Retrieve values of userRoleName attribute
  1138. ArrayList<String> roles = null;
  1139. if (userRoleName != null)
  1140. roles = addAttributeValues(userRoleName, attrs, roles);
  1141. return new User(username, dn, password, roles, userRoleAttrValue);
  1142. }
  1143. /**
  1144. * Check whether the given User can be authenticated with the
  1145. * given credentials. If the <code>userPassword</code>
  1146. * configuration attribute is specified, the credentials
  1147. * previously retrieved from the directory are compared explicitly
  1148. * with those presented by the user. Otherwise the presented
  1149. * credentials are checked by binding to the directory as the
  1150. * user.
  1151. *
  1152. * @param context The directory context
  1153. * @param user The User to be authenticated
  1154. * @param credentials The credentials presented by the user
  1155. *
  1156. * @exception NamingException if a directory server error occurs
  1157. */
  1158. protected boolean checkCredentials(DirContext context,
  1159. User user,
  1160. String credentials)
  1161. throws NamingException {
  1162. boolean validated = false;
  1163. if (userPassword == null) {
  1164. validated = bindAsUser(context, user, credentials);
  1165. } else {
  1166. validated = compareCredentials(context, user, credentials);
  1167. }
  1168. if (containerLog.isTraceEnabled()) {
  1169. if (validated) {
  1170. containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
  1171. user.getUserName()));
  1172. } else {
  1173. containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
  1174. user.getUserName()));
  1175. }
  1176. }
  1177. return (validated);
  1178. }
  1179. /**
  1180. * Check whether the credentials presented by the user match those
  1181. * retrieved from the directory.
  1182. *
  1183. * @param context The directory context
  1184. * @param info The User to be authenticated
  1185. * @param credentials Authentication credentials
  1186. *
  1187. * @exception NamingException if a directory server error occurs
  1188. */
  1189. protected boolean compareCredentials(DirContext context,
  1190. User info,
  1191. String credentials)
  1192. throws NamingException {
  1193. // Validate the credentials specified by the user
  1194. if (containerLog.isTraceEnabled())
  1195. containerLog.trace(" validating credentials");
  1196. if (info == null || credentials == null)
  1197. return (false);
  1198. String password = info.getPassword();
  1199. return compareCredentials(credentials, password);
  1200. }
  1201. /**
  1202. * Check credentials by binding to the directory as the user
  1203. *
  1204. * @param context The directory context
  1205. * @param user The User to be authenticated
  1206. * @param credentials Authentication credentials
  1207. *
  1208. * @exception NamingException if a directory server error occurs
  1209. */
  1210. protected boolean bindAsUser(DirContext context,
  1211. User user,
  1212. String credentials)
  1213. throws NamingException {
  1214. if (credentials == null || user == null)
  1215. return (false);
  1216. String dn = user.getDN();
  1217. if (dn == null)
  1218. return (false);
  1219. // Validate the credentials specified by the user
  1220. if (containerLog.isTraceEnabled()) {
  1221. containerLog.trace(" validating credentials by binding as the user");
  1222. }
  1223. userCredentialsAdd(context, dn, credentials);
  1224. // Elicit an LDAP bind operation
  1225. boolean validated = false;
  1226. try {
  1227. if (containerLog.isTraceEnabled()) {
  1228. containerLog.trace(" binding as " + dn);
  1229. }
  1230. context.getAttributes("", null);
  1231. validated = true;
  1232. }
  1233. catch (AuthenticationException e) {
  1234. if (containerLog.isTraceEnabled()) {
  1235. containerLog.trace(" bind attempt failed");
  1236. }
  1237. }
  1238. userCredentialsRemove(context);
  1239. return (validated);
  1240. }
  1241. /**
  1242. * Configure the context to use the provided credentials for
  1243. * authentication.
  1244. *
  1245. * @param context DirContext to configure
  1246. * @param dn Distinguished name of user
  1247. * @param credentials Credentials of user
  1248. */
  1249. private void userCredentialsAdd(DirContext context, String dn,
  1250. String credentials) throws NamingException {
  1251. // Set up security environment to bind as the user
  1252. context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
  1253. context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
  1254. }
  1255. /**
  1256. * Configure the context to use {@link #connectionName} and
  1257. * {@link #connectionPassword} if specified or an anonymous connection if
  1258. * those attributes are not specified.
  1259. *
  1260. * @param context DirContext to configure
  1261. */
  1262. private void userCredentialsRemove(DirContext context)
  1263. throws NamingException {
  1264. // Restore the original security environment
  1265. if (connectionName != null) {
  1266. context.addToEnvironment(Context.SECURITY_PRINCIPAL,
  1267. connectionName);
  1268. } else {
  1269. context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
  1270. }
  1271. if (connectionPassword != null) {
  1272. context.addToEnvironment(Context.SECURITY_CREDENTIALS,
  1273. connectionPassword);
  1274. }
  1275. else {
  1276. context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
  1277. }
  1278. }
  1279. /**
  1280. * Return a List of roles associated with the given User. Any
  1281. * roles present in the user's directory entry are supplemented by
  1282. * a directory search. If no roles are associated with this user,
  1283. * a zero-length List is returned.
  1284. *
  1285. * @param context The directory context we are searching
  1286. * @param user The User to be checked
  1287. *
  1288. * @exception NamingException if a directory server error occurs
  1289. */
  1290. protected List<String> getRoles(DirContext context, User user)
  1291. throws NamingException {
  1292. if (user == null)
  1293. return (null);
  1294. String dn = user.getDN();

Large files files are truncated, but you can click here to view the full file