directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From erodrig...@apache.org
Subject svn commit: r536046 - in /directory/apacheds/branches/kerberos-encryption-types: kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ p...
Date Tue, 08 May 2007 03:42:28 GMT
Author: erodriguez
Date: Mon May  7 20:42:27 2007
New Revision: 536046

URL: http://svn.apache.org/viewvc?view=rev&rev=536046
Log:
New PasswordPolicyService interceptor in 'kerberos-encryption-types' branch:
o  Converted Change Password protocol's password policy processor to a core interceptor.
o  Added integration test demonstrating password policy enforcement with LDAP protocol.
o  Disabled password policy checking in Change Password in lieu of password policy enforcement
now in core interceptor.
o  Changed Change Password exception handling to propagate password policy error messages
from core out to Change Password clients.
o  Added PasswordPolicyService interceptor to service chain in server-main's server.xml.

Added:
    directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java
  (with props)
    directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java
  (with props)
Modified:
    directory/apacheds/branches/kerberos-encryption-types/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ChangePassword.java
    directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ChangePasswordChain.java
    directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ProcessPasswordChange.java
    directory/apacheds/branches/kerberos-encryption-types/server-main/server.xml

Modified: directory/apacheds/branches/kerberos-encryption-types/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ChangePassword.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ChangePassword.java?view=diff&rev=536046&r1=536045&r2=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ChangePassword.java
(original)
+++ directory/apacheds/branches/kerberos-encryption-types/kerberos-shared/src/main/java/org/apache/directory/server/kerberos/shared/store/operations/ChangePassword.java
Mon May  7 20:42:27 2007
@@ -68,7 +68,7 @@
     }
 
 
-    public Object execute( DirContext ctx, Name searchBaseDn )
+    public Object execute( DirContext ctx, Name searchBaseDn ) throws NamingException
     {
         if ( principal == null )
         {
@@ -83,16 +83,9 @@
 
         String dn = null;
 
-        try
-        {
-            dn = search( ctx, principal.getName() );
-            Name rdn = getRelativeName( ctx.getNameInNamespace(), dn );
-            ctx.modifyAttributes( rdn, mods );
-        }
-        catch ( NamingException e )
-        {
-            return null;
-        }
+        dn = search( ctx, principal.getName() );
+        Name rdn = getRelativeName( ctx.getNameInNamespace(), dn );
+        ctx.modifyAttributes( rdn, mods );
 
         return dn;
     }

Modified: directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ChangePasswordChain.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ChangePasswordChain.java?view=diff&rev=536046&r1=536045&r2=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ChangePasswordChain.java
(original)
+++ directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ChangePasswordChain.java
Mon May  7 20:42:27 2007
@@ -60,7 +60,6 @@
             addLast( "monitorContext", new MonitorContext() );
         }
 
-        addLast( "checkPasswordPolicy", new CheckPasswordPolicy() );
         addLast( "processPasswordChange", new ProcessPasswordChange() );
         addLast( "buildReply", new BuildReply() );
 

Modified: directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ProcessPasswordChange.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ProcessPasswordChange.java?view=diff&rev=536046&r1=536045&r2=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ProcessPasswordChange.java
(original)
+++ directory/apacheds/branches/kerberos-encryption-types/protocol-changepw/src/main/java/org/apache/directory/server/changepw/service/ProcessPasswordChange.java
Mon May  7 20:42:27 2007
@@ -20,6 +20,7 @@
 package org.apache.directory.server.changepw.service;
 
 
+import javax.naming.NamingException;
 import javax.security.auth.kerberos.KerberosPrincipal;
 
 import org.apache.directory.server.changepw.exceptions.ChangePasswordException;
@@ -33,6 +34,8 @@
 
 
 /**
+ * An {@link IoHandlerCommand} for storing the new password.
+ * 
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  * @version $Rev$, $Date$
  */
@@ -57,15 +60,19 @@
         // seq-number must have same value as authenticator
         // ignore r-address
 
-        // store password in database
         try
         {
             String principalName = store.changePassword( clientPrincipal, newPassword );
             log.debug( "Successfully modified principal {}", principalName );
         }
+        catch ( NamingException ne )
+        {
+            log.warn( ne.getMessage(), ne );
+            throw new ChangePasswordException( ErrorType.KRB5_KPASSWD_SOFTERROR, ne.getExplanation().getBytes()
);
+        }
         catch ( Exception e )
         {
-            log.error( e.getMessage(), e );
+            log.error( "Unexpected exception.", e );
             throw new ChangePasswordException( ErrorType.KRB5_KPASSWD_HARDERROR );
         }
 

Added: directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java?view=auto&rev=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java
(added)
+++ directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java
Mon May  7 20:42:27 2007
@@ -0,0 +1,338 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.server.kerberos.shared.interceptors;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+
+import org.apache.directory.server.core.interceptor.BaseInterceptor;
+import org.apache.directory.server.core.interceptor.Interceptor;
+import org.apache.directory.server.core.interceptor.NextInterceptor;
+import org.apache.directory.server.core.interceptor.context.AddOperationContext;
+import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
+import org.apache.directory.server.core.interceptor.context.OperationContext;
+import org.apache.directory.shared.ldap.message.ModificationItemImpl;
+import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.directory.shared.ldap.util.AttributeUtils;
+import org.apache.directory.shared.ldap.util.StringTools;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An {@link Interceptor} that enforces password policy for users.  Add or modify operations
+ * on the 'userPassword' attribute are checked against a password policy.  The password is
+ * rejected if it does not pass the password policy checks.  The password MUST be passed
to
+ * the core as plaintext.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class PasswordPolicyService extends BaseInterceptor
+{
+    /** The log for this class. */
+    private static final Logger log = LoggerFactory.getLogger( PasswordPolicyService.class
);
+
+    /** The service name. */
+    public static final String NAME = "passwordPolicyService";
+
+
+    /**
+     * Check added attributes for a 'userPassword'.  If a 'userPassword' is found, apply
any
+     * password policy checks.
+     */
+    public void add( NextInterceptor next, OperationContext addContext ) throws NamingException
+    {
+        LdapDN normName = addContext.getDn();
+
+        Attributes entry = ( ( AddOperationContext ) addContext ).getEntry();
+
+        log.debug( "Adding the entry " + AttributeUtils.toString( entry ) + " for DN = '"
+ normName.getUpName() + "'" );
+
+        Object attr = null;
+
+        if ( entry.get( "userPassword" ) != null )
+        {
+            String userPassword = "";
+            String username = "";
+
+            attr = entry.get( "userPassword" ).get();
+
+            if ( attr instanceof String )
+            {
+                log.debug( "Adding Attribute id : 'userPassword',  Values : ['" + attr +
"']" );
+                userPassword = ( String ) attr;
+            }
+            else if ( attr instanceof byte[] )
+            {
+                String string = StringTools.utf8ToString( ( byte[] ) attr );
+
+                StringBuffer sb = new StringBuffer();
+                sb.append( "'" + string + "' ( " );
+                sb.append( StringTools.dumpBytes( ( byte[] ) attr ).trim() );
+                log.debug( "Adding Attribute id : 'userPassword',  Values : [ " + sb.toString()
+ " ) ]" );
+
+                userPassword = string;
+            }
+
+            if ( entry.get( "cn" ) != null )
+            {
+                attr = entry.get( "cn" ).get();
+                username = ( String ) attr;
+            }
+
+            // If userPassword fails checks, throw new NamingException.
+            check( username, userPassword );
+        }
+
+        next.add( addContext );
+    }
+
+
+    /**
+     * Check modification items for a 'userPassword'.  If a 'userPassword' is found, apply
any
+     * password policy checks.
+     */
+    public void modify( NextInterceptor next, OperationContext opContext ) throws NamingException
+    {
+        LdapDN name = opContext.getDn();
+        ModifyOperationContext modContext = ( ModifyOperationContext ) opContext;
+
+        ModificationItemImpl[] mods = modContext.getModItems();
+
+        String operation = null;
+
+        for ( int ii = 0; ii < mods.length; ii++ )
+        {
+            switch ( mods[ii].getModificationOp() )
+            {
+                case DirContext.ADD_ATTRIBUTE:
+                    operation = "Adding";
+                    break;
+                case DirContext.REMOVE_ATTRIBUTE:
+                    operation = "Removing";
+                    break;
+                case DirContext.REPLACE_ATTRIBUTE:
+                    operation = "Replacing";
+                    break;
+            }
+
+            Attribute attr = mods[ii].getAttribute();
+            String id = attr.getID();
+
+            if ( id.equalsIgnoreCase( "userPassword" ) )
+            {
+                Object userPassword = attr.get();
+
+                if ( userPassword != null )
+                {
+                    if ( userPassword instanceof String )
+                    {
+                        log.debug( "Adding Attribute id : 'userPassword',  Values : ['" +
attr + "']" );
+                    }
+                    else if ( userPassword instanceof byte[] )
+                    {
+                        String string = StringTools.utf8ToString( ( byte[] ) userPassword
);
+
+                        StringBuffer sb = new StringBuffer();
+                        sb.append( "'" + string + "' ( " );
+                        sb.append( StringTools.dumpBytes( ( byte[] ) userPassword ).trim()
);
+                        log.debug( "Adding Attribute id : 'userPassword',  Values : [ " +
sb.toString() + " ) ]" );
+
+                        userPassword = string;
+                    }
+
+                    // if userPassword fails checks, throw new NamingException.
+                    check( name.getUpName(), ( String ) userPassword );
+                }
+            }
+
+            log.debug( operation + " for entry '" + name.getUpName() + "' the attribute "
+ mods[ii].getAttribute() );
+        }
+
+        next.modify( opContext );
+    }
+
+
+    void check( String username, String password ) throws NamingException
+    {
+        int passwordLength = 6;
+        int categoryCount = 2;
+        int tokenSize = 3;
+
+        if ( !isValid( username, password, passwordLength, categoryCount, tokenSize ) )
+        {
+            String explanation = buildErrorMessage( username, password, passwordLength, categoryCount,
tokenSize );
+            log.error( explanation );
+
+            throw new NamingException( explanation );
+        }
+    }
+
+
+    /**
+     * Tests that:
+     * The password is at least six characters long.
+     * The password contains a mix of characters.
+     * The password does not contain three letter (or more) tokens from the user's account
name.
+     */
+    boolean isValid( String username, String password, int passwordLength, int categoryCount,
int tokenSize )
+    {
+        return isValidPasswordLength( password, passwordLength ) && isValidCategoryCount(
password, categoryCount )
+            && isValidUsernameSubstring( username, password, tokenSize );
+    }
+
+
+    /**
+     * The password is at least six characters long.
+     */
+    boolean isValidPasswordLength( String password, int passwordLength )
+    {
+        return password.length() >= passwordLength;
+    }
+
+
+    /**
+     * The password contains characters from at least three of the following four categories:
+     * English uppercase characters (A - Z)
+     * English lowercase characters (a - z)
+     * Base 10 digits (0 - 9)
+     * Any non-alphanumeric character (for example: !, $, #, or %)
+     */
+    boolean isValidCategoryCount( String password, int categoryCount )
+    {
+        int uppercase = 0;
+        int lowercase = 0;
+        int digit = 0;
+        int nonAlphaNumeric = 0;
+
+        char[] characters = password.toCharArray();
+
+        for ( int ii = 0; ii < characters.length; ii++ )
+        {
+            if ( Character.isLowerCase( characters[ii] ) )
+            {
+                lowercase = 1;
+            }
+            else
+            {
+                if ( Character.isUpperCase( characters[ii] ) )
+                {
+                    uppercase = 1;
+                }
+                else
+                {
+                    if ( Character.isDigit( characters[ii] ) )
+                    {
+                        digit = 1;
+                    }
+                    else
+                    {
+                        if ( !Character.isLetterOrDigit( characters[ii] ) )
+                        {
+                            nonAlphaNumeric = 1;
+                        }
+                    }
+                }
+            }
+        }
+
+        return ( uppercase + lowercase + digit + nonAlphaNumeric ) >= categoryCount;
+    }
+
+
+    /**
+     * The password does not contain three letter (or more) tokens from the user's account
name.
+     * 
+     * If the account name is less than three characters long, this check is not performed
+     * because the rate at which passwords would be rejected is too high. For each token
that is
+     * three or more characters long, that token is searched for in the password; if it is
present,
+     * the password change is rejected. For example, the name "First M. Last" would be split
into
+     * three tokens: "First", "M", and "Last". Because the second token is only one character
long,
+     * it would be ignored. Therefore, this user could not have a password that included
either
+     * "first" or "last" as a substring anywhere in the password. All of these checks are
+     * case-insensitive.
+     */
+    boolean isValidUsernameSubstring( String username, String password, int tokenSize )
+    {
+        String[] tokens = username.split( "[^a-zA-Z]" );
+
+        for ( int ii = 0; ii < tokens.length; ii++ )
+        {
+            if ( tokens[ii].length() >= tokenSize )
+            {
+                if ( password.matches( "(?i).*" + tokens[ii] + ".*" ) )
+                {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    private String buildErrorMessage( String username, String password, int passwordLength,
int categoryCount,
+        int tokenSize )
+    {
+        List<String> violations = new ArrayList<String>();
+
+        if ( !isValidPasswordLength( password, passwordLength ) )
+        {
+            violations.add( "length too short" );
+        }
+
+        if ( !isValidCategoryCount( password, categoryCount ) )
+        {
+            violations.add( "insufficient character mix" );
+        }
+
+        if ( !isValidUsernameSubstring( username, password, tokenSize ) )
+        {
+            violations.add( "contains portions of username" );
+        }
+
+        StringBuffer sb = new StringBuffer( "Password violates policy:  " );
+
+        boolean isFirst = true;
+
+        for ( String violation : violations )
+        {
+            if ( isFirst )
+            {
+                isFirst = false;
+            }
+            else
+            {
+                sb.append( ", " );
+            }
+
+            sb.append( violation );
+        }
+
+        return sb.toString();
+    }
+}

Propchange: directory/apacheds/branches/kerberos-encryption-types/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/shared/interceptors/PasswordPolicyService.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: directory/apacheds/branches/kerberos-encryption-types/server-main/server.xml
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/server-main/server.xml?view=diff&rev=536046&r1=536045&r2=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/server-main/server.xml (original)
+++ directory/apacheds/branches/kerberos-encryption-types/server-main/server.xml Mon May 
7 20:42:27 2007
@@ -153,6 +153,12 @@
           </property>
         </bean>
         <bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
+          <property name="name" value="passwordPolicyService" />
+          <property name="interceptor">
+            <bean class="org.apache.directory.server.kerberos.shared.interceptors.PasswordPolicyService"
/>
+          </property>
+        </bean>
+        <bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
           <property name="name" value="keyDerivationService" />
           <property name="interceptor">
             <bean class="org.apache.directory.server.kerberos.shared.interceptors.KeyDerivationService"
/>

Added: directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java?view=auto&rev=536046
==============================================================================
--- directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java
(added)
+++ directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java
Mon May  7 20:42:27 2007
@@ -0,0 +1,305 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.server;
+
+
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+import org.apache.directory.server.core.configuration.InterceptorConfiguration;
+import org.apache.directory.server.core.configuration.MutableInterceptorConfiguration;
+import org.apache.directory.server.core.configuration.MutablePartitionConfiguration;
+import org.apache.directory.server.core.configuration.PartitionConfiguration;
+import org.apache.directory.server.kerberos.shared.interceptors.PasswordPolicyService;
+import org.apache.directory.server.unit.AbstractServerTest;
+import org.apache.directory.shared.ldap.message.AttributeImpl;
+import org.apache.directory.shared.ldap.message.AttributesImpl;
+
+
+/**
+ * An {@link AbstractServerTest} testing the (@link {@link PasswordPolicyService}.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class PasswordPolicyServiceTest extends AbstractServerTest
+{
+    private DirContext ctx = null;
+    private DirContext users = null;
+
+
+    /**
+     * Set up a partition for EXAMPLE.COM, add the {@link PasswordPolicyService}
+     * interceptor, and create a users subcontext.
+     */
+    public void setUp() throws Exception
+    {
+        configuration.setAllowAnonymousAccess( false );
+
+        Attributes attrs;
+        Set<PartitionConfiguration> pcfgs = new HashSet<PartitionConfiguration>();
+
+        MutablePartitionConfiguration pcfg;
+
+        // Add partition 'example'
+        pcfg = new MutablePartitionConfiguration();
+        pcfg.setName( "example" );
+        pcfg.setSuffix( "dc=example,dc=com" );
+
+        Set<Object> indexedAttrs = new HashSet<Object>();
+        indexedAttrs.add( "ou" );
+        indexedAttrs.add( "dc" );
+        indexedAttrs.add( "objectClass" );
+        pcfg.setIndexedAttributes( indexedAttrs );
+
+        attrs = new AttributesImpl( true );
+        Attribute attr = new AttributeImpl( "objectClass" );
+        attr.add( "top" );
+        attr.add( "domain" );
+        attrs.put( attr );
+        attr = new AttributeImpl( "dc" );
+        attr.add( "example" );
+        attrs.put( attr );
+        pcfg.setContextEntry( attrs );
+
+        pcfgs.add( pcfg );
+        configuration.setPartitionConfigurations( pcfgs );
+
+        MutableInterceptorConfiguration interceptorCfg = new MutableInterceptorConfiguration();
+        List<InterceptorConfiguration> list = configuration.getInterceptorConfigurations();
+
+        interceptorCfg.setName( "passwordPolicyService" );
+        interceptorCfg.setInterceptor( new PasswordPolicyService() );
+        list.add( interceptorCfg );
+        configuration.setInterceptorConfigurations( list );
+
+        super.setUp();
+
+        Hashtable<String, String> env = new Hashtable<String, String>();
+        env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+        env.put( "java.naming.provider.url", "ldap://localhost:" + port + "/dc=example,dc=com"
);
+        env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+        env.put( "java.naming.security.credentials", "secret" );
+        env.put( "java.naming.security.authentication", "simple" );
+        ctx = new InitialDirContext( env );
+
+        attrs = getOrgUnitAttributes( "users" );
+        users = ctx.createSubcontext( "ou=users", attrs );
+    }
+
+
+    /**
+     * Tests that passwords that are too short are properly rejected. 
+     */
+    public void testLength()
+    {
+        Attributes attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "HN1"
);
+        try
+        {
+            users.createSubcontext( "uid=hnelson", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertTrue( ne.getMessage().contains( "length too short" ) );
+            assertFalse( ne.getMessage().contains( "insufficient character mix" ) );
+            assertFalse( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords with insufficient character mix are properly rejected. 
+     */
+    public void testCharacterMix()
+    {
+        Attributes attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "secret"
);
+        try
+        {
+            users.createSubcontext( "uid=hnelson", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertFalse( ne.getMessage().contains( "length too short" ) );
+            assertTrue( ne.getMessage().contains( "insufficient character mix" ) );
+            assertFalse( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords that contain substrings of the username are properly rejected.

+     */
+    public void testContainsUsername()
+    {
+        Attributes attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "A1nelson"
);
+        try
+        {
+            users.createSubcontext( "uid=hnelson", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertFalse( ne.getMessage().contains( "length too short" ) );
+            assertFalse( ne.getMessage().contains( "insufficient character mix" ) );
+            assertTrue( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords with insufficient character mix and that are too
+     * short are properly rejected. 
+     */
+    public void testCharacterMixAndLength()
+    {
+        Attributes attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "hi"
);
+        try
+        {
+            users.createSubcontext( "uid=hnelson", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertTrue( ne.getMessage().contains( "length too short" ) );
+            assertTrue( ne.getMessage().contains( "insufficient character mix" ) );
+            assertFalse( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords that are too short and that contain substrings of
+     * the username are properly rejected.
+     */
+    public void testLengthAndContainsUsername()
+    {
+        Attributes attrs = getPersonAttributes( "Bush", "William Bush", "wbush", "bush1"
);
+        try
+        {
+            users.createSubcontext( "uid=wbush", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertTrue( ne.getMessage().contains( "length too short" ) );
+            assertFalse( ne.getMessage().contains( "insufficient character mix" ) );
+            assertTrue( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords with insufficient character mix and that contain substrings of
+     * the username are properly rejected.
+     */
+    public void testCharacterMixAndContainsUsername()
+    {
+        Attributes attrs = getPersonAttributes( "Nelson", "Horatio Nelson", "hnelson", "hnelson"
);
+        try
+        {
+            users.createSubcontext( "uid=hnelson", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertFalse( ne.getMessage().contains( "length too short" ) );
+            assertTrue( ne.getMessage().contains( "insufficient character mix" ) );
+            assertTrue( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tests that passwords with insufficient character mix and that are too
+     * short and that contain substrings of the username are properly rejected.
+     */
+    public void testCharacterMixAndLengthAndContainsUsername()
+    {
+        Attributes attrs = getPersonAttributes( "Bush", "William Bush", "wbush", "bush" );
+        try
+        {
+            users.createSubcontext( "uid=wbush", attrs );
+            fail( "Shouldn't have gotten here." );
+        }
+        catch ( NamingException ne )
+        {
+            assertTrue( ne.getMessage().contains( "length too short" ) );
+            assertTrue( ne.getMessage().contains( "insufficient character mix" ) );
+            assertTrue( ne.getMessage().contains( "contains portions of username" ) );
+        }
+    }
+
+
+    /**
+     * Tear down.
+     */
+    public void tearDown() throws Exception
+    {
+        ctx.close();
+        ctx = null;
+        super.tearDown();
+    }
+
+
+    /**
+     * Convenience method for creating a person.
+     */
+    protected Attributes getPersonAttributes( String sn, String cn, String uid, String userPassword
)
+    {
+        Attributes attrs = new AttributesImpl();
+        Attribute ocls = new AttributeImpl( "objectClass" );
+        ocls.add( "top" );
+        ocls.add( "person" ); // sn $ cn
+        ocls.add( "inetOrgPerson" ); // uid
+        attrs.put( ocls );
+        attrs.put( "cn", cn );
+        attrs.put( "sn", sn );
+        attrs.put( "uid", uid );
+        attrs.put( "userPassword", userPassword );
+
+        return attrs;
+    }
+
+
+    /**
+     * Convenience method for creating an organizational unit.
+     */
+    protected Attributes getOrgUnitAttributes( String ou )
+    {
+        Attributes attrs = new AttributesImpl();
+        Attribute ocls = new AttributeImpl( "objectClass" );
+        ocls.add( "top" );
+        ocls.add( "organizationalUnit" );
+        attrs.put( ocls );
+        attrs.put( "ou", ou );
+
+        return attrs;
+    }
+}

Propchange: directory/apacheds/branches/kerberos-encryption-types/server-unit/src/test/java/org/apache/directory/server/PasswordPolicyServiceTest.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message