PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

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

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

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