craigmcc    02/03/15 10:37:42

  Modified:    catalina/src/share/org/apache/catalina/realm JNDIRealm.java
               webapps/tomcat-docs/config realm.xml
  Log:
  Major enhancements to the JNDIRealm implementation.  While remaining
  backwards compatible with the previous implementation, the following new
  functionality is supported:
  
  * The realm can authenticate a user by binding to the directory
    server using the username and password specified by the user,
    instead of retrieving the password attribute and performing the
    comparison locally.  (Remove the "userPassword" property to
    activate this mode, which also eliminates the need for "connectionName"
    and "connectionPassword").
  
  * The realm can search the directory for the user's entry, instead
    of picking a particular one.  (Use "userSearch", "userBase", and
    "userSubtree" for this instead of "userPattern".)
  
  * The realm can combine roles held as the values of an attribute
    in the user's entry with those retrieved by the search for roles.
    (Use "userRoleName" to specify this attribute.)
  
  John, thanks for your patience with me on getting this patch committed!
  Could you also make sure that I got the facts right on my edits to the
  configuration docs?  (A patch to tomcat-docs/realm-howto.xml to explain
  the new options would also be cool.)
  
  Submitted by: John Holman <j.g.holman at qmul.ac.uk>
  
  Revision  Changes    Path
  1.6       +669 -148  
jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java
  
  Index: JNDIRealm.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- JNDIRealm.java    7 Sep 2001 20:45:12 -0000       1.5
  +++ JNDIRealm.java    15 Mar 2002 18:37:42 -0000      1.6
  @@ -1,7 +1,12 @@
   /*
  + * $Header: 
/home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java,v
 1.6 2002/03/15 18:37:42 craigmcc Exp $
  + * $Revision: 1.6 $
  + * $Date: 2002/03/15 18:37:42 $
  + *
  + * ====================================================================
    * The Apache Software License, Version 1.1
    *
  - * Copyright (c) 1999 The Apache Software Foundation.  All rights
  + * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
    * reserved.
    *
    * Redistribution and use in source and binary forms, with or without
  @@ -68,6 +73,9 @@
   import javax.naming.NameNotFoundException;
   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;
  @@ -85,20 +93,42 @@
    * The following constraints are imposed on the data structure in the
    * underlying directory server:</p>
    * <ul>
  + *
    * <li>Each user that can be authenticated is represented by an individual
    *     element in the top level <code>DirContext</code> that is accessed
  - *     via the <code>connectionURL</code> property.  This element has the
  - *     following characteristics:
  + *     via the <code>connectionURL</code> property.</li>
  + *
  + * <li>Each user element has a distinguished name that can be formed by
  + *     substituting the presented username into a pattern configured by the
  + *     <code>userPattern</code> property.</li>
  + *
  + * <li>Alternatively, if the <code>userPattern</code> property is not 
  + *     specified, a unique element can be located by searching the directory
  + *     context. In this case:
    *     <ul>
  - *     <li>The distinguished name (<code>dn</code>) attribute of this element
  - *         contains the username that is being presented for authentication.
  - *         </li>
  - *     <li>The distinguished name can be represented by a pattern passed to
  - *         an instance of <code>MessageFormat</code>, where the string "{0}"
  - *         in the pattern is replaced by the username being presented.</li>
  - *     <li>The element for this user contains an attribute named by the
  - *         <code>userPassword</code> property.  The value of this attribute
  - *         is retrieved for use in authentication.</li>
  + *     <li>The <code>userSearch</code> pattern specifies the search filter
  + *         after substitution of the username.</li>
  + *     <li>The <code>userBase</code> property can be set to the element that
  + *         is the base of the subtree containing users.  If not specified,
  + *         the search base is the top-level context.</li>
  + *     <li>The <code>userSubtree</code> property can be set to
  + *         <code>true</code> if you wish to search the entire subtree of the
  + *         directory context.  The default value of <code>false</code>
  + *         requests a search of only the current level.</li>
  + *    </ul>
  + * </li>
  + * 
  + * <li>The user may be authenticated by binding to the directory with the
  + *      username and password presented. This method is used when the
  + *      <code>userPassword</code> property is not specified.</li>
  + *
  + * <li>The user may be authenticated by retrieving the value of an attribute
  + *     from the directory and comparing it explicitly with the value presented
  + *     by the user. This method is used when the <code>userPassword</code>
  + *     property is specified, in which case:
  + *     <ul>
  + *     <li>The element for this user must contain an attribute named by the
  + *         <code>userPassword</code> property.
    *     <li>The value of the user password attribute is either a cleartext
    *         String, or the result of passing a cleartext String through the
    *         <code>RealmBase.digest()</code> method (using the standard digest
  @@ -108,7 +138,8 @@
    *         <code>RealmBase.digest()</code>) are equal to the retrieved value
    *         for the user password attribute.</li>
    *     </ul></li>
  - * <li>Each group of users that has been assigned a particular role is
  + *
  + * <li>Each group of users that has been assigned a particular role may be
    *     represented by an individual element in the top level
    *     <code>DirContext</code> that is accessed via the
    *     <code>connectionURL</code> property.  This element has the following
  @@ -132,6 +163,11 @@
    *         the <code>roleName</code> property) containing the name of the
    *         role represented by this element.</li>
    *     </ul></li>
  + *
  + * <li>In addition, roles may be represented by the values of an attribute
  + * in the user's element whose name is configured by the
  + * <code>userRoleName</code> property.</li>
  + *
    * <li>Note that the standard <code>&lt;security-role-ref&gt;</code> element in
    *     the web application deployment descriptor allows applications to refer
    *     to roles programmatically by names other than those used in the
  @@ -144,7 +180,7 @@
    *
    * @author John Holman
    * @author Craig R. McClanahan
  - * @version $Revision: 1.5 $ $Date: 2001/09/07 20:45:12 $
  + * @version $Revision: 1.6 $ $Date: 2002/03/15 18:37:42 $
    */
   
   public class JNDIRealm extends RealmBase {
  @@ -199,55 +235,90 @@
   
   
       /**
  -     * The base element for role searches.
  +     * The base element for user searches.
        */
  -    protected String roleBase = "";
  +    protected String userBase = "";
  +
  +
  +    /**
  +     * The message format used to search for a user, with "{0}" marking
  +     * the spot where the username goes.
  +     */
  +    protected String userSearch = null;
   
   
       /**
        * The MessageFormat object associated with the current
  -     * <code>roleSearch</code>.
  +     * <code>userSearch</code>.
        */
  -    protected MessageFormat roleFormat = null;
  +    protected MessageFormat userSearchFormat = null;
   
   
       /**
  -     * The name of the attribute containing the role name.
  +     * Should we search the entire subtree for matching users?
        */
  -    protected String roleName[] = null;
  +    protected boolean userSubtree = false;
   
   
       /**
  -     * The message format used to select roles for a user, with "{0}" marking
  -     * the spot where the distinguished name of the user goes.
  +     * The attribute name used to retrieve the user password.
        */
  -    protected String roleSearch = null;
  +    protected String userPassword = null;
   
   
       /**
  -     * Should we search the entire subtree for matching memberships?
  +     * The message format used to form the distinguished name of a
  +     * user, with "{0}" marking the spot where the specified username
  +     * goes.  
        */
  -    protected boolean roleSubtree = false;
  +    protected String userPattern = null;
   
   
       /**
        * The MessageFormat object associated with the current
        * <code>userPattern</code>.
        */
  -    protected MessageFormat userFormat = null;
  +    protected MessageFormat userPatternFormat = null;
   
   
       /**
  -     * The attribute name used to retrieve the user password.
  +     * The base element for role searches.
  +     */
  +    protected String roleBase = "";
  +
  +
  +    /**
  +     * The MessageFormat object associated with the current
  +     * <code>roleSearch</code>.
        */
  -    protected String userPassword[] = null;
  +    protected MessageFormat roleFormat = null;
   
   
       /**
  -     * The message format used to select a user, with "{0}" marking the
  -     * spot where the specified username goes.
  +     * The name of an attribute in the user's entry containing
  +     * roles for that user
        */
  -    protected String userPattern = null;
  +    protected String userRoleName = null;
  +
  +
  +    /**
  +     * The name of the attribute containing roles held elsewhere
  +     */
  +    protected String roleName = null;
  +
  +
  +    /**
  +     * The message format used to select roles for a user, with "{0}" marking
  +     * the spot where the distinguished name of the user goes.
  +     */
  +    protected String roleSearch = null;
  +
  +
  +    /**
  +     * Should we search the entire subtree for matching memberships?
  +     */
  +    protected boolean roleSubtree = false;
  +
   
   
       // ------------------------------------------------------------- Properties
  @@ -340,6 +411,96 @@
   
       }
   
  +    /**
  +     * Return the base element for user searches.
  +     */
  +    public String getUserBase() {
  +
  +        return (this.userBase);
  +
  +    }
  +
  +
  +    /**
  +     * Set the base element for user searches.
  +     *
  +     * @param userBase The new base element
  +     */
  +    public void setUserBase(String userBase) {
  +
  +        this.userBase = userBase;
  +
  +    }
  +
  +
  +    /**
  +     * Return the message format pattern for selecting users in this Realm.
  +     */
  +    public String getUserSearch() {
  +
  +        return (this.userSearch);
  +
  +    }
  +
  +
  +    /**
  +     * Set the message format pattern for selecting users in this Realm.
  +     *
  +     * @param userSearch The new user search pattern
  +     */
  +    public void setUserSearch(String userSearch) {
  +
  +        this.userSearch = userSearch;
  +        if (userSearch == null)
  +            userSearchFormat = null;
  +        else
  +            userSearchFormat = new MessageFormat(userSearch);
  +
  +    }
  +
  +
  +    /**
  +     * Return the "search subtree for users" flag.
  +     */
  +    public boolean getUserSubtree() {
  +
  +        return (this.userSubtree);
  +
  +    }
  +
  +
  +    /**
  +     * Set the "search subtree for users" flag.
  +     *
  +     * @param userSubtree The new search flag
  +     */
  +    public void setUserSubtree(boolean userSubtree) {
  +
  +        this.userSubtree = userSubtree;
  +
  +    }
  +
  +
  +    /**
  +     * Return the user role name attribute name for this Realm.
  +     */
  +    public String getUserRoleName() {
  +
  +        return userRoleName;
  +    }
  +
  +
  +    /**
  +     * Set the user role name attribute name for this Realm.
  +     *
  +     * @param userRoleName The new userRole name attribute name
  +     */
  +    public void setUserRoleName(String userRoleName) {
  +
  +        this.userRoleName = userRoleName;
  +
  +    }
  +
   
       /**
        * Return the base element for role searches.
  @@ -368,10 +529,7 @@
        */
       public String getRoleName() {
   
  -        if (this.roleName != null)
  -            return (this.roleName[0]);
  -        else
  -            return (null);
  +        return (this.roleName);
   
       }
   
  @@ -383,10 +541,7 @@
        */
       public void setRoleName(String roleName) {
   
  -        if (roleName != null)
  -            this.roleName = new String[] { roleName };
  -        else
  -            this.roleName = null;
  +        this.roleName = roleName;
   
       }
   
  @@ -444,10 +599,7 @@
        */
       public String getUserPassword() {
   
  -        if (this.userPassword != null)
  -            return (this.userPassword[0]);
  -        else
  -            return (null);
  +        return (this.userPassword);
   
       }
   
  @@ -459,10 +611,7 @@
        */
       public void setUserPassword(String userPassword) {
   
  -        if (userPassword != null)
  -            this.userPassword = new String[] { userPassword };
  -        else
  -            this.userPassword = null;
  +        this.userPassword = userPassword;
   
       }
   
  @@ -486,9 +635,9 @@
   
           this.userPattern = userPattern;
           if (userPattern == null)
  -            userFormat = null;
  +            userPatternFormat = null;
           else
  -            userFormat = new MessageFormat(userPattern);
  +            userPatternFormat = new MessageFormat(userPattern);
   
       }
   
  @@ -555,6 +704,7 @@
        * Return the Principal associated with the specified username and
        * credentials, if there is one; otherwise return <code>null</code>.
        *
  +     * @param context The directory context
        * @param username Username of the Principal to look up
        * @param credentials Password or other credentials to use in
        *  authenticating this username
  @@ -566,13 +716,20 @@
                                                  String credentials)
           throws NamingException {
   
  -        // Authenticate the specified username if possible
  -        String dn = getUserDN(context, username, credentials);
  -        if (dn == null)
  +        if (username == null || credentials == null)
               return (null);
   
  -        // Look up the associated roles
  -        List roles = getRoles(context, username, dn);
  +        // 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);
   
           // Create and return a suitable Principal for this user
           return (new GenericPrincipal(this, username, credentials, roles));
  @@ -581,79 +738,376 @@
   
   
       /**
  -     * Close any open connection to the directory server for this Realm.
  +     * Return a User object containing information about the user
  +     * with the specified username, if found in the directory;
  +     * otherwise return <code>null</code>.
  +     *
  +     * If the <code>userPassword</code> configuration attribute is
  +     * specified, the value of that attribute is retrieved from the
  +     * user's directory entry. If the <code>userRoleName</code>
  +     * configuration attribute is specified, all values of that
  +     * attribute are retrieved from the directory entry.
        *
  -     * @param context The directory context to be closed
  +     * @param context The directory context
  +     * @param username Username to be looked up
  +     *
  +     * @exception NamingException if a directory server error occurs
        */
  -    protected void close(DirContext context) {
  +    protected User getUser(DirContext context, String username)
  +        throws NamingException {
  +        
  +        User user = null;
   
  -        // Do nothing if there is no opened connection
  -        if (context == null)
  -            return;
  +        // Get attributes to retrieve from user entry
  +        ArrayList list = new ArrayList();
  +        if (userPassword != null)
  +            list.add(userPassword);
  +        if (userRoleName != null)
  +            list.add(userRoleName);
  +        String[] attrIds = new String[list.size()];
  +        list.toArray(attrIds);
  +
  +        // Use pattern or search for user entry
  +        if (userPatternFormat != null) {
  +            user = getUserByPattern(context, username, attrIds);
  +        } else {
  +            user = getUserBySearch(context, username, attrIds);
  +        }
  +        
  +        return user;
  +    }
   
  -        // Close our opened connection
  +
  +    /**
  +     * Use the <code>UserPattern</code> configuration attribute to
  +     * locate the directory entry for the user with the specified
  +     * username and return a User object; otherwise return
  +     * <code>null</code>.
  +     *
  +     * @param context The directory context
  +     * @param username The username
  +     * @param attrIds String[]containing names of attributes to
  +     * retrieve.
  +     *
  +     * @exception NamingException if a directory server error occurs
  +     */
  +    protected User getUserByPattern(DirContext context,
  +                                              String username,
  +                                              String[] attrIds)
  +        throws NamingException {
  +
  +        if (debug >= 2)
  +            log("lookupUser(" + username + ")");
  +
  +        if (username == null || userPatternFormat == null)
  +            return (null);
  +
  +        // Form the dn from the user pattern
  +        String dn = userPatternFormat.format(new String[] { username });
  +        if (debug >= 3) {
  +            log("  dn=" + dn);
  +        }
  +
  +        // Return if no attributes to retrieve
  +        if (attrIds == null || attrIds.length == 0)
  +            return new User(username, dn, null, null);
  +
  +        // Get required attributes from user entry
  +        Attributes attrs = null;
           try {
  -            if (debug >= 1)
  -                log("Closing directory context");
  -            context.close();
  -        } catch (NamingException e) {
  -            log(sm.getString("jndiRealm.close"), e);
  +            attrs = context.getAttributes(dn, attrIds);
  +        } catch (NameNotFoundException e) {
  +            return (null);
           }
  -        this.context = null;
  +        if (attrs == null)
  +            return (null);
  +        
  +        // Retrieve value of userPassword
  +        String password = null;
  +        if (userPassword != null)
  +            password = getAttributeValue(userPassword, attrs);
   
  +        // Retrieve values of userRoleName attribute
  +        ArrayList roles = null;
  +        if (userRoleName != null)
  +            roles = addAttributeValues(userRoleName, attrs, roles);     
  +        
  +        return new User(username, dn, password, roles);
       }
   
   
       /**
  -     * Return a short name for this Realm implementation.
  +     * Search the directory to return a User object containing
  +     * information about the user with the specified username, if
  +     * found in the directory; otherwise return <code>null</code>.
  +     *
  +     * @param context The directory context
  +     * @param username The username
  +     * @param attrIds String[]containing names of attributes to retrieve.
  +     *
  +     * @exception NamingException if a directory server error occurs
        */
  -    protected String getName() {
  +    protected User getUserBySearch(DirContext context,
  +                                           String username,
  +                                           String[] attrIds)
  +        throws NamingException {
   
  -        return (this.name);
  +        if (username == null || userSearchFormat == null)
  +            return (null);
  +
  +        // Form the search filter
  +        String filter = userSearchFormat.format(new String[] { username });
  +
  +        // Set up the search controls
  +        SearchControls constraints = new SearchControls();
  +
  +        if (userSubtree) {
  +            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
  +        }
  +        else {
  +            constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
  +        }
  +
  +        // Specify the attributes to be retrieved
  +        if (attrIds == null)
  +            attrIds = new String[0];
  +        constraints.setReturningAttributes(attrIds); 
  +        
  +        if (debug > 3) {
  +            log("  Searching for " + username);
  +            log("  base: " + userBase + "  filter: " + filter);
  +        }
  +        
  +        NamingEnumeration results = 
  +            context.search(userBase, filter, constraints);
  +        
  +        
  +        // Fail if no entries found
  +        if (results == null || !results.hasMore()) {
  +            if (debug > 2) {
  +                log("  username not found");
  +            }
  +            return (null);
  +        }
  +        
  +        // Get result for the first entry found
  +        SearchResult result = (SearchResult)results.next();
  +        
  +        // Check no further entries were found
  +        if (results.hasMore()) {
  +            log("username " + username + " has multiple entries");
  +            return (null);
  +        }
  +
  +        // Get the entry's distinguished name
  +        NameParser parser = context.getNameParser("");
  +        Name contextName = parser.parse(context.getNameInNamespace());
  +        Name baseName = parser.parse(userBase);
  +        Name entryName = parser.parse(result.getName());
  +        Name name = contextName.addAll(baseName);
  +        name = name.addAll(entryName);
  +        String dn = name.toString();
  +        
  +        if (debug > 2)
  +            log("  entry found for " + username + " with dn " + dn);
  +
  +        // Get the entry's attributes
  +        Attributes attrs = result.getAttributes();
  +        if (attrs == null)
  +            return null;
  +
  +        // Retrieve value of userPassword
  +        String password = null;
  +        if (userPassword != null)
  +            password = getAttributeValue(userPassword, attrs);
   
  +        // Retrieve values of userRoleName attribute
  +        ArrayList roles = null;
  +        if (userRoleName != null)
  +            roles = addAttributeValues(userRoleName, attrs, roles);     
  +        
  +        return new User(username, dn, password, roles);
       }
   
   
       /**
  -     * Return the password associated with the given principal's user name.
  +     * Check whether the given User can be authenticated with the
  +     * given credentials. If the <code>userPassword</code>
  +     * configuration attribute is specified, the credentials
  +     * previously retrieved from the directory are compared explicitly
  +     * with those presented by the user. Otherwise the presented
  +     * credentials are checked by binding to the directory as the
  +     * user.
  +     *
  +     * @param context The directory context
  +     * @param user The User to be authenticated
  +     * @param credentials The credentials presented by the user
  +     *
  +     * @exception NamingException if a directory server error occurs
        */
  -    protected String getPassword(String username) {
  +    protected boolean checkCredentials(DirContext context,
  +                                     User user,
  +                                     String credentials)
  +         throws NamingException {
  +         
  +         boolean validated = false;
  +
  +         if (userPassword == null) {
  +             validated = bindAsUser(context, user, credentials);
  +         } else {
  +             validated = compareCredentials(context, user, credentials);
  +         }
  +         
  +         if (debug >= 2) {
  +             if (validated) {
  +                 log(sm.getString("jndiRealm.authenticateSuccess",
  +                                  user.username));
  +             } else {
  +                 log(sm.getString("jndiRealm.authenticateFailure",
  +                                  user.username));
  +             }
  +         }
  +         return (validated);
  +     }
   
  -        return (null);
  -
  -    }
   
   
       /**
  -     * Return the Principal associated with the given user name.
  +     * Check whether the credentials presented by the user match those
  +     * retrieved from the directory.
  +     *
  +     * @param context The directory context
  +     * @param user The User to be authenticated
  +     * @param credentials Authentication credentials
  +     *
  +     * @exception NamingException if a directory server error occurs
        */
  -    protected Principal getPrincipal(String username) {
  +    protected boolean compareCredentials(DirContext context,
  +                                         User info,
  +                                         String credentials)
  +        throws NamingException {
   
  -        return (null);
  +        if (info == null || credentials == null)
  +            return (false);
  +
  +        String password = info.password;
  +        if (password == null)
  +            return (false);
  +
  +        // Validate the credentials specified by the user
  +        if (debug >= 3)
  +            log("  validating credentials");
  +
  +        boolean validated = false;
  +        if (hasMessageDigest()) {
  +            // Hex hashes should be compared case-insensitive
  +            validated = (digest(credentials).equalsIgnoreCase(password));
  +        } else
  +            validated = (digest(credentials).equals(password));
  +        return (validated);
   
       }
   
   
  +
  +    /**
  +     * Check credentials by binding to the directory as the user
  +     *
  +     * @param context The directory context
  +     * @param user The User to be authenticated
  +     * @param credentials Authentication credentials
  +     *
  +     * @exception NamingException if a directory server error occurs
  +     */
  +     protected boolean bindAsUser(DirContext context,
  +                                  User user,
  +                                  String credentials)
  +         throws NamingException {
  +         Attributes attr;
  +
  +         if (credentials == null || user == null)
  +             return (false);
  +         
  +         String dn = user.dn;
  +         if (dn == null)
  +             return (false);
  + 
  +         // Validate the credentials specified by the user
  +         if (debug >= 3) {
  +             log("  validating credentials by binding as the user");
  +        }
  + 
  +        // Set up security environment to bind as the user
  +        context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
  +        context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
  + 
  +        // Elicit an LDAP bind operation
  +        boolean validated = false;
  +        try {
  +            if (debug > 2) {
  +                log("  binding as "  + dn);
  +            }
  +            attr = context.getAttributes("", null);
  +            validated = true;
  +        }
  +        catch (AuthenticationException e) {
  +            if (debug > 2) {
  +                log("  bind attempt failed");
  +            }
  +        }
  + 
  +        // Restore the original security environment
  +        if (connectionName != null) {
  +            context.addToEnvironment(Context.SECURITY_PRINCIPAL,                    
                 connectionName);
  +        } else {
  +            context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
  +        }
  +
  +        if (connectionPassword != null) {           
  +            context.addToEnvironment(Context.SECURITY_CREDENTIALS,
  +                                     connectionPassword);
  +        }
  +        else {
  +            context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
  +        }
  + 
  +        return (validated);
  +     }
  +
  +
       /**
  -     * Return a List of roles associated with the user with the specified
  -     * distinguished name.  If no roles are associated with this user, a
  -     * zero-length List is returned.
  +     * Return a List of roles associated with the given User.  Any
  +     * roles present in the user's directory entry are supplemented by
  +     * a directory search. If no roles are associated with this user,
  +     * a zero-length List is returned.
        *
        * @param context The directory context we are searching
  -     * @param username The username of the user to be checked
  -     * @param dn Distinguished name of the user to be checked
  +     * @param user The User to be checked
        *
        * @exception NamingException if a directory server error occurs
        */
  -    protected List getRoles(DirContext context,
  -                            String username, String dn)
  +    protected List getRoles(DirContext context, User user)
           throws NamingException {
   
  +        if (user == null)
  +            return (null);
  +
  +        String dn = user.dn;
  +        String username = user.username;
  +
  +        if (dn == null || username == null)
  +            return (null);
  +
           if (debug >= 2)
  -            log("getRoles(" + dn + ")");
  +            log("  getRoles(" + dn + ")");
  +        
  +        // Start with roles retrieved from the user entry
  +        ArrayList list = user.roles;
  +        if (list == null) {
  +            list = new ArrayList();
  +        }
   
           // Are we configured to do role searches?
  -        ArrayList list = new ArrayList();
           if ((roleFormat == null) || (roleName == null))
               return (list);
   
  @@ -664,12 +1118,12 @@
               controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
           else
               controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
  -        controls.setReturningAttributes(roleName);
  +        controls.setReturningAttributes(new String[] {roleName});
   
           // Perform the configured search and process the results
           if (debug >= 3) {
               log("  Searching role base '" + roleBase + "' for attribute '" +
  -                roleName[0] + "'");
  +                roleName + "'");
               log("  With filter expression '" + filter + "'");
           }
           NamingEnumeration results =
  @@ -680,64 +1134,41 @@
               SearchResult result = (SearchResult) results.next();
               Attributes attrs = result.getAttributes();
               if (attrs == null)
  -                continue;
  -            Attribute attr = attrs.get(roleName[0]);
  -            if (attr != null) {
  -                String role = (String) attr.get();
  -                if (debug >= 3)
  -                    log("  Found role '" + role + "'");
  -                list.add(role);
  -            }
  +                continue;           
  +            list = addAttributeValues(roleName, attrs, list);   
           }
   
  -        // Return the completed list of roles
  -        if (debug >= 2)
  +        // Return the augmented list of roles
  +        if (debug >= 2) {
               log("  Returning " + list.size() + " roles");
  -        return (list);
  +            for (int i=0; i<list.size(); i++)
  +                log(  "  Found role " + list.get(i));
  +        }
   
  +        return (list);
       }
   
   
       /**
  -     * Return the distinguished name of an authenticated user (if successful)
  -     * or <code>null</code> if authentication is unsuccessful.
  +     * Return a String representing the value of the specified attribute.
        *
  -     * @param context The directory context we are accessing
  -     * @param username Username to be authenticated
  -     * @param credentials Authentication credentials
  +     * @param attrId Attribute name
  +     * @param attrs Attributes containing the required value
        *
        * @exception NamingException if a directory server error occurs
        */
  -    protected String getUserDN(DirContext context,
  -                               String username, String credentials)
  +    private String getAttributeValue(String attrId, Attributes attrs)
           throws NamingException {
   
  -        if (debug >= 2)
  -            log("getUserDN(" + username + ")");
  -        if (username == null)
  -            return (null);
  -        if ((userFormat == null) || (userPassword == null))
  -            return (null);
  -
  -        // Retrieve the user password attribute for this user
  -        String dn = userFormat.format(new String[] { username });
           if (debug >= 3)
  -            log("  dn=" + dn);
  -        Attributes attrs = null;
  -        try {
  -            attrs = context.getAttributes(dn, userPassword);
  -        } catch (NameNotFoundException e) {
  -            return (null);
  -        }
  -        if (attrs == null)
  -            return (null);
  -        if (debug >= 3)
  -            log("  retrieving attribute " + userPassword[0]);
  -        Attribute attr = attrs.get(userPassword[0]);
  +            log("  retrieving attribute " + attrId);
  +
  +        if (attrId == null || attrs == null)
  +            return null;
  +
  +        Attribute attr = attrs.get(attrId);
           if (attr == null)
               return (null);
  -        if (debug >= 3)
  -            log("  retrieving value");
           Object value = attr.get();
           if (value == null)
               return (null);
  @@ -746,34 +1177,100 @@
               valueString = new String((byte[]) value);
           else
               valueString = value.toString();
  +        
  +        return valueString;
  +    }
   
  -        // Validate the credentials specified by the user
  -        if (debug >= 3)
  -            log("  validating credentials");
   
  -        boolean validated = false;
  -        if (hasMessageDigest()) {
  -            // Hex hashes should be compared case-insensitive
  -            validated = (digest(credentials).equalsIgnoreCase(valueString));
  -        } else
  -            validated = (digest(credentials).equals(valueString));
   
  -        if (validated) {
  -            if (debug >= 2)
  -                log(sm.getString("jndiRealm.authenticateSuccess",
  -                                 username));
  -        } else {
  -            if (debug >= 2)
  -                log(sm.getString("jndiRealm.authenticateFailure",
  -                                 username));
  +    /**
  +     * Add values of a specified attribute to a list
  +     *
  +     * @param attrId Attribute name
  +     * @param attrs Attributes containing the new values
  +     * @param values ArrayList containing values found so far
  +     *
  +     * @exception NamingException if a directory server error occurs
  +     */
  +    private ArrayList addAttributeValues(String attrId,
  +                                         Attributes attrs,
  +                                         ArrayList values)
  +        throws NamingException{
  +
  +        if (debug >= 3)
  +            log("  retrieving values for attribute " + attrId);
  +        if (attrId == null || attrs == null)
  +            return null;
  +        if (values == null)
  +            values = new ArrayList();
  +        Attribute attr = attrs.get(attrId);
  +        if (attr == null)
               return (null);
  +        NamingEnumeration e = attr.getAll();
  +        while(e.hasMore()) {
  +            String value = (String)e.next();
  +            values.add(value);
  +        }                       
  +        return values;
  +    }
  +
  +
  +    /**
  +     * Close any open connection to the directory server for this Realm.
  +     *
  +     * @param context The directory context to be closed
  +     */
  +    protected void close(DirContext context) {
  +
  +        // Do nothing if there is no opened connection
  +        if (context == null)
  +            return;
  +
  +        // Close our opened connection
  +        try {
  +            if (debug >= 1)
  +                log("Closing directory context");
  +            context.close();
  +        } catch (NamingException e) {
  +            log(sm.getString("jndiRealm.close"), e);
           }
  -        return (dn);
  +        this.context = null;
  +
  +    }
  +
  +
  +    /**
  +     * Return a short name for this Realm implementation.
  +     */
  +    protected String getName() {
  +
  +        return (this.name);
   
       }
   
   
       /**
  +     * Return the password associated with the given principal's user name.
  +     */
  +    protected String getPassword(String username) {
  +
  +        return (null);
  +
  +    }
  +
  +
  +    /**
  +     * Return the Principal associated with the given user name.
  +     */
  +    protected Principal getPrincipal(String username) {
  +
  +        return (null);
  +
  +    }
  +
  +
  +
  +    /**
        * Open (if necessary) and return a connection to the configured
        * directory server for this Realm.
        *
  @@ -859,5 +1356,29 @@
       }
   
   
  +}
  +
  +// ------------------------------------------------------ Private Classes
  +
  +/**
  + * A private class representing a User
  + */
  +class User {
  +    String username = null;
  +    String dn = null;
  +    String password = null;
  +    ArrayList roles = null;
  +    
  +
  +    User(String username, 
  +             String dn,
  +             String password,
  +             ArrayList roles) {
  +        this.username = username;
  +        this.dn = dn;
  +        this.password = password;
  +        this.roles = roles;
  +    }
   
   }
  +
  
  
  
  1.3       +58 -10    jakarta-tomcat-4.0/webapps/tomcat-docs/config/realm.xml
  
  Index: realm.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-tomcat-4.0/webapps/tomcat-docs/config/realm.xml,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- realm.xml 27 Aug 2001 20:22:37 -0000      1.2
  +++ realm.xml 15 Mar 2002 18:37:42 -0000      1.3
  @@ -171,14 +171,16 @@
   
       <attributes>
   
  -      <attribute name="connectionName" required="true">
  +      <attribute name="connectionName" required="false">
           <p>The directory username to use when establishing the JNDI
  -        connection.</p>
  +        connection.  This attribute is required if you specify the
  +        <code>userPassword</code> property, and not used otherwise.</p>
         </attribute>
   
  -      <attribute name="connectionPassword" required="true">
  +      <attribute name="connectionPassword" required="false">
           <p>The directory password to use when establishing the JNDI
  -        connection.</p>
  +        connection.  This attribute is required if you specify the
  +        <code>userPassword</code> property, and not used otherwise.</p>
         </attribute>
   
         <attribute name="connectionURL" required="true">
  @@ -196,9 +198,12 @@
           <p>The base directory element for performing role searches.</p>
         </attribute>
   
  -      <attribute name="roleName" required="true">
  +      <attribute name="roleName" required="false">
           <p>The name of the directory attribute to retrieve when selecting
  -        the assigned roles for a user.</p>
  +        the assigned roles for a user.  If not specified, use the
  +        <code>userRoleName</code> property to specify the name of an
  +        attribute, in the user's entry, that contains zero or more role
  +        names assigned to this user.</p>
         </attribute>
   
         <attribute name="roleSearch" required="true">
  @@ -213,14 +218,57 @@
           to <code>false</code> to not search subtrees.</p>
         </attribute>
   
  -      <attribute name="userPassword" required="true">
  -        <p>Name of the LDAP element containing the user's password.</p>
  +      <attribute name="userBase" required="false">
  +        <p>The base element for user searches performed using the
  +        <code>userSearch</code> expression.  Not used if you are using
  +        the <code>userPattern</code> expression.</p>
  +      </attribute>
  +
  +      <attribute name="userPassword" required="false">
  +        <p>Name of the LDAP element containing the user's password.  If you
  +        specify this value, JNDIRealm will bind to the directory using
  +        the values specified by <code>connectionName</code> and
  +        <code>connectionPassword</code> properties, and retrieve the
  +        corresponding attribute for comparison to the value specified by
  +        the user being authenticated.  If you do <strong>not</strong>
  +        specify this value, JNDIRealm will attempt to bind to the
  +        directory using the username and password specified by the user,
  +        with a successful bind being interpreted as an authenticated user.</p>
         </attribute>
   
  -      <attribute name="userPattern" required="true">
  +      <attribute name="userPattern" required="false">
           <p>The LDAP search expression to use when retrieving the attributes
           of a particular user, with <code>{0}</code> marking where the
  -        actual username should be inserted.</p>
  +        actual username should be inserted.  Use this property instead of
  +        <code>userSearch</code> if you want to select a particular single
  +        entry based on the username.</p>
  +      </attribute>
  +
  +      <attribute name="userRoleName" required="false">
  +        <p>The name of a directory attribute, in the user's entry, containing
  +        zero or more values for the names of roles assigned to this user.  If
  +        not specified, use the <code>roleName</code> property to specify
  +        the name of a particular attribute that is retrieved from individual
  +        role entries associated with this user.</p>
  +      </attribute>
  +
  +      <attribute name="userSearch" required="false">
  +        <p>The LDAP search expression to use when retrieving the attributes
  +        of a particular user, with <code>{0}</code> marking where the
  +        actual username should be inserted.  Use this property instead of
  +        <code>userPattern</code> if you want to search the entire directory
  +        (under the optional additional control of the <code>userBase</code>
  +        and <code>userSubtree</code> properties) instead of retrieving a
  +        particular named entry.</p>
  +      </attribute>
  +
  +      <attribute name="userSubtree" required="false">
  +        <p>Set to <code>true</code> if you are using the
  +        <code>userSearch</code> pattern to search for authenticated users,
  +        and you want to search subtrees of the element specified by the
  +        <code>userBase</code> element.  The default value of <code>false</code>
  +        causes only the specified level to be searched.  Not used if you are
  +        using the <code>userPattern</code> expression.</p>
         </attribute>
   
       </attributes>
  
  
  

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to