mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
27.00.2007 20a5a72f2842bddd089de5d161b3e2b1c644a3d4
Add two new password validators to OpenDS:

- A validator which enforces a restriction that passwords must have at least a
specified number of unique characters (issue #1219).

- A validator which enforces a restriction that passwords may not have any
character which appears more than a specified number of times in a row (issue
#1220).
6 files added
3 files modified
1985 ■■■■■ changed files
opends/resource/config/config.ldif 20 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 22 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RepeatedCharactersPasswordValidatorConfiguration.xml 66 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/UniqueCharactersPasswordValidatorConfiguration.xml 64 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidator.java 196 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidator.java 187 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 32 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java 699 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java 699 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1098,6 +1098,16 @@
ds-cfg-minimum-password-length: 6
ds-cfg-maximum-password-length: 0
dn: cn=Repeated Characters,cn=Password Validators,cn=config
objectClass: top
objectClass: ds-cfg-password-validator
objectClass: ds-cfg-repeated-characters-password-validator
cn: Repeated Characters
ds-cfg-password-validator-class: org.opends.server.extensions.RepeatedCharactersPasswordValidator
ds-cfg-password-validator-enabled: true
ds-cfg-maximum-consecutive-length: 2
ds-cfg-case-sensitive-validation: false
dn: cn=Similarity-Based Password Validator,cn=Password Validators,cn=config
objectClass: top
objectClass: ds-cfg-password-validator
@@ -1107,6 +1117,16 @@
ds-cfg-password-validator-enabled: true
ds-cfg-minimum-password-difference: 3
dn: cn=Unique Characters,cn=Password Validators,cn=config
objectClass: top
objectClass: ds-cfg-password-validator
objectClass: ds-cfg-unique-characters-password-validator
cn: Unique Characters
ds-cfg-password-validator-class: org.opends.server.extensions.UniqueCharactersPasswordValidator
ds-cfg-password-validator-enabled: true
ds-cfg-minimum-unique-characters: 5
ds-cfg-case-sensitive-validation: false
dn: cn=Plugins,cn=config
objectClass: top
objectClass: ds-cfg-branch
opends/resource/schema/02-config.ldif
@@ -1091,6 +1091,18 @@
  NAME 'ds-cfg-minimum-password-difference' 
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE 
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.322
  NAME 'ds-cfg-minimum-unique-characters'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.323
  NAME 'ds-cfg-maximum-consecutive-length'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.324
  NAME 'ds-cfg-case-sensitive-validation'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1510,4 +1522,14 @@
  NAME 'ds-cfg-similarity-based-password-validator'
  SUP ds-cfg-password-validator STRUCTURAL
  MUST ds-cfg-minimum-password-difference X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.89
  NAME 'ds-cfg-unique-characters-password-validator'
  SUP ds-cfg-password-validator STRUCTURAL
  MUST ( ds-cfg-minimum-unique-characters $ ds-cfg-case-sensitive-validation )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.90
  NAME 'ds-cfg-repeated-characters-password-validator'
  SUP ds-cfg-password-validator STRUCTURAL
  MUST ( ds-cfg-maximum-consecutive-length $ ds-cfg-case-sensitive-validation )
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/RepeatedCharactersPasswordValidatorConfiguration.xml
New file
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<adm:managed-object name="repeated-characters-password-validator"
plural-name="repeated-characters-password-validators"
package="org.opends.server.admin.std" extends="password-validator"
xmlns:adm="http://www.opends.org/admin"
xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The
    <adm:user-friendly-name />
    is used to determine whether a proposed password is acceptable based on
    the number of times any character may appear consecutively in a password
    value.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.90</ldap:oid>
      <ldap:name>ds-cfg-repeated-characters-password-validator</ldap:name>
      <ldap:superior>ds-cfg-password-validator</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="maximum-consecutive-length" mandatory="true">
    <adm:synopsis>
      Specifies the maximum number of times that any character may appear
      consecutively in a password value.
    </adm:synopsis>
    <adm:description>
      Specifies the maximum number of times that any character may appear
      consecutively in a password value.  A value of zero indicates that there
      will be no maximum limit enforced.  Changes to this configuration
      attribute will take effect immediately.
    </adm:description>
    <adm:syntax>
      <adm:integer lower-limit="0" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.323</ldap:oid>
        <ldap:name>ds-cfg-maximum-consecutive-length</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="case-sensitive-validation" mandatory="true">
    <adm:synopsis>
      Indicates whether this password validator should treat password characters
      in a case-sensitive manner.
    </adm:synopsis>
    <adm:description>
      Indicates whether this password validator should treat password characters
      in a case-sensitive manner.  A value of false indicates that any
      differences in capitalization should be ignored when looking for
      consecutive characters in the password.  A value of true indicates that
      a character should only be considered repeating if all consecutive
      occurrences use the same capitalization.
    </adm:description>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.324</ldap:oid>
        <ldap:name>ds-cfg-case-sensitive-validation</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/UniqueCharactersPasswordValidatorConfiguration.xml
New file
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<adm:managed-object name="unique-characters-password-validator"
plural-name="unique-characters-password-validators"
package="org.opends.server.admin.std" extends="password-validator"
xmlns:adm="http://www.opends.org/admin"
xmlns:ldap="http://www.opends.org/admin-ldap">
  <adm:synopsis>
    The
    <adm:user-friendly-name />
    is used to determine whether a proposed password is acceptable based on
    the number of unique characters that it contains.
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.89</ldap:oid>
      <ldap:name>ds-cfg-unique-characters-password-validator</ldap:name>
      <ldap:superior>ds-cfg-password-validator</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="minimum-unique-characters" mandatory="true">
    <adm:synopsis>
      Specifies the minimum number of unique characters that a password will be
      allowed to contain.
    </adm:synopsis>
    <adm:description>
      Specifies the minimum number of unique characters that a password will be
      allowed to contain.  A value of zero indicates that there will be no
      minimum value enforced.  Changes to this configuration attribute will take
      effect immediately.
    </adm:description>
    <adm:syntax>
      <adm:integer lower-limit="0" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.322</ldap:oid>
        <ldap:name>ds-cfg-minimum-unique-characters</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="case-sensitive-validation" mandatory="true">
    <adm:synopsis>
      Indicates whether this password validator should treat password characters
      in a case-sensitive manner.
    </adm:synopsis>
    <adm:description>
      Indicates whether this password validator should treat password characters
      in a case-sensitive manner.  A value of true indicates that a capital
      letter should not be considered the same as its lowercase counterpart.
      A value of false indicates that differences in capitalization should be
      ignored when looking at the number of unique characters in the password.
    </adm:description>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.324</ldap:oid>
        <ldap:name>ds-cfg-case-sensitive-validation</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidator.java
New file
@@ -0,0 +1,196 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.
            RepeatedCharactersPasswordValidatorCfg;
import org.opends.server.api.PasswordValidator;
import org.opends.server.core.Operation;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.ByteString;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
/**
 * This class provides an OpenDS password validator that may be used to ensure
 * that proposed passwords are not allowed to have the same character appear
 * several times consecutively.
 */
public class RepeatedCharactersPasswordValidator
       extends PasswordValidator<RepeatedCharactersPasswordValidatorCfg>
       implements ConfigurationChangeListener<
                       RepeatedCharactersPasswordValidatorCfg>
{
  // The current configuration for this password validator.
  private RepeatedCharactersPasswordValidatorCfg currentConfig;
  /**
   * Creates a new instance of this repeated characters password validator.
   */
  public RepeatedCharactersPasswordValidator()
  {
    super();
    // No implementation is required here.  All initialization should be
    // performed in the initializePasswordValidator() method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordValidator(
                   RepeatedCharactersPasswordValidatorCfg configuration)
  {
    configuration.addRepeatedCharactersChangeListener(this);
    currentConfig = configuration;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizePasswordValidator()
  {
    currentConfig.removeRepeatedCharactersChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean passwordIsAcceptable(ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      Operation operation, Entry userEntry,
                                      StringBuilder invalidReason)
  {
    // Get a handle to the current configuration and see if we need to count
    // the number of repeated characters in the password.
    RepeatedCharactersPasswordValidatorCfg config = currentConfig;
    int maxRepeats = config.getMaximumConsecutiveLength();
    if (maxRepeats <= 0)
    {
      // We don't need to check anything, so the password will be acceptable.
      return true;
    }
    // Get the password as a string.  If we should use case-insensitive
    // validation, then convert it to use all lowercase characters.
    String passwordString = newPassword.stringValue();
    if (! config.isCaseSensitiveValidation())
    {
      passwordString = passwordString.toLowerCase();
    }
    // Create variables to keep track of the last character we've seen and how
    // many times we have seen it.
    char lastCharacter    = '\u0000';
    int  consecutiveCount = 0;
    // Iterate through the characters in the password.  If the consecutive
    // count ever gets too high, then fail.
    for (int i=0; i < passwordString.length(); i++)
    {
      char currentCharacter = passwordString.charAt(i);
      if (currentCharacter == lastCharacter)
      {
        consecutiveCount++;
        if (consecutiveCount > maxRepeats)
        {
          int    msgID   = MSGID_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE;
          String message = getMessage(msgID, maxRepeats);
          invalidReason.append(message);
          return false;
        }
      }
      else
      {
        lastCharacter    = currentCharacter;
        consecutiveCount = 1;
      }
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      RepeatedCharactersPasswordValidatorCfg configuration,
                      List<String> unacceptableReasons)
  {
    // All of the necessary validation should have been performed automatically,
    // so if we get to this point then the new configuration will be acceptable.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
                      RepeatedCharactersPasswordValidatorCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // For this password validator, we will always be able to successfully apply
    // the new configuration.
    currentConfig = configuration;
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidator.java
New file
@@ -0,0 +1,187 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.UniqueCharactersPasswordValidatorCfg;
import org.opends.server.api.PasswordValidator;
import org.opends.server.core.Operation;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.ByteString;
import org.opends.server.types.Entry;
import org.opends.server.types.ResultCode;
import static org.opends.server.messages.ExtensionsMessages.*;
import static org.opends.server.messages.MessageHandler.*;
/**
 * This class provides an OpenDS password validator that may be used to ensure
 * that proposed passwords contain at least a specified number of different
 * characters.
 */
public class UniqueCharactersPasswordValidator
       extends PasswordValidator<UniqueCharactersPasswordValidatorCfg>
       implements ConfigurationChangeListener<
                       UniqueCharactersPasswordValidatorCfg>
{
  // The current configuration for this password validator.
  private UniqueCharactersPasswordValidatorCfg currentConfig;
  /**
   * Creates a new instance of this unique characters password validator.
   */
  public UniqueCharactersPasswordValidator()
  {
    super();
    // No implementation is required here.  All initialization should be
    // performed in the initializePasswordValidator() method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordValidator(
                   UniqueCharactersPasswordValidatorCfg configuration)
  {
    configuration.addUniqueCharactersChangeListener(this);
    currentConfig = configuration;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizePasswordValidator()
  {
    currentConfig.removeUniqueCharactersChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean passwordIsAcceptable(ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      Operation operation, Entry userEntry,
                                      StringBuilder invalidReason)
  {
    // Get a handle to the current configuration and see if we need to count
    // the number of unique characters in the password.
    UniqueCharactersPasswordValidatorCfg config = currentConfig;
    int minUniqueCharacters = config.getMinimumUniqueCharacters();
    if (minUniqueCharacters <= 0)
    {
      // We don't need to check anything, so the password will be acceptable.
      return true;
    }
    // Create a set that will be used to keep track of the unique characters
    // contained in the proposed password.
    HashSet<Character> passwordCharacters = new HashSet<Character>();
    // Iterate through the characters in the new password and place them in the
    // set as needed.  If we should behave in a case-insensitive manner, then
    // convert all the characters to lowercase first.
    String passwordString = newPassword.stringValue();
    if (! config.isCaseSensitiveValidation())
    {
      passwordString = passwordString.toLowerCase();
    }
    for (int i=0; i < passwordString.length(); i++)
    {
      passwordCharacters.add(passwordString.charAt(i));
    }
    // If the size of the password characters set is less than the minimum
    // number of allowed unique characters, then we will reject the password.
    if (passwordCharacters.size() < minUniqueCharacters)
    {
      int    msgID   = MSGID_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS;
      String message = getMessage(msgID, minUniqueCharacters);
      invalidReason.append(message);
      return false;
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      UniqueCharactersPasswordValidatorCfg configuration,
                      List<String> unacceptableReasons)
  {
    // All of the necessary validation should have been performed automatically,
    // so if we get to this point then the new configuration will be acceptable.
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
                      UniqueCharactersPasswordValidatorCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // For this password validator, we will always be able to successfully apply
    // the new configuration.
    currentConfig = configuration;
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -4807,6 +4807,25 @@
  /**
   * The message ID for the message that will be used if the same character
   * appears too many times in consecutive order in a given password.  This
   * takes a single argument, which is the maximum number of times a character
   * may appear in consecutive order.
   */
  public static final int MSGID_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 457;
  /**
   * The message ID for the message that will be used if a given password does
   * not have enough unique characters.  This takes a single argument, which is
   * the minimum number of unique characters that a password may contain.
   */
  public static final int MSGID_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_MILD_ERROR | 458;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -6930,6 +6949,19 @@
    registerMessage(MSGID_DYNAMICGROUP_CANNOT_RETURN_ENTRY,
                    "The server encountered a timeout while attempting to " +
                    "add user %s to the member list for dynamic group %s.");
    registerMessage(MSGID_REPEATEDCHARS_VALIDATOR_TOO_MANY_CONSECUTIVE,
                    "The provided password contained too many instances " +
                    "of the same character appearing consecutively.  The " +
                    "maximum number of times the same character may appear " +
                    "consecutively in a password is %d.");
    registerMessage(MSGID_UNIQUECHARS_VALIDATOR_NOT_ENOUGH_UNIQUE_CHARS,
                    "The provided password does not contain enough unique " +
                    "characters.  The minimum number of unique characters " +
                    "that may appear in a user password is %d.");
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/RepeatedCharactersPasswordValidatorTestCase.java
New file
@@ -0,0 +1,699 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.
            RepeatedCharactersPasswordValidatorCfgDefn;
import org.opends.server.admin.std.server.
            RepeatedCharactersPasswordValidatorCfg;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.Attribute;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of test cases for the repeated characters password validator.
 */
public class RepeatedCharactersPasswordValidatorTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Retrieves a set of valid configuration entries that may be used to
   * initialize the validator.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "validConfigs")
  public Object[][] getValidConfigs()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: false",
         "",
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: true",
         "",
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 0",
         "ds-cfg-case-sensitive-validation: false");
    Object[][] array = new Object[entries.size()][1];
    for (int i=0; i < array.length; i++)
    {
      array[i] = new Object[] { entries.get(i) };
    }
    return array;
  }
  /**
   * Tests the process of initializing the server with valid configurations.
   *
   * @param  entry  The configuration entry to use for the initialization.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "validConfigs")
  public void testInitializeWithValidConfigs(Entry e)
         throws Exception
  {
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(), e);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    validator.finalizePasswordValidator();
  }
  /**
   * Retrieves a set of invalid configuration entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidConfigs")
  public Object[][] getInvalidConfigs()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
         // Missing maximum consecutive length
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-case-sensitive-validation: false",
         "",
         // Missing case-sensitive validation
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "",
         // Non-numeric maximum consecutive length
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: non-numeric",
         "ds-cfg-case-sensitive-validation: false",
         "",
         // Non-boolean case-sensitive validation
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: non-boolean",
         "",
         // Maximum consecutive length out of range.
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: -1",
         "ds-cfg-case-sensitive-validation: false");
    Object[][] array = new Object[entries.size()][1];
    for (int i=0; i < array.length; i++)
    {
      array[i] = new Object[] { entries.get(i) };
    }
    return array;
  }
  /**
   * Tests the process of initializing the server with invalid configurations.
   *
   * @param  entry  The configuration entry to use for the initialization.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidConfigs",
        expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testInitializeWithInvalidConfigs(Entry e)
         throws Exception
  {
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(), e);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * within the constraints of the password validator.  Case-sensitivity will
   * not be an issue.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptable2Consecutive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: false");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("password");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "password")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * outside of the constraints of the password validator.  Case-sensitivity
   * will not be an issue.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptable3Consecutive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: false");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("passsword");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "passsword")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * within the constraints of the password validator only because it is
   * configured to operate in a case-sensitive manner.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableCaseSensitive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: true");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("passSword");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "passSword")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * outside of the constraints of the password validator because it is
   * configured to operate in a case-insensitive manner.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableCaseInsensitive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: false");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("passSword");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "passSword")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method when the validator is
   * configured to accept any number of repeated characters.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableUnlimitedRepeats()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 0",
         "ds-cfg-case-sensitive-validation: true");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("aaaaaaaa");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "aaaaaaaa")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the ability of the password validator to change its behavior when
   * the configuration is updated.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableConfigurationChange()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 0",
         "ds-cfg-case-sensitive-validation: true");
    RepeatedCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    RepeatedCharactersPasswordValidator validator =
         new RepeatedCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("aaaaaaaa");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "aaaaaaaa")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    Entry updatedValidatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Repeated Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-repeated-characters-password-validator",
         "cn: Repeated Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "RepeatedCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-maximum-consecutive-length: 2",
         "ds-cfg-case-sensitive-validation: true");
    RepeatedCharactersPasswordValidatorCfg updatedConfiguration =
         AdminTestCaseUtils.getConfiguration(
              RepeatedCharactersPasswordValidatorCfgDefn.getInstance(),
              updatedValidatorEntry);
    ArrayList<String> unacceptableReasons = new ArrayList<String>();
    assertTrue(validator.isConfigurationChangeAcceptable(updatedConfiguration,
                                                         unacceptableReasons),
               String.valueOf(unacceptableReasons));
    ConfigChangeResult changeResult =
         validator.applyConfigurationChange(updatedConfiguration);
    assertEquals(changeResult.getResultCode(), ResultCode.SUCCESS);
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/UniqueCharactersPasswordValidatorTestCase.java
New file
@@ -0,0 +1,699 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.std.meta.
            UniqueCharactersPasswordValidatorCfgDefn;
import org.opends.server.admin.std.server.
            UniqueCharactersPasswordValidatorCfg;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.Attribute;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.Control;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of test cases for the unique characters password validator.
 */
public class UniqueCharactersPasswordValidatorTestCase
       extends ExtensionsTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Retrieves a set of valid configuration entries that may be used to
   * initialize the validator.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "validConfigs")
  public Object[][] getValidConfigs()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: false",
         "",
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: true",
         "",
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 0",
         "ds-cfg-case-sensitive-validation: false");
    Object[][] array = new Object[entries.size()][1];
    for (int i=0; i < array.length; i++)
    {
      array[i] = new Object[] { entries.get(i) };
    }
    return array;
  }
  /**
   * Tests the process of initializing the server with valid configurations.
   *
   * @param  entry  The configuration entry to use for the initialization.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "validConfigs")
  public void testInitializeWithValidConfigs(Entry e)
         throws Exception
  {
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(), e);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    validator.finalizePasswordValidator();
  }
  /**
   * Retrieves a set of invalid configuration entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "invalidConfigs")
  public Object[][] getInvalidConfigs()
         throws Exception
  {
    List<Entry> entries = TestCaseUtils.makeEntries(
         // Missing minimum unique characters
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-case-sensitive-validation: false",
         "",
         // Missing case-sensitive validation
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "",
         // Non-numeric minimum unique characters
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: non-numeric",
         "ds-cfg-case-sensitive-validation: false",
         "",
         // Non-boolean case-sensitive validation
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: non-boolean",
         "",
         // Minimum unique characters out of range.
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: -1",
         "ds-cfg-case-sensitive-validation: false");
    Object[][] array = new Object[entries.size()][1];
    for (int i=0; i < array.length; i++)
    {
      array[i] = new Object[] { entries.get(i) };
    }
    return array;
  }
  /**
   * Tests the process of initializing the server with invalid configurations.
   *
   * @param  entry  The configuration entry to use for the initialization.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "invalidConfigs",
        expectedExceptions = { ConfigException.class,
                               InitializationException.class })
  public void testInitializeWithInvalidConfigs(Entry e)
         throws Exception
  {
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(), e);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * within the constraints of the password validator.  Case-sensitivity will
   * not be an issue.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptable7Unique()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: false");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("password");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "password")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * outside the constraints of the password validator.  Case-sensitivity will
   * not be an issue.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptable4Unique()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: false");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("passw");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "passw")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * within the constraints of the password validator only because it uses
   * case-sensitive validation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableCaseSensitive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: true");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("pasSw");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "pasSw")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method with a password that falls
   * outside the constraints of the password validator because it uses
   * case-insensitive validation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableCaseInsensitive()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: false");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("pasSw");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "pasSw")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the {@code passwordIsAcceptable} method when the validator is
   * configured to accept any number of unique characters.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableAnyNumberOfCharacters()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 0",
         "ds-cfg-case-sensitive-validation: true");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("aaaaaaaa");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "aaaaaaaa")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    validator.finalizePasswordValidator();
  }
  /**
   * Tests the ability of the password validator to change its behavior when
   * the configuration is updated.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsAcceptableConfigurationChange()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    Entry userEntry = TestCaseUtils.makeEntry(
         "dn: uid=test.user,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: test.user",
         "givenName: Test",
         "sn: User",
         "cn: Test User",
         "userPassword: doesntmatter");
    Entry validatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 0",
         "ds-cfg-case-sensitive-validation: true");
    UniqueCharactersPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              validatorEntry);
    UniqueCharactersPasswordValidator validator =
         new UniqueCharactersPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString password = new ASN1OctetString("aaaaaaaa");
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("userpassword", "aaaaaaaa")));
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    ModifyOperation modifyOperation =
         new ModifyOperation(conn, conn.nextOperationID(), conn.nextMessageID(),
                             new ArrayList<Control>(),
                             DN.decode("uid=test.user,o=test"), mods);
    StringBuilder invalidReason = new StringBuilder();
    assertTrue(validator.passwordIsAcceptable(password,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
               invalidReason.toString());
    Entry updatedValidatorEntry = TestCaseUtils.makeEntry(
         "dn: cn=Unique Characters,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-unique-characters-password-validator",
         "cn: Unique Characters",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "UniqueCharactersPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-minimum-unique-characters: 5",
         "ds-cfg-case-sensitive-validation: true");
    UniqueCharactersPasswordValidatorCfg updatedConfiguration =
         AdminTestCaseUtils.getConfiguration(
              UniqueCharactersPasswordValidatorCfgDefn.getInstance(),
              updatedValidatorEntry);
    ArrayList<String> unacceptableReasons = new ArrayList<String>();
    assertTrue(validator.isConfigurationChangeAcceptable(updatedConfiguration,
                                                         unacceptableReasons),
               String.valueOf(unacceptableReasons));
    ConfigChangeResult changeResult =
         validator.applyConfigurationChange(updatedConfiguration);
    assertEquals(changeResult.getResultCode(), ResultCode.SUCCESS);
    assertFalse(validator.passwordIsAcceptable(password,
                               new HashSet<ByteString>(0), modifyOperation,
                               userEntry, invalidReason));
    validator.finalizePasswordValidator();
  }
}