funkman 2003/12/12 13:43:26 Modified: catalina/src/share/org/apache/catalina/realm JNDIRealm.java Log: Resync with 4.1 fixes, in particular - they include: BZ 23190 16541 And Allow Multiple user patterns per http://marc.theaimsgroup.com/?l=tomcat-dev&m=106254937722504&w=2 Revision Changes Path 1.8 +185 -37 jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java Index: JNDIRealm.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- JNDIRealm.java 2 Sep 2003 21:22:05 -0000 1.7 +++ JNDIRealm.java 12 Dec 2003 21:43:26 -0000 1.8 @@ -61,6 +61,8 @@ */ + + package org.apache.catalina.realm; @@ -70,24 +72,25 @@ import java.util.Hashtable; import java.util.List; -import javax.naming.AuthenticationException; -import javax.naming.CommunicationException; import javax.naming.Context; -import javax.naming.Name; +import javax.naming.CommunicationException; +import javax.naming.InvalidNameException; import javax.naming.NameNotFoundException; -import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; +import javax.naming.NameParser; +import javax.naming.Name; +import javax.naming.AuthenticationException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; - import org.apache.catalina.LifecycleException; import org.apache.catalina.util.Base64; + /** * <p>Implementation of <strong>Realm</strong> that works with a directory * server accessed via the Java Naming and Directory Interface (JNDI) APIs. @@ -298,6 +301,17 @@ /** + * A string of LDAP user patterns or paths, ":"-separated + * These will be used to form the distinguished name of a + * user, with "{0}" marking the spot where the specified username + * goes. + * This is similar to userPattern, but allows for multiple searches + * for a user. + */ + protected String[] userPatternArray = null; + + + /** * The message format used to form the distinguished name of a * user, with "{0}" marking the spot where the specified username * goes. @@ -306,10 +320,10 @@ /** - * The MessageFormat object associated with the current - * <code>userPattern</code>. + * An array of MessageFormat objects associated with the current + * <code>userPatternArray</code>. */ - protected MessageFormat userPatternFormat = null; + protected MessageFormat[] userPatternFormatArray = null; /** @@ -361,6 +375,11 @@ */ protected int connectionAttempt = 0; + /** + * The current user pattern to be used for lookup and binding of a user. + */ + protected int curUserPattern = 0; + // ------------------------------------------------------------- Properties /** @@ -726,6 +745,11 @@ /** * Set the message format pattern for selecting users in this Realm. + * This may be one simple pattern, or multiple patterns to be tried, + * separated by parentheses. (for example, either "cn={0}", or + * "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported, + * but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is + * also valid. Complex search strings with &, etc are NOT supported. * * @param userPattern The new user pattern */ @@ -733,12 +757,19 @@ this.userPattern = userPattern; if (userPattern == null) - userPatternFormat = null; - else - userPatternFormat = new MessageFormat(userPattern); - + userPatternArray = null; + else { + userPatternArray = parseUserPatternString(userPattern); + int len = this.userPatternArray.length; + userPatternFormatArray = new MessageFormat[len]; + for (int i=0; i < len; i++) { + userPatternFormatArray[i] = + new MessageFormat(userPatternArray[i]); + } + } } + /** * Getter for property alternateURL. * @@ -750,6 +781,7 @@ } + /** * Setter for property alternateURL. * @@ -870,21 +902,50 @@ || credentials == null || credentials.equals("")) return (null); - // Retrieve user information - User user = getUser(context, username); - if (user == null) - return (null); - - // Check the user's credentials - if (!checkCredentials(context, user, credentials)) - return (null); - - // Search for additional roles - List roles = getRoles(context, user); + if (userPatternArray != null) { + for (curUserPattern = 0; + curUserPattern < userPatternFormatArray.length; + curUserPattern++) { + // Retrieve user information + User user = getUser(context, username); + if (user != null) { + try { + // Check the user's credentials + if (checkCredentials(context, user, credentials)) { + // Search for additional roles + List roles = getRoles(context, user); + return (new GenericPrincipal(this, + username, + credentials, + roles)); + } + } catch (InvalidNameException ine) { + // Log the problem for posterity + log(sm.getString("jndiRealm.exception"), ine); + // ignore; this is probably due to a name not fitting + // the search path format exactly, as in a fully- + // qualified name being munged into a search path + // that already contains cn= or vice-versa + } + } + } + return null; + } else { + // Retrieve user information + User user = getUser(context, username); + if (user == null) + return (null); + + // Check the user's credentials + if (!checkCredentials(context, user, credentials)) + return (null); - // Create and return a suitable Principal for this user - return (new GenericPrincipal(this, username, credentials, roles)); + // Search for additional roles + List roles = getRoles(context, user); + // Create and return a suitable Principal for this user + return (new GenericPrincipal(this, username, credentials, roles)); + } } @@ -919,7 +980,7 @@ list.toArray(attrIds); // Use pattern or search for user entry - if (userPatternFormat != null) { + if (userPatternFormatArray != null) { user = getUserByPattern(context, username, attrIds); } else { user = getUserBySearch(context, username, attrIds); @@ -950,11 +1011,11 @@ if (debug >= 2) log("lookupUser(" + username + ")"); - if (username == null || userPatternFormat == null) + if (username == null || userPatternFormatArray[curUserPattern] == null) return (null); // Form the dn from the user pattern - String dn = userPatternFormat.format(new String[] { username }); + String dn = userPatternFormatArray[curUserPattern].format(new String[] { username }); if (debug >= 3) { log(" dn=" + dn); } @@ -1160,7 +1221,8 @@ password = password.substring(5); md.reset(); md.update(credentials.getBytes()); - String digestedPassword = new String(Base64.encode(md.digest())); + String digestedPassword = + new String(Base64.encode(md.digest())); validated = password.equals(digestedPassword); } } else { @@ -1279,6 +1341,7 @@ // Set up parameters for an appropriate search String filter = roleFormat.format(new String[] { dn, username }); + filter = doRFC2254Encoding(filter); SearchControls controls = new SearchControls(); if (roleSubtree) controls.setSearchScope(SearchControls.SUBTREE_SCOPE); @@ -1566,6 +1629,94 @@ } + /** + * Given a string containing LDAP patterns for user locations (separated by + * parentheses in a pseudo-LDAP search string format - + * "(location1)(location2)", returns an array of those paths. Real LDAP + * search strings are supported as well (though only the "|" "OR" type). + * + * @param userPatternString - a string LDAP search paths surrounded by + * parentheses + */ + protected String[] parseUserPatternString(String userPatternString) { + + if (userPatternString != null) { + ArrayList pathList = new ArrayList(); + int startParenLoc = userPatternString.indexOf('('); + if (startParenLoc == -1) { + // no parens here; return whole thing + return new String[] {userPatternString}; + } + int startingPoint = 0; + while (startParenLoc > -1) { + int endParenLoc = 0; + // weed out escaped open parens and parens enclosing the + // whole statement (in the case of valid LDAP search + // strings: (|(something)(somethingelse)) + while ( (userPatternString.charAt(startParenLoc + 1) == '|') || + (startParenLoc != 0 && userPatternString.charAt(startParenLoc - 1) == '\\') ) { + startParenLoc = userPatternString.indexOf("(", startParenLoc+1); + } + endParenLoc = userPatternString.indexOf(")", startParenLoc+1); + // weed out escaped end-parens + while (userPatternString.charAt(endParenLoc - 1) == '\\') { + endParenLoc = userPatternString.indexOf(")", endParenLoc+1); + } + String nextPathPart = userPatternString.substring + (startParenLoc+1, endParenLoc); + pathList.add(nextPathPart); + startingPoint = endParenLoc+1; + startParenLoc = userPatternString.indexOf('(', startingPoint); + } + return (String[])pathList.toArray(new String[] {}); + } + return null; + + } + + + /** + * Given an LDAP search string, returns the string with certain characters + * escaped according to RFC 2254 guidelines. + * The character mapping is as follows: + * char -> Replacement + * --------------------------- + * * -> \2a + * ( -> \28 + * ) -> \29 + * \ -> \5c + * \0 -> \00 + * @param inString string to escape according to RFC 2254 guidelines + * @return + */ + protected String doRFC2254Encoding(String inString) { + StringBuffer buf = new StringBuffer(inString.length()); + for (int i = 0; i < inString.length(); i++) { + char c = inString.charAt(i); + switch (c) { + case '\\': + buf.append("\\5c"); + break; + case '*': + buf.append("\\2a"); + break; + case '(': + buf.append("\\28"); + break; + case ')': + buf.append("\\29"); + break; + case '\0': + buf.append("\\00"); + break; + default: + buf.append(c); + break; + } + } + return buf.toString(); + } + } @@ -1581,10 +1732,7 @@ ArrayList roles = null; - User(String username, - String dn, - String password, - ArrayList roles) { + User(String username, String dn, String password, ArrayList roles) { this.username = username; this.dn = dn; this.password = password;
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]