You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by ak...@apache.org on 2006/07/17 21:21:14 UTC

svn commit: r422808 - in /directory/branches/apacheds/optimization: core-unit/src/test/java/org/apache/directory/server/core/authn/ core/src/main/java/org/apache/directory/server/core/authn/

Author: akarasulu
Date: Mon Jul 17 12:21:14 2006
New Revision: 422808

URL: http://svn.apache.org/viewvc?rev=422808&view=rev
Log:
Added credential cache to SimpleAuthenticator to improve Bind request throughput
by 27%.  Also not another commit to follow for changes to LDAP shared.

Modified:
    directory/branches/apacheds/optimization/core-unit/src/test/java/org/apache/directory/server/core/authn/SimpleAuthenticationITest.java
    directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AbstractAuthenticator.java
    directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AuthenticationService.java
    directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/Authenticator.java
    directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/SimpleAuthenticator.java

Modified: directory/branches/apacheds/optimization/core-unit/src/test/java/org/apache/directory/server/core/authn/SimpleAuthenticationITest.java
URL: http://svn.apache.org/viewvc/directory/branches/apacheds/optimization/core-unit/src/test/java/org/apache/directory/server/core/authn/SimpleAuthenticationITest.java?rev=422808&r1=422807&r2=422808&view=diff
==============================================================================
--- directory/branches/apacheds/optimization/core-unit/src/test/java/org/apache/directory/server/core/authn/SimpleAuthenticationITest.java (original)
+++ directory/branches/apacheds/optimization/core-unit/src/test/java/org/apache/directory/server/core/authn/SimpleAuthenticationITest.java Mon Jul 17 12:21:14 2006
@@ -29,11 +29,13 @@
 import javax.naming.directory.Attributes;
 import javax.naming.directory.DirContext;
 import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.ModificationItem;
 import javax.naming.ldap.InitialLdapContext;
 
 import org.apache.directory.server.core.unit.AbstractAdminTestCase;
 import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
+import org.apache.directory.shared.ldap.message.LockableAttributeImpl;
 import org.apache.directory.shared.ldap.util.ArrayUtils;
 
 
@@ -309,4 +311,156 @@
         env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
         assertNotNull( new InitialContext( env ) );
     }
+
+
+    public void test11InvalidateCredentialCache() throws NamingException
+    {
+        Hashtable env = new Hashtable( configuration.toJndiEnvironment() );
+        env.put( Context.PROVIDER_URL, "ou=system" );
+        env.put( Context.SECURITY_PRINCIPAL, "uid=akarasulu,ou=users,ou=system" );
+        env.put( Context.SECURITY_CREDENTIALS, "test" );
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
+        InitialDirContext ic = new InitialDirContext( env );
+        Attributes attrs = ic.getAttributes( "uid=akarasulu,ou=users" );
+        Attribute ou = attrs.get( "ou" );
+        assertTrue( ou.contains( "Engineering" ) );
+        assertTrue( ou.contains( "People" ) );
+
+        Attribute objectClass = attrs.get( "objectClass" );
+        assertTrue( objectClass.contains( "top" ) );
+        assertTrue( objectClass.contains( "person" ) );
+        assertTrue( objectClass.contains( "organizationalPerson" ) );
+        assertTrue( objectClass.contains( "inetOrgPerson" ) );
+
+        assertTrue( attrs.get( "telephonenumber" ).contains( "+1 408 555 4798" ) );
+        assertTrue( attrs.get( "uid" ).contains( "akarasulu" ) );
+        assertTrue( attrs.get( "givenname" ).contains( "Alex" ) );
+        assertTrue( attrs.get( "mail" ).contains( "akarasulu@apache.org" ) );
+        assertTrue( attrs.get( "l" ).contains( "Bogusville" ) );
+        assertTrue( attrs.get( "sn" ).contains( "Karasulu" ) );
+        assertTrue( attrs.get( "cn" ).contains( "Alex Karasulu" ) );
+        assertTrue( attrs.get( "facsimiletelephonenumber" ).contains( "+1 408 555 9751" ) );
+        assertTrue( attrs.get( "roomnumber" ).contains( "4612" ) );
+        
+        // now modify the password for akarasulu
+        LockableAttributeImpl userPasswordAttribute = new LockableAttributeImpl( "userPassword", "newpwd" );
+        ic.modifyAttributes( "uid=akarasulu,ou=users", new ModificationItem[] { 
+            new ModificationItem( DirContext.REPLACE_ATTRIBUTE, userPasswordAttribute ) } );
+        
+        // close and try with old password (should fail)
+        ic.close();
+        env.put( Context.SECURITY_CREDENTIALS, "test" );
+        try
+        {
+            ic = new InitialDirContext( env );
+            fail( "Authentication with old password should fail" );
+        }
+        catch ( NamingException e )
+        {
+            // we should fail 
+        }
+
+        // close and try again now with new password (should fail)
+        ic.close();
+        env.put( Context.SECURITY_CREDENTIALS, "newpwd" );
+        ic = new InitialDirContext( env );
+        attrs = ic.getAttributes( "uid=akarasulu,ou=users" );
+        ou = attrs.get( "ou" );
+        assertTrue( ou.contains( "Engineering" ) );
+        assertTrue( ou.contains( "People" ) );
+
+        objectClass = attrs.get( "objectClass" );
+        assertTrue( objectClass.contains( "top" ) );
+        assertTrue( objectClass.contains( "person" ) );
+        assertTrue( objectClass.contains( "organizationalPerson" ) );
+        assertTrue( objectClass.contains( "inetOrgPerson" ) );
+
+        assertTrue( attrs.get( "telephonenumber" ).contains( "+1 408 555 4798" ) );
+        assertTrue( attrs.get( "uid" ).contains( "akarasulu" ) );
+        assertTrue( attrs.get( "givenname" ).contains( "Alex" ) );
+        assertTrue( attrs.get( "mail" ).contains( "akarasulu@apache.org" ) );
+        assertTrue( attrs.get( "l" ).contains( "Bogusville" ) );
+        assertTrue( attrs.get( "sn" ).contains( "Karasulu" ) );
+        assertTrue( attrs.get( "cn" ).contains( "Alex Karasulu" ) );
+        assertTrue( attrs.get( "facsimiletelephonenumber" ).contains( "+1 408 555 9751" ) );
+        assertTrue( attrs.get( "roomnumber" ).contains( "4612" ) );
+    }
+
+
+    public void test12InvalidateCredentialCacheWithOID() throws NamingException
+    {
+        Hashtable env = new Hashtable( configuration.toJndiEnvironment() );
+        env.put( Context.PROVIDER_URL, "ou=system" );
+        env.put( Context.SECURITY_PRINCIPAL, "uid=akarasulu,ou=users,ou=system" );
+        env.put( Context.SECURITY_CREDENTIALS, "test" );
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.core.jndi.CoreContextFactory" );
+        InitialDirContext ic = new InitialDirContext( env );
+        Attributes attrs = ic.getAttributes( "uid=akarasulu,ou=users" );
+        Attribute ou = attrs.get( "ou" );
+        assertTrue( ou.contains( "Engineering" ) );
+        assertTrue( ou.contains( "People" ) );
+
+        Attribute objectClass = attrs.get( "objectClass" );
+        assertTrue( objectClass.contains( "top" ) );
+        assertTrue( objectClass.contains( "person" ) );
+        assertTrue( objectClass.contains( "organizationalPerson" ) );
+        assertTrue( objectClass.contains( "inetOrgPerson" ) );
+
+        assertTrue( attrs.get( "telephonenumber" ).contains( "+1 408 555 4798" ) );
+        assertTrue( attrs.get( "uid" ).contains( "akarasulu" ) );
+        assertTrue( attrs.get( "givenname" ).contains( "Alex" ) );
+        assertTrue( attrs.get( "mail" ).contains( "akarasulu@apache.org" ) );
+        assertTrue( attrs.get( "l" ).contains( "Bogusville" ) );
+        assertTrue( attrs.get( "sn" ).contains( "Karasulu" ) );
+        assertTrue( attrs.get( "cn" ).contains( "Alex Karasulu" ) );
+        assertTrue( attrs.get( "facsimiletelephonenumber" ).contains( "+1 408 555 9751" ) );
+        assertTrue( attrs.get( "roomnumber" ).contains( "4612" ) );
+        
+        // now modify the password for akarasulu
+        LockableAttributeImpl userPasswordAttribute = new LockableAttributeImpl( "2.5.4.35", "newpwd" );
+        ic.modifyAttributes( "uid=akarasulu,ou=users", new ModificationItem[] { 
+            new ModificationItem( DirContext.REPLACE_ATTRIBUTE, userPasswordAttribute ) } );
+        
+        // close and try with old password (should fail)
+        ic.close();
+        env.put( Context.SECURITY_CREDENTIALS, "test" );
+        try
+        {
+            ic = new InitialDirContext( env );
+            fail( "Authentication with old password should fail" );
+        }
+        catch ( NamingException e )
+        {
+            // we should fail 
+        }
+
+        // close and try again now with new password (should fail)
+        ic.close();
+        env.put( Context.SECURITY_CREDENTIALS, "newpwd" );
+        ic = new InitialDirContext( env );
+        attrs = ic.getAttributes( "uid=akarasulu,ou=users" );
+        ou = attrs.get( "ou" );
+        assertTrue( ou.contains( "Engineering" ) );
+        assertTrue( ou.contains( "People" ) );
+
+        objectClass = attrs.get( "objectClass" );
+        assertTrue( objectClass.contains( "top" ) );
+        assertTrue( objectClass.contains( "person" ) );
+        assertTrue( objectClass.contains( "organizationalPerson" ) );
+        assertTrue( objectClass.contains( "inetOrgPerson" ) );
+
+        assertTrue( attrs.get( "telephonenumber" ).contains( "+1 408 555 4798" ) );
+        assertTrue( attrs.get( "uid" ).contains( "akarasulu" ) );
+        assertTrue( attrs.get( "givenname" ).contains( "Alex" ) );
+        assertTrue( attrs.get( "mail" ).contains( "akarasulu@apache.org" ) );
+        assertTrue( attrs.get( "l" ).contains( "Bogusville" ) );
+        assertTrue( attrs.get( "sn" ).contains( "Karasulu" ) );
+        assertTrue( attrs.get( "cn" ).contains( "Alex Karasulu" ) );
+        assertTrue( attrs.get( "facsimiletelephonenumber" ).contains( "+1 408 555 9751" ) );
+        assertTrue( attrs.get( "roomnumber" ).contains( "4612" ) );
+    }
+
+
 }

Modified: directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AbstractAuthenticator.java
URL: http://svn.apache.org/viewvc/directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AbstractAuthenticator.java?rev=422808&r1=422807&r2=422808&view=diff
==============================================================================
--- directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AbstractAuthenticator.java (original)
+++ directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AbstractAuthenticator.java Mon Jul 17 12:21:14 2006
@@ -129,6 +129,14 @@
 
     public abstract LdapPrincipal authenticate( LdapDN bindDn, ServerContext ctx ) throws NamingException;
 
+    
+    /**
+     * Does nothing leaving it so subclasses can override.
+     */
+    public void passwordChanged( LdapDN bindDn, byte[] userPassword )
+    {
+    }
+    
 
     /**
      * Returns a new {@link LdapPrincipal} instance whose value is the specified

Modified: directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AuthenticationService.java
URL: http://svn.apache.org/viewvc/directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AuthenticationService.java?rev=422808&r1=422807&r2=422808&view=diff
==============================================================================
--- directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AuthenticationService.java (original)
+++ directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/AuthenticationService.java Mon Jul 17 12:21:14 2006
@@ -27,6 +27,7 @@
 import javax.naming.Context;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
 import javax.naming.directory.ModificationItem;
 import javax.naming.directory.SearchControls;
@@ -42,7 +43,9 @@
 import org.apache.directory.server.core.jndi.ServerContext;
 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException;
 import org.apache.directory.shared.ldap.filter.ExprNode;
+import org.apache.directory.shared.ldap.schema.AttributeType;
 import org.apache.directory.shared.ldap.util.AttributeUtils;
+import org.apache.directory.shared.ldap.util.StringTools;
 import org.apache.directory.shared.ldap.name.LdapDN;
 
 import org.slf4j.Logger;
@@ -65,7 +68,7 @@
     public Map authenticators = new HashMap();
 
     private DirectoryServiceConfiguration factoryCfg;
-
+    private AttributeType userPasswordAttributeType;
 
     /**
      * Creates an authentication service interceptor.
@@ -81,6 +84,8 @@
     public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
     {
         this.factoryCfg = factoryCfg;
+        this.userPasswordAttributeType = factoryCfg.getGlobalRegistries()
+            .getAttributeTypeRegistry().lookup( "userPassword" );
 
         // Register all authenticators
         Iterator i = factoryCfg.getStartupConfiguration().getAuthenticatorConfigurations().iterator();
@@ -324,8 +329,39 @@
 
         checkAuthenticated();
         next.modify( name, modOp, mods );
+        
+        Attribute userPasswordAttribute = AttributeUtils.getAttribute( mods, userPasswordAttributeType ); 
+        if ( userPasswordAttribute != null )
+        {
+            notifyUserPasswordChanged( userPasswordAttribute );
+        }
     }
 
+    
+    private byte[] notifyUserPasswordChanged( Attribute userPasswordAttribute ) throws NamingException
+    {
+        byte[] passwordBytes = null;
+        Object password = userPasswordAttribute.get();
+        if ( password instanceof byte[] )
+        {
+            passwordBytes = ( byte[] ) password;
+        }
+        else
+        {
+            passwordBytes = StringTools.getBytesUtf8( ( String ) password );
+        }
+        
+        Collection authenticators = getAuthenticators( "simple" );
+        // try each authenticators
+        for ( Iterator i = authenticators.iterator(); i.hasNext(); )
+        {
+            Authenticator authenticator = ( Authenticator ) i.next();
+            authenticator.passwordChanged( getPrincipal().getJndiName(), passwordBytes );
+        }
+
+        return passwordBytes;
+    }
+    
 
     public void modify( NextInterceptor next, LdapDN name, ModificationItem[] mods ) throws NamingException
     {
@@ -336,6 +372,12 @@
 
         checkAuthenticated();
         next.modify( name, mods );
+
+        Attribute userPasswordAttribute = AttributeUtils.getAttribute( mods, userPasswordAttributeType ); 
+        if ( userPasswordAttribute != null )
+        {
+            notifyUserPasswordChanged( userPasswordAttribute );
+        }
     }
 
 

Modified: directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/Authenticator.java
URL: http://svn.apache.org/viewvc/directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/Authenticator.java?rev=422808&r1=422807&r2=422808&view=diff
==============================================================================
--- directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/Authenticator.java (original)
+++ directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/Authenticator.java Mon Jul 17 12:21:14 2006
@@ -65,6 +65,15 @@
      */
     public void destroy();
 
+    /**
+     * Callback used to respond to password changes by invalidating a password
+     * cache if implemented.  This is an additional feature of an authenticator
+     * which need not be implemented: empty implementation is sufficient.
+     * 
+     * @param bindDn the already normalized distinguished name of the bind principal
+     * @param userPassword the new password for the bind principal
+     */
+    public void passwordChanged( LdapDN bindDn, byte[] userPassword );
 
     /**
      * Performs authentication and returns the principal if succeeded.

Modified: directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/SimpleAuthenticator.java
URL: http://svn.apache.org/viewvc/directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/SimpleAuthenticator.java?rev=422808&r1=422807&r2=422808&view=diff
==============================================================================
--- directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/SimpleAuthenticator.java (original)
+++ directory/branches/apacheds/optimization/core/src/main/java/org/apache/directory/server/core/authn/SimpleAuthenticator.java Mon Jul 17 12:21:14 2006
@@ -23,6 +23,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.WeakHashMap;
 
 import javax.naming.Context;
 import javax.naming.NamingException;
@@ -38,6 +39,7 @@
 import org.apache.directory.shared.ldap.name.LdapDN;
 import org.apache.directory.shared.ldap.util.ArrayUtils;
 import org.apache.directory.shared.ldap.util.Base64;
+import org.apache.directory.shared.ldap.util.StringTools;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,9 +56,10 @@
 public class SimpleAuthenticator extends AbstractAuthenticator
 {
     private static final Logger log = LoggerFactory.getLogger( SimpleAuthenticator.class );
-
     private static final Collection USERLOOKUP_BYPASS;
 
+    private WeakHashMap credentialCache = new WeakHashMap( 1000 );
+    
     static
     {
         Set c = new HashSet();
@@ -81,7 +84,7 @@
         super( "simple" );
     }
 
-
+    
     /**
      * Looks up <tt>userPassword</tt> attribute of the entry whose name is the
      * value of {@link Context#SECURITY_PRINCIPAL} environment variable, and
@@ -102,6 +105,60 @@
             creds = ( ( String ) creds ).getBytes();
         }
 
+        byte[] userPassword = null;
+        if ( credentialCache.containsKey( principalDn.getNormName() ) )
+        {
+            userPassword = ( byte[] ) credentialCache.get( principalDn.getNormName() );
+        }
+        else
+        {
+            userPassword = lookupUserPassword( principalDn );
+        }
+
+        boolean credentialsMatch = false;
+
+        // Check if password is stored as a message digest, i.e. one-way
+        // encrypted
+        if ( this.isPasswordOneWayEncrypted( userPassword ) )
+        {
+            try
+            {
+                // create a corresponding digested password from creds
+                String algorithm = this.getAlgorithmForHashedPassword( userPassword );
+                String digestedCredits = this.createDigestedPassword( algorithm, creds );
+
+                credentialsMatch = ArrayUtils.isEquals( digestedCredits.getBytes(), userPassword );
+            }
+            catch ( NoSuchAlgorithmException nsae )
+            {
+                log.warn( "Password stored with unknown algorithm.", nsae );
+            }
+            catch ( IllegalArgumentException e )
+            {
+                log.warn( "Exception during authentication", e );
+            }
+        }
+        else
+        {
+            // password is not stored one-way encrypted
+            credentialsMatch = ArrayUtils.isEquals( creds, userPassword );
+        }
+
+        if ( credentialsMatch )
+        {
+            LdapPrincipal principal = new LdapPrincipal( principalDn, AuthenticationLevel.SIMPLE );
+            credentialCache.put( principalDn.getNormName(), userPassword );
+            return principal;
+        }
+        else
+        {
+            throw new LdapAuthenticationException();
+        }
+    }
+    
+    
+    protected byte[] lookupUserPassword( LdapDN principalDn ) throws NamingException
+    {
         // ---- lookup the principal entry's userPassword attribute
 
         Invocation invocation = InvocationStack.getInstance().peek();
@@ -132,8 +189,6 @@
 
         // ---- assert that credentials match
 
-        boolean credentialsMatch = false;
-
         if ( userPasswordAttr == null )
         {
             userPassword = ArrayUtils.EMPTY_BYTE_ARRAY;
@@ -144,45 +199,11 @@
 
             if ( userPassword instanceof String )
             {
-                userPassword = ( ( String ) userPassword ).getBytes();
+                userPassword = StringTools.getBytesUtf8( ( String ) userPassword );
             }
         }
-
-        // Check if password is stored as a message digest, i.e. one-way
-        // encrypted
-        if ( this.isPasswordOneWayEncrypted( userPassword ) )
-        {
-            try
-            {
-                // create a corresponding digested password from creds
-                String algorithm = this.getAlgorithmForHashedPassword( userPassword );
-                String digestedCredits = this.createDigestedPassword( algorithm, creds );
-
-                credentialsMatch = ArrayUtils.isEquals( digestedCredits.getBytes(), userPassword );
-            }
-            catch ( NoSuchAlgorithmException nsae )
-            {
-                log.warn( "Password stored with unknown algorithm.", nsae );
-            }
-            catch ( IllegalArgumentException e )
-            {
-                log.warn( "Exception during authentication", e );
-            }
-        }
-        else
-        {
-            // password is not stored one-way encrypted
-            credentialsMatch = ArrayUtils.isEquals( creds, userPassword );
-        }
-
-        if ( credentialsMatch )
-        {
-            return new LdapPrincipal( principalDn, AuthenticationLevel.SIMPLE );
-        }
-        else
-        {
-            throw new LdapAuthenticationException();
-        }
+        
+        return ( byte[] ) userPassword;
     }
 
 
@@ -321,5 +342,11 @@
         result.append( encoded );
 
         return result.toString();
+    }
+
+
+    public void passwordChanged( LdapDN bindDn, byte[] userPassword )
+    {
+        credentialCache.put( bindDn.getNormName(), userPassword );
     }
 }