Here is a patch for the JNDI  realm in Catalina that supports authentication
by binding to the directory with the credentials specified by the user.

I've added a configuration parameter "bindAsUser" which defaults to "true".
If set to "false" the realm authenticates as before: ie it retrieves the
password from the directory and compares it explicitly with the presented
credentials.

For the moment I've kept JNDIRealm as a single implementation class, but it
would be trivial to split it into different classes for the two modes of
authentication, if that is thought better. I've not updated the initial
javadoc comment because this will depend on whether the class is split or
not.

Note that digest authentication is not supported in the default "bind" mode.
Torgeir has suggested that this might be possible - if so that would be
good.

As said before I'd like to add the ability to search the directory for the
user's dn to cover cases when a fixed pattern will not work, but will wait
to see the fate of this patch before going ahead.

Cheers, John

Index: JNDIRealm.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/realm/JNDIRealm.java,v
retrieving revision 1.1
diff -c -r1.1 JNDIRealm.java
*** JNDIRealm.java      2001/04/13 21:18:42     1.1
--- JNDIRealm.java      2001/05/15 21:17:46
***************
*** 68,73 ****
--- 68,74 ----
  import javax.naming.NameNotFoundException;
  import javax.naming.NamingEnumeration;
  import javax.naming.NamingException;
+ import javax.naming.AuthenticationException;
  import javax.naming.directory.Attribute;
  import javax.naming.directory.Attributes;
  import javax.naming.directory.DirContext;
***************
*** 238,243 ****
--- 239,250 ----
  
  
      /**
+      * Should we authenticate by binding to the directory as the user?
+      */
+     protected boolean bindAsUser = true;
+ 
+ 
+     /**
       * The attribute name used to retrieve the user password.
       */
      protected String userPassword[] = null;
***************
*** 253,258 ****
--- 260,266 ----
      // ------------------------------------------------------------- Properties
  
  
+ 
      /**
       * Return the connection username for this Realm.
       */
***************
*** 342,347 ****
--- 350,377 ----
  
  
      /**
+      * Return the "bind as user" flag.
+      */
+     public boolean getBindAsUser() {
+ 
+         return (this.bindAsUser);
+ 
+     }
+ 
+ 
+     /**
+      * Set the "bind as user" flag.
+      *
+      * @param bindAsUser The new search flag
+      */
+     public void setBindAsUser(boolean bindAsUser) {
+ 
+         this.bindAsUser = bindAsUser;
+ 
+     }
+ 
+ 
+     /**
       * Return the base element for role searches.
       */
      public String getRoleBase() {
***************
*** 581,586 ****
--- 611,766 ----
  
  
      /**
+      * Return the distinguished name of an authenticated user (if successful)
+      * or <code>null</code> if authentication is unsuccessful.
+      *
+      * @param context The directory context we are accessing
+      * @param username Username to be authenticated
+      * @param credentials Authentication credentials
+      *
+      * @exception NamingException if a directory server error occurs
+      */
+     protected String getUserDN(DirContext context,
+                                String username, String credentials)
+         throws NamingException {
+ 
+         if (debug >= 2)
+             log("getUserDN(" + username + ")");
+ 
+         if (username == null)
+             return (null);
+ 
+         if ((userFormat == null) || (userPassword == null))
+             return (null);
+ 
+         // substitute username into the pattern to get the dn
+         String dn = userFormat.format(new String[] { username });
+         if (debug >= 3)
+             log("  dn=" + dn);
+ 
+       boolean isAuthenticated = false;
+         if (bindAsUser)
+           isAuthenticated = bindAsUser(context, dn, credentials);
+       else
+           isAuthenticated = compareCredentials(context, dn, credentials);
+ 
+         if (isAuthenticated) {
+             if (debug >= 2)
+                 log(sm.getString("jndiRealm.authenticateSuccess",
+                                  username));
+         } else {
+             if (debug >= 2)
+                 log(sm.getString("jndiRealm.authenticateFailure",
+                                  username));
+             return (null);
+         }
+ 
+       return (dn);
+     }
+ 
+ 
+     /**
+      * Can we bind to the directory as the user?
+      *
+      * @param context The directory context we are accessing
+      * @param dn DN to bind as
+      * @param credentials Authentication credentials
+      *
+      * @exception NamingException if a directory server error occurs
+      */
+     protected boolean bindAsUser(DirContext context,
+                                String dn, String credentials)
+         throws NamingException {
+ 
+         // Validate the credentials specified by the user
+         if (debug >= 3) {
+             log("  validating credentials by binding as the user");
+       }
+ 
+       // set up environment to bind as the user
+       context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
+       context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
+ 
+       // try a lookup operation to force the directory bind
+       boolean isAuthenticated = false;
+       try {
+           if (debug > 2) {
+               log("binding as " + dn);
+           }
+           context.lookup("");
+           isAuthenticated = true;
+       }
+       catch (AuthenticationException e) {
+           if (debug > 2) {
+               log("bind attempt failed");
+           }
+       }
+ 
+       // restore the original environment
+       if (connectionName != null && connectionPassword != null) {
+           context.addToEnvironment(Context.SECURITY_PRINCIPAL,
+                                    connectionName);
+           context.addToEnvironment(Context.SECURITY_CREDENTIALS,
+                                    connectionPassword);
+       }
+       else {
+           context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
+           context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
+       }
+ 
+       return (isAuthenticated);
+     }
+         
+ 
+     /**
+      * Do the credentials presented match those in the directory?
+      * The user's password is retrieved from the directory and compared
+      * with the value presented
+      *
+      * @param context The directory context we are accessing
+      * @param dn DN to be authenticated
+      * @param credentials Authentication credentials
+      *
+      * @exception NamingException if a directory server error occurs
+      */
+     protected boolean compareCredentials(DirContext context,
+                                String dn, String credentials)
+         throws NamingException {
+ 
+         Attributes attrs = null;
+         try {
+             attrs = context.getAttributes(dn, userPassword);
+         } catch (NameNotFoundException e) {
+             return (false);
+         }
+         if (attrs == null)
+             return (false);
+         if (debug >= 3)
+             log("  retrieving attribute " + userPassword[0]);
+         Attribute attr = attrs.get(userPassword[0]);
+         if (attr == null)
+             return (false);
+         if (debug >= 3)
+             log("  retrieving value");
+         Object value = attr.get();
+         if (value == null)
+             return (false);
+         String valueString = null;
+         if (value instanceof byte[])
+             valueString = new String((byte[]) value);
+         else
+             valueString = value.toString();
+ 
+         // Validate the credentials specified by the user
+         if (debug >= 3) {
+             log("  validating credentials by explicit comparison");
+       }
+         
+       return (digest(credentials).equals(valueString));
+     }
+ 
+ 
+     /**
       * Close any open connection to the directory server for this Realm.
       *
       * @param context The directory context to be closed
***************
*** 694,766 ****
          if (debug >= 2)
              log("  Returning " + list.size() + " roles");
          return (list);
- 
-     }
- 
- 
-     /**
-      * Return the distinguished name of an authenticated user (if successful)
-      * or <code>null</code> if authentication is unsuccessful.
-      *
-      * @param context The directory context we are accessing
-      * @param username Username to be authenticated
-      * @param credentials Authentication credentials
-      *
-      * @exception NamingException if a directory server error occurs
-      */
-     protected String getUserDN(DirContext context,
-                                String username, String credentials)
-         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]);
-         if (attr == null)
-             return (null);
-         if (debug >= 3)
-             log("  retrieving value");
-         Object value = attr.get();
-         if (value == null)
-             return (null);
-         String valueString = null;
-         if (value instanceof byte[])
-             valueString = new String((byte[]) value);
-         else
-             valueString = value.toString();
- 
-         // Validate the credentials specified by the user
-         if (debug >= 3)
-             log("  validating credentials");
-         if (digest(credentials).equals(valueString)) {
-             if (debug >= 2)
-                 log(sm.getString("jndiRealm.authenticateSuccess",
-                                  username));
-         } else {
-             if (debug >= 2)
-                 log(sm.getString("jndiRealm.authenticateFailure",
-                                  username));
-             return (null);
-         }
-         return (dn);
  
      }
  
--- 874,879 ----

Reply via email to