Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 42072 invoked from network); 17 Jul 2006 19:21:42 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 17 Jul 2006 19:21:42 -0000 Received: (qmail 43606 invoked by uid 500); 17 Jul 2006 19:21:41 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 43563 invoked by uid 500); 17 Jul 2006 19:21:41 -0000 Mailing-List: contact commits-help@directory.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@directory.apache.org Delivered-To: mailing list commits@directory.apache.org Received: (qmail 43486 invoked by uid 99); 17 Jul 2006 19:21:40 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 17 Jul 2006 12:21:40 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 17 Jul 2006 12:21:36 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id 0FBA91A981A; Mon, 17 Jul 2006 12:21:16 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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/ Date: Mon, 17 Jul 2006 19:21:14 -0000 To: commits@directory.apache.org From: akarasulu@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060717192116.0FBA91A981A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N 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 userPassword 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 ); } }