From commits-return-13612-apmail-directory-commits-archive=directory.apache.org@directory.apache.org Tue May 08 03:42:51 2007 Return-Path: Delivered-To: apmail-directory-commits-archive@www.apache.org Received: (qmail 27823 invoked from network); 8 May 2007 03:42:50 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 8 May 2007 03:42:50 -0000 Received: (qmail 53527 invoked by uid 500); 8 May 2007 03:42:57 -0000 Delivered-To: apmail-directory-commits-archive@directory.apache.org Received: (qmail 53485 invoked by uid 500); 8 May 2007 03:42:57 -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 53474 invoked by uid 99); 8 May 2007 03:42:56 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 07 May 2007 20:42:56 -0700 X-ASF-Spam-Status: No, hits=-99.5 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 07 May 2007 20:42:49 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id AC76D1A9838; Mon, 7 May 2007 20:42:28 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit 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 -0000 To: commits@directory.apache.org From: erodriguez@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070508034228.AC76D1A9838@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 Apache Directory Project * @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 Apache Directory Project + * @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 violations = new ArrayList(); + + 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 @@ + + + + + + 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 Apache Directory Project + * @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 pcfgs = new HashSet(); + + MutablePartitionConfiguration pcfg; + + // Add partition 'example' + pcfg = new MutablePartitionConfiguration(); + pcfg.setName( "example" ); + pcfg.setSuffix( "dc=example,dc=com" ); + + Set indexedAttrs = new HashSet(); + 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 list = configuration.getInterceptorConfigurations(); + + interceptorCfg.setName( "passwordPolicyService" ); + interceptorCfg.setInterceptor( new PasswordPolicyService() ); + list.add( interceptorCfg ); + configuration.setInterceptorConfigurations( list ); + + super.setUp(); + + Hashtable env = new Hashtable(); + 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