0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.realm;
0019:
0020: import java.io.IOException;
0021: import java.security.Principal;
0022: import java.text.MessageFormat;
0023: import java.util.ArrayList;
0024: import java.util.Arrays;
0025: import java.util.Hashtable;
0026: import java.util.List;
0027:
0028: import javax.naming.Context;
0029: import javax.naming.CommunicationException;
0030: import javax.naming.CompositeName;
0031: import javax.naming.InvalidNameException;
0032: import javax.naming.NameNotFoundException;
0033: import javax.naming.NamingEnumeration;
0034: import javax.naming.NamingException;
0035: import javax.naming.NameParser;
0036: import javax.naming.Name;
0037: import javax.naming.AuthenticationException;
0038: import javax.naming.ServiceUnavailableException;
0039: import javax.naming.directory.Attribute;
0040: import javax.naming.directory.Attributes;
0041: import javax.naming.directory.DirContext;
0042: import javax.naming.directory.InitialDirContext;
0043: import javax.naming.directory.SearchControls;
0044: import javax.naming.directory.SearchResult;
0045: import org.apache.catalina.LifecycleException;
0046: import org.apache.catalina.util.Base64;
0047: import org.apache.tomcat.util.buf.ByteChunk;
0048: import org.apache.tomcat.util.buf.CharChunk;
0049:
0050: /**
0051: * <p>Implementation of <strong>Realm</strong> that works with a directory
0052: * server accessed via the Java Naming and Directory Interface (JNDI) APIs.
0053: * The following constraints are imposed on the data structure in the
0054: * underlying directory server:</p>
0055: * <ul>
0056: *
0057: * <li>Each user that can be authenticated is represented by an individual
0058: * element in the top level <code>DirContext</code> that is accessed
0059: * via the <code>connectionURL</code> property.</li>
0060: *
0061: * <li>If a socket connection can not be made to the <code>connectURL</code>
0062: * an attempt will be made to use the <code>alternateURL</code> if it
0063: * exists.</li>
0064: *
0065: * <li>Each user element has a distinguished name that can be formed by
0066: * substituting the presented username into a pattern configured by the
0067: * <code>userPattern</code> property.</li>
0068: *
0069: * <li>Alternatively, if the <code>userPattern</code> property is not
0070: * specified, a unique element can be located by searching the directory
0071: * context. In this case:
0072: * <ul>
0073: * <li>The <code>userSearch</code> pattern specifies the search filter
0074: * after substitution of the username.</li>
0075: * <li>The <code>userBase</code> property can be set to the element that
0076: * is the base of the subtree containing users. If not specified,
0077: * the search base is the top-level context.</li>
0078: * <li>The <code>userSubtree</code> property can be set to
0079: * <code>true</code> if you wish to search the entire subtree of the
0080: * directory context. The default value of <code>false</code>
0081: * requests a search of only the current level.</li>
0082: * </ul>
0083: * </li>
0084: *
0085: * <li>The user may be authenticated by binding to the directory with the
0086: * username and password presented. This method is used when the
0087: * <code>userPassword</code> property is not specified.</li>
0088: *
0089: * <li>The user may be authenticated by retrieving the value of an attribute
0090: * from the directory and comparing it explicitly with the value presented
0091: * by the user. This method is used when the <code>userPassword</code>
0092: * property is specified, in which case:
0093: * <ul>
0094: * <li>The element for this user must contain an attribute named by the
0095: * <code>userPassword</code> property.
0096: * <li>The value of the user password attribute is either a cleartext
0097: * String, or the result of passing a cleartext String through the
0098: * <code>RealmBase.digest()</code> method (using the standard digest
0099: * support included in <code>RealmBase</code>).
0100: * <li>The user is considered to be authenticated if the presented
0101: * credentials (after being passed through
0102: * <code>RealmBase.digest()</code>) are equal to the retrieved value
0103: * for the user password attribute.</li>
0104: * </ul></li>
0105: *
0106: * <li>Each group of users that has been assigned a particular role may be
0107: * represented by an individual element in the top level
0108: * <code>DirContext</code> that is accessed via the
0109: * <code>connectionURL</code> property. This element has the following
0110: * characteristics:
0111: * <ul>
0112: * <li>The set of all possible groups of interest can be selected by a
0113: * search pattern configured by the <code>roleSearch</code>
0114: * property.</li>
0115: * <li>The <code>roleSearch</code> pattern optionally includes pattern
0116: * replacements "{0}" for the distinguished name, and/or "{1}" for
0117: * the username, of the authenticated user for which roles will be
0118: * retrieved.</li>
0119: * <li>The <code>roleBase</code> property can be set to the element that
0120: * is the base of the search for matching roles. If not specified,
0121: * the entire context will be searched.</li>
0122: * <li>The <code>roleSubtree</code> property can be set to
0123: * <code>true</code> if you wish to search the entire subtree of the
0124: * directory context. The default value of <code>false</code>
0125: * requests a search of only the current level.</li>
0126: * <li>The element includes an attribute (whose name is configured by
0127: * the <code>roleName</code> property) containing the name of the
0128: * role represented by this element.</li>
0129: * </ul></li>
0130: *
0131: * <li>In addition, roles may be represented by the values of an attribute
0132: * in the user's element whose name is configured by the
0133: * <code>userRoleName</code> property.</li>
0134: *
0135: * <li>Note that the standard <code><security-role-ref></code> element in
0136: * the web application deployment descriptor allows applications to refer
0137: * to roles programmatically by names other than those used in the
0138: * directory server itself.</li>
0139: * </ul>
0140: *
0141: * <p><strong>TODO</strong> - Support connection pooling (including message
0142: * format objects) so that <code>authenticate()</code> does not have to be
0143: * synchronized.</p>
0144: *
0145: * <p><strong>WARNING</strong> - There is a reported bug against the Netscape
0146: * provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
0147: * successfully authenticated a non-existing user. The
0148: * report is here: http://issues.apache.org/bugzilla/show_bug.cgi?id=11210 .
0149: * With luck, Netscape has updated their provider code and this is not an
0150: * issue. </p>
0151: *
0152: * @author John Holman
0153: * @author Craig R. McClanahan
0154: * @version $Revision: 543693 $ $Date: 2007-06-02 03:42:17 +0200 (sam., 02 juin 2007) $
0155: */
0156:
0157: public class JNDIRealm extends RealmBase {
0158:
0159: // ----------------------------------------------------- Instance Variables
0160:
0161: /**
0162: * The type of authentication to use
0163: */
0164: protected String authentication = null;
0165:
0166: /**
0167: * The connection username for the server we will contact.
0168: */
0169: protected String connectionName = null;
0170:
0171: /**
0172: * The connection password for the server we will contact.
0173: */
0174: protected String connectionPassword = null;
0175:
0176: /**
0177: * The connection URL for the server we will contact.
0178: */
0179: protected String connectionURL = null;
0180:
0181: /**
0182: * The directory context linking us to our directory server.
0183: */
0184: protected DirContext context = null;
0185:
0186: /**
0187: * The JNDI context factory used to acquire our InitialContext. By
0188: * default, assumes use of an LDAP server using the standard JNDI LDAP
0189: * provider.
0190: */
0191: protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
0192:
0193: /**
0194: * How aliases should be dereferenced during search operations.
0195: */
0196: protected String derefAliases = null;
0197:
0198: /**
0199: * Constant that holds the name of the environment property for specifying
0200: * the manner in which aliases should be dereferenced.
0201: */
0202: public final static String DEREF_ALIASES = "java.naming.ldap.derefAliases";
0203:
0204: /**
0205: * Descriptive information about this Realm implementation.
0206: */
0207: protected static final String info = "org.apache.catalina.realm.JNDIRealm/1.0";
0208:
0209: /**
0210: * Descriptive information about this Realm implementation.
0211: */
0212: protected static final String name = "JNDIRealm";
0213:
0214: /**
0215: * The protocol that will be used in the communication with the
0216: * directory server.
0217: */
0218: protected String protocol = null;
0219:
0220: /**
0221: * How should we handle referrals? Microsoft Active Directory can't handle
0222: * the default case, so an application authenticating against AD must
0223: * set referrals to "follow".
0224: */
0225: protected String referrals = null;
0226:
0227: /**
0228: * The base element for user searches.
0229: */
0230: protected String userBase = "";
0231:
0232: /**
0233: * The message format used to search for a user, with "{0}" marking
0234: * the spot where the username goes.
0235: */
0236: protected String userSearch = null;
0237:
0238: /**
0239: * The MessageFormat object associated with the current
0240: * <code>userSearch</code>.
0241: */
0242: protected MessageFormat userSearchFormat = null;
0243:
0244: /**
0245: * Should we search the entire subtree for matching users?
0246: */
0247: protected boolean userSubtree = false;
0248:
0249: /**
0250: * The attribute name used to retrieve the user password.
0251: */
0252: protected String userPassword = null;
0253:
0254: /**
0255: * A string of LDAP user patterns or paths, ":"-separated
0256: * These will be used to form the distinguished name of a
0257: * user, with "{0}" marking the spot where the specified username
0258: * goes.
0259: * This is similar to userPattern, but allows for multiple searches
0260: * for a user.
0261: */
0262: protected String[] userPatternArray = null;
0263:
0264: /**
0265: * The message format used to form the distinguished name of a
0266: * user, with "{0}" marking the spot where the specified username
0267: * goes.
0268: */
0269: protected String userPattern = null;
0270:
0271: /**
0272: * An array of MessageFormat objects associated with the current
0273: * <code>userPatternArray</code>.
0274: */
0275: protected MessageFormat[] userPatternFormatArray = null;
0276:
0277: /**
0278: * The base element for role searches.
0279: */
0280: protected String roleBase = "";
0281:
0282: /**
0283: * The MessageFormat object associated with the current
0284: * <code>roleSearch</code>.
0285: */
0286: protected MessageFormat roleFormat = null;
0287:
0288: /**
0289: * The name of an attribute in the user's entry containing
0290: * roles for that user
0291: */
0292: protected String userRoleName = null;
0293:
0294: /**
0295: * The name of the attribute containing roles held elsewhere
0296: */
0297: protected String roleName = null;
0298:
0299: /**
0300: * The message format used to select roles for a user, with "{0}" marking
0301: * the spot where the distinguished name of the user goes.
0302: */
0303: protected String roleSearch = null;
0304:
0305: /**
0306: * Should we search the entire subtree for matching memberships?
0307: */
0308: protected boolean roleSubtree = false;
0309:
0310: /**
0311: * An alternate URL, to which, we should connect if connectionURL fails.
0312: */
0313: protected String alternateURL;
0314:
0315: /**
0316: * The number of connection attempts. If greater than zero we use the
0317: * alternate url.
0318: */
0319: protected int connectionAttempt = 0;
0320:
0321: /**
0322: * The current user pattern to be used for lookup and binding of a user.
0323: */
0324: protected int curUserPattern = 0;
0325:
0326: // ------------------------------------------------------------- Properties
0327:
0328: /**
0329: * Return the type of authentication to use.
0330: */
0331: public String getAuthentication() {
0332:
0333: return authentication;
0334:
0335: }
0336:
0337: /**
0338: * Set the type of authentication to use.
0339: *
0340: * @param authentication The authentication
0341: */
0342: public void setAuthentication(String authentication) {
0343:
0344: this .authentication = authentication;
0345:
0346: }
0347:
0348: /**
0349: * Return the connection username for this Realm.
0350: */
0351: public String getConnectionName() {
0352:
0353: return (this .connectionName);
0354:
0355: }
0356:
0357: /**
0358: * Set the connection username for this Realm.
0359: *
0360: * @param connectionName The new connection username
0361: */
0362: public void setConnectionName(String connectionName) {
0363:
0364: this .connectionName = connectionName;
0365:
0366: }
0367:
0368: /**
0369: * Return the connection password for this Realm.
0370: */
0371: public String getConnectionPassword() {
0372:
0373: return (this .connectionPassword);
0374:
0375: }
0376:
0377: /**
0378: * Set the connection password for this Realm.
0379: *
0380: * @param connectionPassword The new connection password
0381: */
0382: public void setConnectionPassword(String connectionPassword) {
0383:
0384: this .connectionPassword = connectionPassword;
0385:
0386: }
0387:
0388: /**
0389: * Return the connection URL for this Realm.
0390: */
0391: public String getConnectionURL() {
0392:
0393: return (this .connectionURL);
0394:
0395: }
0396:
0397: /**
0398: * Set the connection URL for this Realm.
0399: *
0400: * @param connectionURL The new connection URL
0401: */
0402: public void setConnectionURL(String connectionURL) {
0403:
0404: this .connectionURL = connectionURL;
0405:
0406: }
0407:
0408: /**
0409: * Return the JNDI context factory for this Realm.
0410: */
0411: public String getContextFactory() {
0412:
0413: return (this .contextFactory);
0414:
0415: }
0416:
0417: /**
0418: * Set the JNDI context factory for this Realm.
0419: *
0420: * @param contextFactory The new context factory
0421: */
0422: public void setContextFactory(String contextFactory) {
0423:
0424: this .contextFactory = contextFactory;
0425:
0426: }
0427:
0428: /**
0429: * Return the derefAliases setting to be used.
0430: */
0431: public java.lang.String getDerefAliases() {
0432: return derefAliases;
0433: }
0434:
0435: /**
0436: * Set the value for derefAliases to be used when searching the directory.
0437: *
0438: * @param derefAliases New value of property derefAliases.
0439: */
0440: public void setDerefAliases(java.lang.String derefAliases) {
0441: this .derefAliases = derefAliases;
0442: }
0443:
0444: /**
0445: * Return the protocol to be used.
0446: */
0447: public String getProtocol() {
0448:
0449: return protocol;
0450:
0451: }
0452:
0453: /**
0454: * Set the protocol for this Realm.
0455: *
0456: * @param protocol The new protocol.
0457: */
0458: public void setProtocol(String protocol) {
0459:
0460: this .protocol = protocol;
0461:
0462: }
0463:
0464: /**
0465: * Returns the current settings for handling JNDI referrals.
0466: */
0467: public String getReferrals() {
0468: return referrals;
0469: }
0470:
0471: /**
0472: * How do we handle JNDI referrals? ignore, follow, or throw
0473: * (see javax.naming.Context.REFERRAL for more information).
0474: */
0475: public void setReferrals(String referrals) {
0476: this .referrals = referrals;
0477: }
0478:
0479: /**
0480: * Return the base element for user searches.
0481: */
0482: public String getUserBase() {
0483:
0484: return (this .userBase);
0485:
0486: }
0487:
0488: /**
0489: * Set the base element for user searches.
0490: *
0491: * @param userBase The new base element
0492: */
0493: public void setUserBase(String userBase) {
0494:
0495: this .userBase = userBase;
0496:
0497: }
0498:
0499: /**
0500: * Return the message format pattern for selecting users in this Realm.
0501: */
0502: public String getUserSearch() {
0503:
0504: return (this .userSearch);
0505:
0506: }
0507:
0508: /**
0509: * Set the message format pattern for selecting users in this Realm.
0510: *
0511: * @param userSearch The new user search pattern
0512: */
0513: public void setUserSearch(String userSearch) {
0514:
0515: this .userSearch = userSearch;
0516: if (userSearch == null)
0517: userSearchFormat = null;
0518: else
0519: userSearchFormat = new MessageFormat(userSearch);
0520:
0521: }
0522:
0523: /**
0524: * Return the "search subtree for users" flag.
0525: */
0526: public boolean getUserSubtree() {
0527:
0528: return (this .userSubtree);
0529:
0530: }
0531:
0532: /**
0533: * Set the "search subtree for users" flag.
0534: *
0535: * @param userSubtree The new search flag
0536: */
0537: public void setUserSubtree(boolean userSubtree) {
0538:
0539: this .userSubtree = userSubtree;
0540:
0541: }
0542:
0543: /**
0544: * Return the user role name attribute name for this Realm.
0545: */
0546: public String getUserRoleName() {
0547:
0548: return userRoleName;
0549: }
0550:
0551: /**
0552: * Set the user role name attribute name for this Realm.
0553: *
0554: * @param userRoleName The new userRole name attribute name
0555: */
0556: public void setUserRoleName(String userRoleName) {
0557:
0558: this .userRoleName = userRoleName;
0559:
0560: }
0561:
0562: /**
0563: * Return the base element for role searches.
0564: */
0565: public String getRoleBase() {
0566:
0567: return (this .roleBase);
0568:
0569: }
0570:
0571: /**
0572: * Set the base element for role searches.
0573: *
0574: * @param roleBase The new base element
0575: */
0576: public void setRoleBase(String roleBase) {
0577:
0578: this .roleBase = roleBase;
0579:
0580: }
0581:
0582: /**
0583: * Return the role name attribute name for this Realm.
0584: */
0585: public String getRoleName() {
0586:
0587: return (this .roleName);
0588:
0589: }
0590:
0591: /**
0592: * Set the role name attribute name for this Realm.
0593: *
0594: * @param roleName The new role name attribute name
0595: */
0596: public void setRoleName(String roleName) {
0597:
0598: this .roleName = roleName;
0599:
0600: }
0601:
0602: /**
0603: * Return the message format pattern for selecting roles in this Realm.
0604: */
0605: public String getRoleSearch() {
0606:
0607: return (this .roleSearch);
0608:
0609: }
0610:
0611: /**
0612: * Set the message format pattern for selecting roles in this Realm.
0613: *
0614: * @param roleSearch The new role search pattern
0615: */
0616: public void setRoleSearch(String roleSearch) {
0617:
0618: this .roleSearch = roleSearch;
0619: if (roleSearch == null)
0620: roleFormat = null;
0621: else
0622: roleFormat = new MessageFormat(roleSearch);
0623:
0624: }
0625:
0626: /**
0627: * Return the "search subtree for roles" flag.
0628: */
0629: public boolean getRoleSubtree() {
0630:
0631: return (this .roleSubtree);
0632:
0633: }
0634:
0635: /**
0636: * Set the "search subtree for roles" flag.
0637: *
0638: * @param roleSubtree The new search flag
0639: */
0640: public void setRoleSubtree(boolean roleSubtree) {
0641:
0642: this .roleSubtree = roleSubtree;
0643:
0644: }
0645:
0646: /**
0647: * Return the password attribute used to retrieve the user password.
0648: */
0649: public String getUserPassword() {
0650:
0651: return (this .userPassword);
0652:
0653: }
0654:
0655: /**
0656: * Set the password attribute used to retrieve the user password.
0657: *
0658: * @param userPassword The new password attribute
0659: */
0660: public void setUserPassword(String userPassword) {
0661:
0662: this .userPassword = userPassword;
0663:
0664: }
0665:
0666: /**
0667: * Return the message format pattern for selecting users in this Realm.
0668: */
0669: public String getUserPattern() {
0670:
0671: return (this .userPattern);
0672:
0673: }
0674:
0675: /**
0676: * Set the message format pattern for selecting users in this Realm.
0677: * This may be one simple pattern, or multiple patterns to be tried,
0678: * separated by parentheses. (for example, either "cn={0}", or
0679: * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
0680: * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
0681: * also valid. Complex search strings with &, etc are NOT supported.
0682: *
0683: * @param userPattern The new user pattern
0684: */
0685: public void setUserPattern(String userPattern) {
0686:
0687: this .userPattern = userPattern;
0688: if (userPattern == null)
0689: userPatternArray = null;
0690: else {
0691: userPatternArray = parseUserPatternString(userPattern);
0692: int len = this .userPatternArray.length;
0693: userPatternFormatArray = new MessageFormat[len];
0694: for (int i = 0; i < len; i++) {
0695: userPatternFormatArray[i] = new MessageFormat(
0696: userPatternArray[i]);
0697: }
0698: }
0699: }
0700:
0701: /**
0702: * Getter for property alternateURL.
0703: *
0704: * @return Value of property alternateURL.
0705: */
0706: public String getAlternateURL() {
0707:
0708: return this .alternateURL;
0709:
0710: }
0711:
0712: /**
0713: * Setter for property alternateURL.
0714: *
0715: * @param alternateURL New value of property alternateURL.
0716: */
0717: public void setAlternateURL(String alternateURL) {
0718:
0719: this .alternateURL = alternateURL;
0720:
0721: }
0722:
0723: // ---------------------------------------------------------- Realm Methods
0724:
0725: /**
0726: * Return the Principal associated with the specified username and
0727: * credentials, if there is one; otherwise return <code>null</code>.
0728: *
0729: * If there are any errors with the JDBC connection, executing
0730: * the query or anything we return null (don't authenticate). This
0731: * event is also logged, and the connection will be closed so that
0732: * a subsequent request will automatically re-open it.
0733: *
0734: * @param username Username of the Principal to look up
0735: * @param credentials Password or other credentials to use in
0736: * authenticating this username
0737: */
0738: public Principal authenticate(String username, String credentials) {
0739:
0740: DirContext context = null;
0741: Principal principal = null;
0742:
0743: try {
0744:
0745: // Ensure that we have a directory context available
0746: context = open();
0747:
0748: // Occassionally the directory context will timeout. Try one more
0749: // time before giving up.
0750: try {
0751:
0752: // Authenticate the specified username if possible
0753: principal = authenticate(context, username, credentials);
0754:
0755: } catch (NullPointerException e) {
0756: /* BZ 42449 - Kludge Sun's LDAP provider
0757: with broken SSL
0758: */
0759: // log the exception so we know it's there.
0760: containerLog.warn(sm.getString("jndiRealm.exception"),
0761: e);
0762:
0763: // close the connection so we know it will be reopened.
0764: if (context != null)
0765: close(context);
0766:
0767: // open a new directory context.
0768: context = open();
0769:
0770: // Try the authentication again.
0771: principal = authenticate(context, username, credentials);
0772:
0773: } catch (CommunicationException e) {
0774:
0775: // log the exception so we know it's there.
0776: containerLog.warn(sm.getString("jndiRealm.exception"),
0777: e);
0778:
0779: // close the connection so we know it will be reopened.
0780: if (context != null)
0781: close(context);
0782:
0783: // open a new directory context.
0784: context = open();
0785:
0786: // Try the authentication again.
0787: principal = authenticate(context, username, credentials);
0788:
0789: } catch (ServiceUnavailableException e) {
0790:
0791: // log the exception so we know it's there.
0792: containerLog.warn(sm.getString("jndiRealm.exception"),
0793: e);
0794:
0795: // close the connection so we know it will be reopened.
0796: if (context != null)
0797: close(context);
0798:
0799: // open a new directory context.
0800: context = open();
0801:
0802: // Try the authentication again.
0803: principal = authenticate(context, username, credentials);
0804:
0805: }
0806:
0807: // Release this context
0808: release(context);
0809:
0810: // Return the authenticated Principal (if any)
0811: return (principal);
0812:
0813: } catch (NamingException e) {
0814:
0815: // Log the problem for posterity
0816: containerLog.error(sm.getString("jndiRealm.exception"), e);
0817:
0818: // Close the connection so that it gets reopened next time
0819: if (context != null)
0820: close(context);
0821:
0822: // Return "not authenticated" for this request
0823: return (null);
0824:
0825: }
0826:
0827: }
0828:
0829: // -------------------------------------------------------- Package Methods
0830:
0831: // ------------------------------------------------------ Protected Methods
0832:
0833: /**
0834: * Return the Principal associated with the specified username and
0835: * credentials, if there is one; otherwise return <code>null</code>.
0836: *
0837: * @param context The directory context
0838: * @param username Username of the Principal to look up
0839: * @param credentials Password or other credentials to use in
0840: * authenticating this username
0841: *
0842: * @exception NamingException if a directory server error occurs
0843: */
0844: public synchronized Principal authenticate(DirContext context,
0845: String username, String credentials) throws NamingException {
0846:
0847: if (username == null || username.equals("")
0848: || credentials == null || credentials.equals(""))
0849: return (null);
0850:
0851: if (userPatternArray != null) {
0852: for (curUserPattern = 0; curUserPattern < userPatternFormatArray.length; curUserPattern++) {
0853: // Retrieve user information
0854: User user = getUser(context, username);
0855: if (user != null) {
0856: try {
0857: // Check the user's credentials
0858: if (checkCredentials(context, user, credentials)) {
0859: // Search for additional roles
0860: List<String> roles = getRoles(context, user);
0861: return (new GenericPrincipal(this ,
0862: username, credentials, roles));
0863: }
0864: } catch (InvalidNameException ine) {
0865: // Log the problem for posterity
0866: containerLog.warn(sm
0867: .getString("jndiRealm.exception"), ine);
0868: // ignore; this is probably due to a name not fitting
0869: // the search path format exactly, as in a fully-
0870: // qualified name being munged into a search path
0871: // that already contains cn= or vice-versa
0872: }
0873: }
0874: }
0875: return null;
0876: } else {
0877: // Retrieve user information
0878: User user = getUser(context, username);
0879: if (user == null)
0880: return (null);
0881:
0882: // Check the user's credentials
0883: if (!checkCredentials(context, user, credentials))
0884: return (null);
0885:
0886: // Search for additional roles
0887: List<String> roles = getRoles(context, user);
0888:
0889: // Create and return a suitable Principal for this user
0890: return (new GenericPrincipal(this , username, credentials,
0891: roles));
0892: }
0893: }
0894:
0895: /**
0896: * Return a User object containing information about the user
0897: * with the specified username, if found in the directory;
0898: * otherwise return <code>null</code>.
0899: *
0900: * If the <code>userPassword</code> configuration attribute is
0901: * specified, the value of that attribute is retrieved from the
0902: * user's directory entry. If the <code>userRoleName</code>
0903: * configuration attribute is specified, all values of that
0904: * attribute are retrieved from the directory entry.
0905: *
0906: * @param context The directory context
0907: * @param username Username to be looked up
0908: *
0909: * @exception NamingException if a directory server error occurs
0910: */
0911: protected User getUser(DirContext context, String username)
0912: throws NamingException {
0913:
0914: User user = null;
0915:
0916: // Get attributes to retrieve from user entry
0917: ArrayList<String> list = new ArrayList<String>();
0918: if (userPassword != null)
0919: list.add(userPassword);
0920: if (userRoleName != null)
0921: list.add(userRoleName);
0922: String[] attrIds = new String[list.size()];
0923: list.toArray(attrIds);
0924:
0925: // Use pattern or search for user entry
0926: if (userPatternFormatArray != null) {
0927: user = getUserByPattern(context, username, attrIds);
0928: } else {
0929: user = getUserBySearch(context, username, attrIds);
0930: }
0931:
0932: return user;
0933: }
0934:
0935: /**
0936: * Use the <code>UserPattern</code> configuration attribute to
0937: * locate the directory entry for the user with the specified
0938: * username and return a User object; otherwise return
0939: * <code>null</code>.
0940: *
0941: * @param context The directory context
0942: * @param username The username
0943: * @param attrIds String[]containing names of attributes to
0944: * retrieve.
0945: *
0946: * @exception NamingException if a directory server error occurs
0947: */
0948: protected User getUserByPattern(DirContext context,
0949: String username, String[] attrIds) throws NamingException {
0950:
0951: if (username == null
0952: || userPatternFormatArray[curUserPattern] == null)
0953: return (null);
0954:
0955: // Form the dn from the user pattern
0956: String dn = userPatternFormatArray[curUserPattern]
0957: .format(new String[] { username });
0958:
0959: // Get required attributes from user entry
0960: Attributes attrs = null;
0961: try {
0962: attrs = context.getAttributes(dn, attrIds);
0963: } catch (NameNotFoundException e) {
0964: return (null);
0965: }
0966: if (attrs == null)
0967: return (null);
0968:
0969: // Retrieve value of userPassword
0970: String password = null;
0971: if (userPassword != null)
0972: password = getAttributeValue(userPassword, attrs);
0973:
0974: // Retrieve values of userRoleName attribute
0975: ArrayList<String> roles = null;
0976: if (userRoleName != null)
0977: roles = addAttributeValues(userRoleName, attrs, roles);
0978:
0979: return new User(username, dn, password, roles);
0980: }
0981:
0982: /**
0983: * Search the directory to return a User object containing
0984: * information about the user with the specified username, if
0985: * found in the directory; otherwise return <code>null</code>.
0986: *
0987: * @param context The directory context
0988: * @param username The username
0989: * @param attrIds String[]containing names of attributes to retrieve.
0990: *
0991: * @exception NamingException if a directory server error occurs
0992: */
0993: protected User getUserBySearch(DirContext context, String username,
0994: String[] attrIds) throws NamingException {
0995:
0996: if (username == null || userSearchFormat == null)
0997: return (null);
0998:
0999: // Form the search filter
1000: String filter = userSearchFormat
1001: .format(new String[] { username });
1002:
1003: // Set up the search controls
1004: SearchControls constraints = new SearchControls();
1005:
1006: if (userSubtree) {
1007: constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
1008: } else {
1009: constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1010: }
1011:
1012: // Specify the attributes to be retrieved
1013: if (attrIds == null)
1014: attrIds = new String[0];
1015: constraints.setReturningAttributes(attrIds);
1016:
1017: NamingEnumeration results = context.search(userBase, filter,
1018: constraints);
1019:
1020: // Fail if no entries found
1021: if (results == null || !results.hasMore()) {
1022: return (null);
1023: }
1024:
1025: // Get result for the first entry found
1026: SearchResult result = (SearchResult) results.next();
1027:
1028: // Check no further entries were found
1029: if (results.hasMore()) {
1030: if (containerLog.isInfoEnabled())
1031: containerLog.info("username " + username
1032: + " has multiple entries");
1033: return (null);
1034: }
1035:
1036: // Get the entry's distinguished name
1037: NameParser parser = context.getNameParser("");
1038: Name contextName = parser.parse(context.getNameInNamespace());
1039: Name baseName = parser.parse(userBase);
1040:
1041: // Bugzilla 32269
1042: Name entryName = parser.parse(new CompositeName(result
1043: .getName()).get(0));
1044:
1045: Name name = contextName.addAll(baseName);
1046: name = name.addAll(entryName);
1047: String dn = name.toString();
1048:
1049: if (containerLog.isTraceEnabled())
1050: containerLog.trace(" entry found for " + username
1051: + " with dn " + dn);
1052:
1053: // Get the entry's attributes
1054: Attributes attrs = result.getAttributes();
1055: if (attrs == null)
1056: return null;
1057:
1058: // Retrieve value of userPassword
1059: String password = null;
1060: if (userPassword != null)
1061: password = getAttributeValue(userPassword, attrs);
1062:
1063: // Retrieve values of userRoleName attribute
1064: ArrayList<String> roles = null;
1065: if (userRoleName != null)
1066: roles = addAttributeValues(userRoleName, attrs, roles);
1067:
1068: return new User(username, dn, password, roles);
1069: }
1070:
1071: /**
1072: * Check whether the given User can be authenticated with the
1073: * given credentials. If the <code>userPassword</code>
1074: * configuration attribute is specified, the credentials
1075: * previously retrieved from the directory are compared explicitly
1076: * with those presented by the user. Otherwise the presented
1077: * credentials are checked by binding to the directory as the
1078: * user.
1079: *
1080: * @param context The directory context
1081: * @param user The User to be authenticated
1082: * @param credentials The credentials presented by the user
1083: *
1084: * @exception NamingException if a directory server error occurs
1085: */
1086: protected boolean checkCredentials(DirContext context, User user,
1087: String credentials) throws NamingException {
1088:
1089: boolean validated = false;
1090:
1091: if (userPassword == null) {
1092: validated = bindAsUser(context, user, credentials);
1093: } else {
1094: validated = compareCredentials(context, user, credentials);
1095: }
1096:
1097: if (containerLog.isTraceEnabled()) {
1098: if (validated) {
1099: containerLog
1100: .trace(sm.getString(
1101: "jndiRealm.authenticateSuccess",
1102: user.username));
1103: } else {
1104: containerLog
1105: .trace(sm.getString(
1106: "jndiRealm.authenticateFailure",
1107: user.username));
1108: }
1109: }
1110: return (validated);
1111: }
1112:
1113: /**
1114: * Check whether the credentials presented by the user match those
1115: * retrieved from the directory.
1116: *
1117: * @param context The directory context
1118: * @param info The User to be authenticated
1119: * @param credentials Authentication credentials
1120: *
1121: * @exception NamingException if a directory server error occurs
1122: */
1123: protected boolean compareCredentials(DirContext context, User info,
1124: String credentials) throws NamingException {
1125:
1126: if (info == null || credentials == null)
1127: return (false);
1128:
1129: String password = info.password;
1130: if (password == null)
1131: return (false);
1132:
1133: // Validate the credentials specified by the user
1134: if (containerLog.isTraceEnabled())
1135: containerLog.trace(" validating credentials");
1136:
1137: boolean validated = false;
1138: if (hasMessageDigest()) {
1139: // iPlanet support if the values starts with {SHA1}
1140: // The string is in a format compatible with Base64.encode not
1141: // the Hex encoding of the parent class.
1142: if (password.startsWith("{SHA}")) {
1143: /* sync since super.digest() does this same thing */
1144: synchronized (this ) {
1145: password = password.substring(5);
1146: md.reset();
1147: md.update(credentials.getBytes());
1148: String digestedPassword = new String(Base64
1149: .encode(md.digest()));
1150: validated = password.equals(digestedPassword);
1151: }
1152: } else if (password.startsWith("{SSHA}")) {
1153: // Bugzilla 32938
1154: /* sync since super.digest() does this same thing */
1155: synchronized (this ) {
1156: password = password.substring(6);
1157:
1158: md.reset();
1159: md.update(credentials.getBytes());
1160:
1161: // Decode stored password.
1162: ByteChunk pwbc = new ByteChunk(password.length());
1163: try {
1164: pwbc.append(password.getBytes(), 0, password
1165: .length());
1166: } catch (IOException e) {
1167: // Should never happen
1168: containerLog
1169: .error(
1170: "Could not append password bytes to chunk: ",
1171: e);
1172: }
1173:
1174: CharChunk decoded = new CharChunk();
1175: Base64.decode(pwbc, decoded);
1176: char[] pwarray = decoded.getBuffer();
1177:
1178: // Split decoded password into hash and salt.
1179: final int saltpos = 20;
1180: byte[] hash = new byte[saltpos];
1181: for (int i = 0; i < hash.length; i++) {
1182: hash[i] = (byte) pwarray[i];
1183: }
1184:
1185: byte[] salt = new byte[pwarray.length - saltpos];
1186: for (int i = 0; i < salt.length; i++)
1187: salt[i] = (byte) pwarray[i + saltpos];
1188:
1189: md.update(salt);
1190: byte[] dp = md.digest();
1191:
1192: validated = Arrays.equals(dp, hash);
1193: } // End synchronized(this) block
1194: } else {
1195: // Hex hashes should be compared case-insensitive
1196: validated = (digest(credentials)
1197: .equalsIgnoreCase(password));
1198: }
1199: } else
1200: validated = (digest(credentials).equals(password));
1201: return (validated);
1202:
1203: }
1204:
1205: /**
1206: * Check credentials by binding to the directory as the user
1207: *
1208: * @param context The directory context
1209: * @param user The User to be authenticated
1210: * @param credentials Authentication credentials
1211: *
1212: * @exception NamingException if a directory server error occurs
1213: */
1214: protected boolean bindAsUser(DirContext context, User user,
1215: String credentials) throws NamingException {
1216:
1217: if (credentials == null || user == null)
1218: return (false);
1219:
1220: String dn = user.dn;
1221: if (dn == null)
1222: return (false);
1223:
1224: // Validate the credentials specified by the user
1225: if (containerLog.isTraceEnabled()) {
1226: containerLog
1227: .trace(" validating credentials by binding as the user");
1228: }
1229:
1230: // Set up security environment to bind as the user
1231: context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
1232: context.addToEnvironment(Context.SECURITY_CREDENTIALS,
1233: credentials);
1234:
1235: // Elicit an LDAP bind operation
1236: boolean validated = false;
1237: try {
1238: if (containerLog.isTraceEnabled()) {
1239: containerLog.trace(" binding as " + dn);
1240: }
1241: context.getAttributes("", null);
1242: validated = true;
1243: } catch (AuthenticationException e) {
1244: if (containerLog.isTraceEnabled()) {
1245: containerLog.trace(" bind attempt failed");
1246: }
1247: }
1248:
1249: // Restore the original security environment
1250: if (connectionName != null) {
1251: context.addToEnvironment(Context.SECURITY_PRINCIPAL,
1252: connectionName);
1253: } else {
1254: context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
1255: }
1256:
1257: if (connectionPassword != null) {
1258: context.addToEnvironment(Context.SECURITY_CREDENTIALS,
1259: connectionPassword);
1260: } else {
1261: context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
1262: }
1263:
1264: return (validated);
1265: }
1266:
1267: /**
1268: * Return a List of roles associated with the given User. Any
1269: * roles present in the user's directory entry are supplemented by
1270: * a directory search. If no roles are associated with this user,
1271: * a zero-length List is returned.
1272: *
1273: * @param context The directory context we are searching
1274: * @param user The User to be checked
1275: *
1276: * @exception NamingException if a directory server error occurs
1277: */
1278: protected List<String> getRoles(DirContext context, User user)
1279: throws NamingException {
1280:
1281: if (user == null)
1282: return (null);
1283:
1284: String dn = user.dn;
1285: String username = user.username;
1286:
1287: if (dn == null || username == null)
1288: return (null);
1289:
1290: if (containerLog.isTraceEnabled())
1291: containerLog.trace(" getRoles(" + dn + ")");
1292:
1293: // Start with roles retrieved from the user entry
1294: ArrayList<String> list = user.roles;
1295: if (list == null) {
1296: list = new ArrayList<String>();
1297: }
1298:
1299: // Are we configured to do role searches?
1300: if ((roleFormat == null) || (roleName == null))
1301: return (list);
1302:
1303: // Set up parameters for an appropriate search
1304: String filter = roleFormat.format(new String[] {
1305: doRFC2254Encoding(dn), username });
1306: SearchControls controls = new SearchControls();
1307: if (roleSubtree)
1308: controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1309: else
1310: controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1311: controls.setReturningAttributes(new String[] { roleName });
1312:
1313: // Perform the configured search and process the results
1314: NamingEnumeration results = context.search(roleBase, filter,
1315: controls);
1316: if (results == null)
1317: return (list); // Should never happen, but just in case ...
1318: while (results.hasMore()) {
1319: SearchResult result = (SearchResult) results.next();
1320: Attributes attrs = result.getAttributes();
1321: if (attrs == null)
1322: continue;
1323: list = addAttributeValues(roleName, attrs, list);
1324: }
1325:
1326: if (containerLog.isTraceEnabled()) {
1327: if (list != null) {
1328: containerLog.trace(" Returning " + list.size()
1329: + " roles");
1330: for (int i = 0; i < list.size(); i++)
1331: containerLog.trace(" Found role " + list.get(i));
1332: } else {
1333: containerLog.trace(" getRoles about to return null ");
1334: }
1335: }
1336:
1337: return (list);
1338: }
1339:
1340: /**
1341: * Return a String representing the value of the specified attribute.
1342: *
1343: * @param attrId Attribute name
1344: * @param attrs Attributes containing the required value
1345: *
1346: * @exception NamingException if a directory server error occurs
1347: */
1348: private String getAttributeValue(String attrId, Attributes attrs)
1349: throws NamingException {
1350:
1351: if (containerLog.isTraceEnabled())
1352: containerLog.trace(" retrieving attribute " + attrId);
1353:
1354: if (attrId == null || attrs == null)
1355: return null;
1356:
1357: Attribute attr = attrs.get(attrId);
1358: if (attr == null)
1359: return (null);
1360: Object value = attr.get();
1361: if (value == null)
1362: return (null);
1363: String valueString = null;
1364: if (value instanceof byte[])
1365: valueString = new String((byte[]) value);
1366: else
1367: valueString = value.toString();
1368:
1369: return valueString;
1370: }
1371:
1372: /**
1373: * Add values of a specified attribute to a list
1374: *
1375: * @param attrId Attribute name
1376: * @param attrs Attributes containing the new values
1377: * @param values ArrayList containing values found so far
1378: *
1379: * @exception NamingException if a directory server error occurs
1380: */
1381: private ArrayList<String> addAttributeValues(String attrId,
1382: Attributes attrs, ArrayList<String> values)
1383: throws NamingException {
1384:
1385: if (containerLog.isTraceEnabled())
1386: containerLog.trace(" retrieving values for attribute "
1387: + attrId);
1388: if (attrId == null || attrs == null)
1389: return values;
1390: if (values == null)
1391: values = new ArrayList<String>();
1392: Attribute attr = attrs.get(attrId);
1393: if (attr == null)
1394: return (values);
1395: NamingEnumeration e = attr.getAll();
1396: while (e.hasMore()) {
1397: String value = (String) e.next();
1398: values.add(value);
1399: }
1400: return values;
1401: }
1402:
1403: /**
1404: * Close any open connection to the directory server for this Realm.
1405: *
1406: * @param context The directory context to be closed
1407: */
1408: protected void close(DirContext context) {
1409:
1410: // Do nothing if there is no opened connection
1411: if (context == null)
1412: return;
1413:
1414: // Close our opened connection
1415: try {
1416: if (containerLog.isDebugEnabled())
1417: containerLog.debug("Closing directory context");
1418: context.close();
1419: } catch (NamingException e) {
1420: containerLog.error(sm.getString("jndiRealm.close"), e);
1421: }
1422: this .context = null;
1423:
1424: }
1425:
1426: /**
1427: * Return a short name for this Realm implementation.
1428: */
1429: protected String getName() {
1430:
1431: return (name);
1432:
1433: }
1434:
1435: /**
1436: * Return the password associated with the given principal's user name.
1437: */
1438: protected String getPassword(String username) {
1439:
1440: return (null);
1441:
1442: }
1443:
1444: /**
1445: * Return the Principal associated with the given user name.
1446: */
1447: protected Principal getPrincipal(String username) {
1448:
1449: DirContext context = null;
1450: Principal principal = null;
1451:
1452: try {
1453:
1454: // Ensure that we have a directory context available
1455: context = open();
1456:
1457: // Occassionally the directory context will timeout. Try one more
1458: // time before giving up.
1459: try {
1460:
1461: // Authenticate the specified username if possible
1462: principal = getPrincipal(context, username);
1463:
1464: } catch (CommunicationException e) {
1465:
1466: // log the exception so we know it's there.
1467: containerLog.warn(sm.getString("jndiRealm.exception"),
1468: e);
1469:
1470: // close the connection so we know it will be reopened.
1471: if (context != null)
1472: close(context);
1473:
1474: // open a new directory context.
1475: context = open();
1476:
1477: // Try the authentication again.
1478: principal = getPrincipal(context, username);
1479:
1480: }
1481:
1482: // Release this context
1483: release(context);
1484:
1485: // Return the authenticated Principal (if any)
1486: return (principal);
1487:
1488: } catch (NamingException e) {
1489:
1490: // Log the problem for posterity
1491: containerLog.error(sm.getString("jndiRealm.exception"), e);
1492:
1493: // Close the connection so that it gets reopened next time
1494: if (context != null)
1495: close(context);
1496:
1497: // Return "not authenticated" for this request
1498: return (null);
1499:
1500: }
1501:
1502: }
1503:
1504: /**
1505: * Return the Principal associated with the given user name.
1506: */
1507: protected synchronized Principal getPrincipal(DirContext context,
1508: String username) throws NamingException {
1509:
1510: User user = getUser(context, username);
1511:
1512: return new GenericPrincipal(this , user.username, user.password,
1513: getRoles(context, user));
1514: }
1515:
1516: /**
1517: * Open (if necessary) and return a connection to the configured
1518: * directory server for this Realm.
1519: *
1520: * @exception NamingException if a directory server error occurs
1521: */
1522: protected DirContext open() throws NamingException {
1523:
1524: // Do nothing if there is a directory server connection already open
1525: if (context != null)
1526: return (context);
1527:
1528: try {
1529:
1530: // Ensure that we have a directory context available
1531: context = new InitialDirContext(
1532: getDirectoryContextEnvironment());
1533:
1534: } catch (Exception e) {
1535:
1536: connectionAttempt = 1;
1537:
1538: // log the first exception.
1539: containerLog.warn(sm.getString("jndiRealm.exception"), e);
1540:
1541: // Try connecting to the alternate url.
1542: context = new InitialDirContext(
1543: getDirectoryContextEnvironment());
1544:
1545: } finally {
1546:
1547: // reset it in case the connection times out.
1548: // the primary may come back.
1549: connectionAttempt = 0;
1550:
1551: }
1552:
1553: return (context);
1554:
1555: }
1556:
1557: /**
1558: * Create our directory context configuration.
1559: *
1560: * @return java.util.Hashtable the configuration for the directory context.
1561: */
1562: protected Hashtable getDirectoryContextEnvironment() {
1563:
1564: Hashtable<String, String> env = new Hashtable<String, String>();
1565:
1566: // Configure our directory context environment.
1567: if (containerLog.isDebugEnabled() && connectionAttempt == 0)
1568: containerLog.debug("Connecting to URL " + connectionURL);
1569: else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
1570: containerLog.debug("Connecting to URL " + alternateURL);
1571: env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
1572: if (connectionName != null)
1573: env.put(Context.SECURITY_PRINCIPAL, connectionName);
1574: if (connectionPassword != null)
1575: env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
1576: if (connectionURL != null && connectionAttempt == 0)
1577: env.put(Context.PROVIDER_URL, connectionURL);
1578: else if (alternateURL != null && connectionAttempt > 0)
1579: env.put(Context.PROVIDER_URL, alternateURL);
1580: if (authentication != null)
1581: env.put(Context.SECURITY_AUTHENTICATION, authentication);
1582: if (protocol != null)
1583: env.put(Context.SECURITY_PROTOCOL, protocol);
1584: if (referrals != null)
1585: env.put(Context.REFERRAL, referrals);
1586: if (derefAliases != null)
1587: env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
1588:
1589: return env;
1590:
1591: }
1592:
1593: /**
1594: * Release our use of this connection so that it can be recycled.
1595: *
1596: * @param context The directory context to release
1597: */
1598: protected void release(DirContext context) {
1599:
1600: ; // NO-OP since we are not pooling anything
1601:
1602: }
1603:
1604: // ------------------------------------------------------ Lifecycle Methods
1605:
1606: /**
1607: * Prepare for active use of the public methods of this Component.
1608: *
1609: * @exception LifecycleException if this component detects a fatal error
1610: * that prevents it from being started
1611: */
1612: public void start() throws LifecycleException {
1613:
1614: // Perform normal superclass initialization
1615: super .start();
1616:
1617: // Validate that we can open our connection
1618: try {
1619: open();
1620: } catch (NamingException e) {
1621: throw new LifecycleException(
1622: sm.getString("jndiRealm.open"), e);
1623: }
1624:
1625: }
1626:
1627: /**
1628: * Gracefully shut down active use of the public methods of this Component.
1629: *
1630: * @exception LifecycleException if this component detects a fatal error
1631: * that needs to be reported
1632: */
1633: public void stop() throws LifecycleException {
1634:
1635: // Perform normal superclass finalization
1636: super .stop();
1637:
1638: // Close any open directory server connection
1639: close(this .context);
1640:
1641: }
1642:
1643: /**
1644: * Given a string containing LDAP patterns for user locations (separated by
1645: * parentheses in a pseudo-LDAP search string format -
1646: * "(location1)(location2)", returns an array of those paths. Real LDAP
1647: * search strings are supported as well (though only the "|" "OR" type).
1648: *
1649: * @param userPatternString - a string LDAP search paths surrounded by
1650: * parentheses
1651: */
1652: protected String[] parseUserPatternString(String userPatternString) {
1653:
1654: if (userPatternString != null) {
1655: ArrayList<String> pathList = new ArrayList<String>();
1656: int startParenLoc = userPatternString.indexOf('(');
1657: if (startParenLoc == -1) {
1658: // no parens here; return whole thing
1659: return new String[] { userPatternString };
1660: }
1661: int startingPoint = 0;
1662: while (startParenLoc > -1) {
1663: int endParenLoc = 0;
1664: // weed out escaped open parens and parens enclosing the
1665: // whole statement (in the case of valid LDAP search
1666: // strings: (|(something)(somethingelse))
1667: while ((userPatternString.charAt(startParenLoc + 1) == '|')
1668: || (startParenLoc != 0 && userPatternString
1669: .charAt(startParenLoc - 1) == '\\')) {
1670: startParenLoc = userPatternString.indexOf("(",
1671: startParenLoc + 1);
1672: }
1673: endParenLoc = userPatternString.indexOf(")",
1674: startParenLoc + 1);
1675: // weed out escaped end-parens
1676: while (userPatternString.charAt(endParenLoc - 1) == '\\') {
1677: endParenLoc = userPatternString.indexOf(")",
1678: endParenLoc + 1);
1679: }
1680: String nextPathPart = userPatternString.substring(
1681: startParenLoc + 1, endParenLoc);
1682: pathList.add(nextPathPart);
1683: startingPoint = endParenLoc + 1;
1684: startParenLoc = userPatternString.indexOf('(',
1685: startingPoint);
1686: }
1687: return (String[]) pathList.toArray(new String[] {});
1688: }
1689: return null;
1690:
1691: }
1692:
1693: /**
1694: * Given an LDAP search string, returns the string with certain characters
1695: * escaped according to RFC 2254 guidelines.
1696: * The character mapping is as follows:
1697: * char -> Replacement
1698: * ---------------------------
1699: * * -> \2a
1700: * ( -> \28
1701: * ) -> \29
1702: * \ -> \5c
1703: * \0 -> \00
1704: * @param inString string to escape according to RFC 2254 guidelines
1705: * @return String the escaped/encoded result
1706: */
1707: protected String doRFC2254Encoding(String inString) {
1708: StringBuffer buf = new StringBuffer(inString.length());
1709: for (int i = 0; i < inString.length(); i++) {
1710: char c = inString.charAt(i);
1711: switch (c) {
1712: case '\\':
1713: buf.append("\\5c");
1714: break;
1715: case '*':
1716: buf.append("\\2a");
1717: break;
1718: case '(':
1719: buf.append("\\28");
1720: break;
1721: case ')':
1722: buf.append("\\29");
1723: break;
1724: case '\0':
1725: buf.append("\\00");
1726: break;
1727: default:
1728: buf.append(c);
1729: break;
1730: }
1731: }
1732: return buf.toString();
1733: }
1734:
1735: }
1736:
1737: // ------------------------------------------------------ Private Classes
1738:
1739: /**
1740: * A private class representing a User
1741: */
1742: class User {
1743: String username = null;
1744: String dn = null;
1745: String password = null;
1746: ArrayList<String> roles = null;
1747:
1748: User(String username, String dn, String password,
1749: ArrayList<String> roles) {
1750: this.username = username;
1751: this.dn = dn;
1752: this.password = password;
1753: this.roles = roles;
1754: }
1755:
1756: }
|