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

neil_a_wilson
06.01.2006 19a4cbeb29fbf1052416728310f062281bceccb4
Update the password validator API in the following ways:

- Make the current password(s) for the user available so that they can be used
in the process of determining whether the new password is acceptable.

- Rename the passwordIsValid method to passwordIsAcceptable to make it clearer
what the intended purpose of the password validator is.

Also, improve test coverage for the password validator classes.
2 files added
9 files modified
1381 ■■■■■ changed files
opends/src/server/org/opends/server/api/PasswordValidator.java 33 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperation.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperation.java 45 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/LengthBasedPasswordValidator.java 87 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java 22 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/resource/config-changes.ldif 16 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/api/PasswordValidatorTestCase.java 866 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java 4 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java 43 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/TestPasswordValidator.java 237 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/PasswordValidator.java
@@ -28,6 +28,8 @@
import java.util.Set;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.Operation;
@@ -98,22 +100,29 @@
   * unacceptable, then a human-readable explanation should be
   * appended to the provided buffer.
   *
   * @param  password       The proposed clear-text password that
   *                        should be validated.
   * @param  operation      The operation that is being used to set
   *                        the password.  It may be an add, a
   *                        modify, or a password modify operation.
   * @param  userEntry      The entry for the user whose password is
   *                        being changed.
   * @param  invalidReason  The buffer to which the human-readable
   *                        explanation should be appended if it is
   *                        determined that the password is not
   *                        acceptable.
   * @param  newPassword       The proposed clear-text password that
   *                           should be validated.
   * @param  currentPasswords  The set of clear-text current passwords
   *                           for the user (if available).  Note that
   *                           the current passwords may not always be
   *                           available, and this may not comprise
   *                           entire set of passwords currently
   *                           for the user.
   * @param  operation         The operation that is being used to set
   *                           the password.  It may be an add, a
   *                           modify, or a password modify operation.
   * @param  userEntry         The entry for the user whose password
   *                           is being changed.
   * @param  invalidReason     The buffer to which the human-readable
   *                           explanation should be appended if it is
   *                           determined that the password is not
   *                           acceptable.
   *
   * @return  <CODE>true</CODE> if the password is acceptable, or
   *          <CODE>false</CODE> if not.
   */
  public abstract boolean passwordIsValid(ByteString password,
  public abstract boolean passwordIsAcceptable(ByteString newPassword,
                               Set<ByteString> currentPasswords,
                               Operation operation,
                               Entry userEntry,
                               StringBuilder invalidReason);
opends/src/server/org/opends/server/core/AddOperation.java
@@ -2298,12 +2298,14 @@
      // validation should be performed for administrators.
      if (! passwordPolicy.skipValidationForAdministrators())
      {
        // There are never any current passwords for an add operation.
        HashSet<ByteString> currentPasswords = new HashSet<ByteString>(0);
        StringBuilder invalidReason = new StringBuilder();
        for (PasswordValidator validator :
             passwordPolicy.getPasswordValidators().values())
        {
          if (! validator.passwordIsValid(value, this, userEntry,
                                          invalidReason))
          if (! validator.passwordIsAcceptable(value, currentPasswords, this,
                                               userEntry, invalidReason))
          {
            int    msgID   = MSGID_PWPOLICY_VALIDATION_FAILED;
            String message = getMessage(msgID, passwordAttribute.getNameOrOID(),
opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -29,6 +29,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -2190,11 +2192,54 @@
          {
            if (newPasswords != null)
            {
              HashSet<ByteString> clearPasswords = new HashSet<ByteString>();
              clearPasswords.addAll(pwPolicyState.getClearPasswords());
              if (currentPasswords != null)
              {
                if (clearPasswords.isEmpty())
                {
                  for (AttributeValue v : currentPasswords)
                  {
                    clearPasswords.add(v.getValue());
                  }
                }
                else
                {
                  // NOTE:  We can't rely on the fact that Set doesn't allow
                  // duplicates because technically it's possible that the
                  // values aren't duplicates if they are ASN.1 elements with
                  // different types (like 0x04 for a standard universal octet
                  // string type versus 0x80 for a simple password in a bind
                  // operation).  So we have to manually check for duplicates.
                  for (AttributeValue v : currentPasswords)
                  {
                    ByteString pw = v.getValue();
                    boolean found = false;
                    for (ByteString s : clearPasswords)
                    {
                      if (Arrays.equals(s.value(), pw.value()))
                      {
                        found = true;
                        break;
                      }
                    }
                    if (! found)
                    {
                      clearPasswords.add(pw);
                    }
                  }
                }
              }
              for (AttributeValue v : newPasswords)
              {
                StringBuilder invalidReason = new StringBuilder();
                if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
                                                         v.getValue(),
                                                         clearPasswords,
                                                         invalidReason))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -36,6 +36,7 @@
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.opends.server.api.AccountStatusNotificationHandler;
import org.opends.server.api.PasswordGenerator;
@@ -3758,17 +3759,22 @@
   * Indicates whether the provided password appears to be acceptable according
   * to the password validators.
   *
   * @param  operation      The operation that provided the password.
   * @param  userEntry      The user entry in which the password is used.
   * @param  password       The password to be validated.
   * @param  invalidReason  A buffer that may be used to hold the invalid reason
   *                        if the password is rejected.
   * @param  operation         The operation that provided the password.
   * @param  userEntry         The user entry in which the password is used.
   * @param  newPassword       The password to be validated.
   * @param  currentPasswords  The set of clear-text current passwords for the
   *                           user (this may be a subset if not all of them are
   *                           available in the clear, or empty if none of them
   *                           are available in the clear).
   * @param  invalidReason     A buffer that may be used to hold the invalid
   *                           reason if the password is rejected.
   *
   * @return  <CODE>true</CODE> if the password is acceptable for use, or
   *          <CODE>false</CODE> if it is not.
   */
  public boolean passwordIsAcceptable(Operation operation, Entry userEntry,
                                      ByteString password,
                                      ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      StringBuilder invalidReason)
  {
    assert debugEnter(CLASS_NAME, "passwordIsAcceptable",
@@ -3780,8 +3786,8 @@
      PasswordValidator validator =
           passwordPolicy.getPasswordValidators().get(validatorDN);
      if (! validator.passwordIsValid(password, operation, userEntry,
                                      invalidReason))
      if (! validator.passwordIsAcceptable(newPassword, currentPasswords,
                                           operation, userEntry, invalidReason))
      {
        if (debug)
        {
opends/src/server/org/opends/server/extensions/LengthBasedPasswordValidator.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.opends.server.api.ConfigurableComponent;
import org.opends.server.api.PasswordValidator;
@@ -98,19 +99,9 @@
  /**
   * Initializes this password validator based on the information in the
   * provided configuration entry.
   *
   * @param  configEntry  The configuration entry that contains the information
   *                      to use to initialize this password validator.
   *
   * @throws  ConfigException  If an unrecoverable problem arises in the
   *                           process of performing the initialization.
   *
   * @throws  InitializationException  If a problem occurs during initialization
   *                                   that is not related to the server
   *                                   configuration.
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordValidator(ConfigEntry configEntry)
         throws ConfigException, InitializationException
  {
@@ -190,9 +181,9 @@
  /**
   * Performs any finalization that might be required when this password
   * validator is unloaded.  No action is taken in the default implementation.
   * {@inheritDoc}
   */
  @Override()
  public void finalizePasswordValidator()
  {
    assert debugEnter(CLASS_NAME, "finalizePasswordValidator");
@@ -203,33 +194,21 @@
  /**
   * Indicates whether the provided password is acceptable for use by the
   * specified user.  If the password is determined to be unacceptable, then a
   * human-readable explanation should be appended to the provided buffer.
   *
   * @param  password       The proposed clear-text password that should be
   *                        validated.
   * @param  operation      The operation that is being used to set the
   *                        password.
   * @param  userEntry      The entry for the user whose password is being
   *                        changed.
   * @param  invalidReason  The buffer to which the human-readable explanation
   *                        should be appended if it is determined that the
   *                        password is not acceptable.
   *
   * @return  <CODE>true</CODE> if the password is acceptable, or
   *          <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean passwordIsValid(ByteString password, Operation operation,
                                 Entry userEntry, StringBuilder invalidReason)
  @Override()
  public boolean passwordIsAcceptable(ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      Operation operation, Entry userEntry,
                                      StringBuilder invalidReason)
  {
    assert debugEnter(CLASS_NAME, "passwordIsValid",
    assert debugEnter(CLASS_NAME, "passwordIsAcceptable",
                      "org.opends.server.protocols.asn1.ASN1OctetString",
                      String.valueOf(operation), String.valueOf(userEntry),
                      "java.lang.StringBuilder");
    int numChars = password.stringValue().length();
    int numChars = newPassword.stringValue().length();
    if ((minLength > 0) && (numChars < minLength))
    {
@@ -251,11 +230,7 @@
  /**
   * Retrieves the DN of the configuration entry with which this component is
   * associated.
   *
   * @return  The DN of the configuration entry with which this component is
   *          associated.
   * {@inheritDoc}
   */
  public DN getConfigurableComponentEntryDN()
  {
@@ -267,11 +242,7 @@
  /**
   * Retrieves the set of configuration attributes that are associated with this
   * configurable component.
   *
   * @return  The set of configuration attributes that are associated with this
   *          configurable component.
   * {@inheritDoc}
   */
  public List<ConfigAttribute> getConfigurationAttributes()
  {
@@ -296,18 +267,7 @@
  /**
   * Indicates whether the provided configuration entry has an acceptable
   * configuration for this component.  If it does not, then detailed
   * information about the problem(s) should be added to the provided list.
   *
   * @param  configEntry          The configuration entry for which to make the
   *                              determination.
   * @param  unacceptableReasons  A list that can be used to hold messages about
   *                              why the provided entry does not have an
   *                              acceptable configuration.
   *
   * @return  <CODE>true</CODE> if the provided entry has an acceptable
   *          configuration for this component, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean hasAcceptableConfiguration(ConfigEntry configEntry,
                                            List<String> unacceptableReasons)
@@ -387,20 +347,7 @@
  /**
   * Makes a best-effort attempt to apply the configuration contained in the
   * provided entry.  Information about the result of this processing should be
   * added to the provided message list.  Information should always be added to
   * this list if a configuration change could not be applied.  If detailed
   * results are requested, then information about the changes applied
   * successfully (and optionally about parameters that were not changed) should
   * also be included.
   *
   * @param  configEntry      The entry containing the new configuration to
   *                          apply for this component.
   * @param  detailedResults  Indicates whether detailed information about the
   *                          processing should be added to the list.
   *
   * @return  Information about the result of the configuration update.
   * {@inheritDoc}
   */
  public ConfigChangeResult applyNewConfiguration(ConfigEntry configEntry,
                                                  boolean detailedResults)
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -29,9 +29,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.HashSet;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ClientConnection;
@@ -685,9 +687,29 @@
        {
          if (selfChange || (! pwPolicyState.skipValidationForAdministrators()))
          {
            HashSet<ByteString> clearPasswords;
            if (oldPassword == null)
            {
              clearPasswords =
                   new HashSet<ByteString>(pwPolicyState.getClearPasswords());
            }
            else
            {
              clearPasswords = new HashSet<ByteString>();
              clearPasswords.add(oldPassword);
              for (ByteString pw : pwPolicyState.getClearPasswords())
              {
                if (! Arrays.equals(pw.value(), oldPassword.value()))
                {
                  clearPasswords.add(pw);
                }
              }
            }
            StringBuilder invalidReason = new StringBuilder();
            if (! pwPolicyState.passwordIsAcceptable(operation, userEntry,
                                                     newPassword,
                                                     clearPasswords,
                                                     invalidReason))
            {
              if (oldPassword == null)
opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -21,6 +21,19 @@
replace: ds-cfg-suppress-internal-operations
ds-cfg-suppress-internal-operations: false
dn: cn=Test Password Validator,cn=Password Validators,cn=config
changetype: add
objectClass: top
objectClass: ds-cfg-password-validator
cn: Test Password Validator
ds-cfg-password-validator-class: org.opends.server.extensions.TestPasswordValidator
ds-cfg-password-validator-enabled: true
dn: cn=Default Password Policy,cn=Password Policies,cn=config
changetype: modify
add: ds-cfg-password-validator-dn
ds-cfg-password-validator-dn: cn=Test Password Validator,cn=Password Validators,cn=config
dn: cn=SSHA512 UserPassword Policy,cn=Password Policies,cn=config
changetype: add
objectClass: top
@@ -49,6 +62,7 @@
ds-cfg-require-secure-authentication: false
ds-cfg-require-secure-password-changes: false
ds-cfg-skip-validation-for-administrators: false
ds-cfg-password-validator-dn: cn=Test Password Validator,cn=Password Validators,cn=config
dn: cn=SHA1 AuthPassword Policy,cn=Password Policies,cn=config
changetype: add
@@ -78,6 +92,7 @@
ds-cfg-require-secure-authentication: false
ds-cfg-require-secure-password-changes: false
ds-cfg-skip-validation-for-administrators: false
ds-cfg-password-validator-dn: cn=Test Password Validator,cn=Password Validators,cn=config
dn: cn=Clear UserPassword Policy,cn=Password Policies,cn=config
changetype: add
@@ -107,6 +122,7 @@
ds-cfg-require-secure-authentication: false
ds-cfg-require-secure-password-changes: false
ds-cfg-skip-validation-for-administrators: false
ds-cfg-password-validator-dn: cn=Test Password Validator,cn=Password Validators,cn=config
dn: cn=Delay PreOperation Plugin,cn=Plugins,cn=config
changetype: add
opends/tests/unit-tests-testng/src/server/org/opends/server/api/PasswordValidatorTestCase.java
New file
@@ -0,0 +1,866 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Set;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.AddOperation;
import org.opends.server.extensions.TestPasswordValidator;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.asn1.ASN1Writer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.BindRequestProtocolOp;
import org.opends.server.protocols.ldap.BindResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
import org.opends.server.tools.LDAPPasswordModify;
import org.opends.server.types.Attribute;
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import static org.testng.Assert.*;
/**
 * A set of generic test cases for password validators.
 */
public class PasswordValidatorTestCase
       extends APITestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Gets simple test coverage for the default
   * PasswordValidator.finalizePasswordValidator method.
   */
  @Test()
  public void testFinalizePasswordValidator()
  {
    TestPasswordValidator.getInstance().finalizePasswordValidator();
  }
  /**
   * Performs a test to ensure that the password validation will be successful
   * under the base conditions for the password modify extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulValidationPasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-c", "password",
      "-n", "newPassword"
    };
    assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null, null),
                 0);
    assertEquals(TestPasswordValidator.getLastNewPassword().value(),
                 new ASN1OctetString("newPassword").value());
    assertFalse(TestPasswordValidator.getLastCurrentPasswords().isEmpty());
  }
  /**
   * Performs a test to ensure that the password validation will fail if the
   * test validator is configured to make it fail for the password modify
   * extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedValidationPasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    TestPasswordValidator.setNextReturnValue(false);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-c", "password",
      "-n", "newPassword"
    };
    int returnCode = LDAPPasswordModify.mainPasswordModify(args, false, null,
                                                           null);
    assertFalse(returnCode == 0);
    assertEquals(TestPasswordValidator.getLastNewPassword().value(),
                 new ASN1OctetString("newPassword").value());
    assertFalse(TestPasswordValidator.getLastCurrentPasswords().isEmpty());
    TestPasswordValidator.setNextReturnValue(true);
  }
  /**
   * Performs a test to make sure that the clear-text password will not be
   * provided if the user has a non-reversible scheme and does not provide the
   * current password for a password modify extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCurrentPasswordNotAvailablePasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-n", "newPassword"
    };
    assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null, null),
                 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertTrue(currentPasswords.isEmpty());
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a non-reversible scheme but provides the current password
   * for a password modify extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCurrentPasswordAvailablePasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-c", "password",
      "-n", "newPassword"
    };
    assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null, null),
                 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a reversible scheme and does not provide the current
   * password for a password modify extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStoredPasswordAvailablePasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password",
         "pwdPolicySubentry: cn=Clear UserPassword Policy,cn=Password " +
              "Policies,cn=config");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-n", "newPassword"
    };
    assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null, null),
                 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a reversible scheme and also provides the current password
   * for a password modify extended operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStoredAndCurrentPasswordAvailablePasswordModifyExtOp()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password",
         "pwdPolicySubentry: cn=Clear UserPassword Policy,cn=Password " +
              "Policies,cn=config");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "uid=test.user,o=test",
      "-w", "password",
      "-c", "password",
      "-n", "newPassword"
    };
    assertEquals(LDAPPasswordModify.mainPasswordModify(args, false, null, null),
                 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
  /**
   * Performs a test to ensure that the password validation will be successful
   * under the base conditions for the modify operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testSuccessfulValidationModify()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Socket s = new Socket("127.0.0.1", (int) TestCaseUtils.getServerLdapPort());
    ASN1Reader r = new ASN1Reader(s);
    ASN1Writer w = new ASN1Writer(s);
    r.setIOTimeout(3000);
    BindRequestProtocolOp bindRequest =
      new BindRequestProtocolOp(
               new ASN1OctetString("uid=test.user,o=test"),
                                3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>();
    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("newPassword"));
    LDAPAttribute attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.REPLACE, attr));
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(
                  new ASN1OctetString("uid=test.user,o=test"), mods);
    message = new LDAPMessage(2, modifyRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    ModifyResponseProtocolOp modifyResponse =
         message.getModifyResponseProtocolOp();
    assertEquals(modifyResponse.getResultCode(), 0);
    assertEquals(TestPasswordValidator.getLastNewPassword().value(),
                 new ASN1OctetString("newPassword").value());
    assertTrue(TestPasswordValidator.getLastCurrentPasswords().isEmpty());
  }
  /**
   * Performs a test to ensure that the password validation will fail if the
   * test validator is configured to make it fail for the modify operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testFailedValidationModify()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Socket s = new Socket("127.0.0.1", (int) TestCaseUtils.getServerLdapPort());
    ASN1Reader r = new ASN1Reader(s);
    ASN1Writer w = new ASN1Writer(s);
    r.setIOTimeout(3000);
    BindRequestProtocolOp bindRequest =
      new BindRequestProtocolOp(
               new ASN1OctetString("uid=test.user,o=test"),
                                3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>();
    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("newPassword"));
    LDAPAttribute attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.REPLACE, attr));
    TestPasswordValidator.setNextReturnValue(false);
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(
                  new ASN1OctetString("uid=test.user,o=test"), mods);
    message = new LDAPMessage(2, modifyRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    ModifyResponseProtocolOp modifyResponse =
         message.getModifyResponseProtocolOp();
    assertFalse(modifyResponse.getResultCode() == 0);
    assertEquals(TestPasswordValidator.getLastNewPassword().value(),
                 new ASN1OctetString("newPassword").value());
    assertTrue(TestPasswordValidator.getLastCurrentPasswords().isEmpty());
    TestPasswordValidator.setNextReturnValue(true);
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a non-reversible scheme but provides the current password
   * for a modify operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testCurrentPasswordAvailableModify()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Socket s = new Socket("127.0.0.1", (int) TestCaseUtils.getServerLdapPort());
    ASN1Reader r = new ASN1Reader(s);
    ASN1Writer w = new ASN1Writer(s);
    r.setIOTimeout(3000);
    BindRequestProtocolOp bindRequest =
      new BindRequestProtocolOp(
               new ASN1OctetString("uid=test.user,o=test"),
                                3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>();
    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("password"));
    LDAPAttribute attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.DELETE, attr));
    values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("newPassword"));
    attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.ADD, attr));
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(
                  new ASN1OctetString("uid=test.user,o=test"), mods);
    message = new LDAPMessage(2, modifyRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    ModifyResponseProtocolOp modifyResponse =
         message.getModifyResponseProtocolOp();
    assertEquals(modifyResponse.getResultCode(), 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a reversible scheme and does not provide the current
   * password for a modify operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStoredPasswordAvailableModify()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password",
         "pwdPolicySubentry: cn=Clear UserPassword Policy,cn=Password " +
              "Policies,cn=config");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Socket s = new Socket("127.0.0.1", (int) TestCaseUtils.getServerLdapPort());
    ASN1Reader r = new ASN1Reader(s);
    ASN1Writer w = new ASN1Writer(s);
    r.setIOTimeout(3000);
    BindRequestProtocolOp bindRequest =
      new BindRequestProtocolOp(
               new ASN1OctetString("uid=test.user,o=test"),
                                3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>();
    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("newPassword"));
    LDAPAttribute attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.REPLACE, attr));
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(
                  new ASN1OctetString("uid=test.user,o=test"), mods);
    message = new LDAPMessage(2, modifyRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    ModifyResponseProtocolOp modifyResponse =
         message.getModifyResponseProtocolOp();
    assertEquals(modifyResponse.getResultCode(), 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
  /**
   * Performs a test to make sure that the clear-text password will be provided
   * if the user has a reversible scheme and also provides the current password
   * for a modify operation.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testStoredAndCurrentPasswordAvailableModify()
         throws Exception
  {
    TestPasswordValidator.setNextReturnValue(true);
    TestPasswordValidator.setNextInvalidReason(null);
    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: password",
         "pwdPolicySubentry: cn=Clear UserPassword Policy,cn=Password " +
              "Policies,cn=config");
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(userEntry.getDN(), userEntry.getObjectClasses(),
                         userEntry.getUserAttributes(),
                         userEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Socket s = new Socket("127.0.0.1", (int) TestCaseUtils.getServerLdapPort());
    ASN1Reader r = new ASN1Reader(s);
    ASN1Writer w = new ASN1Writer(s);
    r.setIOTimeout(3000);
    BindRequestProtocolOp bindRequest =
      new BindRequestProtocolOp(
               new ASN1OctetString("uid=test.user,o=test"),
                                3, new ASN1OctetString("password"));
    LDAPMessage message = new LDAPMessage(1, bindRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    BindResponseProtocolOp bindResponse = message.getBindResponseProtocolOp();
    assertEquals(bindResponse.getResultCode(), 0);
    ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>();
    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("password"));
    LDAPAttribute attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.DELETE, attr));
    values = new ArrayList<ASN1OctetString>();
    values.add(new ASN1OctetString("newPassword"));
    attr = new LDAPAttribute("userPassword", values);
    mods.add(new LDAPModification(ModificationType.ADD, attr));
    ModifyRequestProtocolOp modifyRequest =
         new ModifyRequestProtocolOp(
                  new ASN1OctetString("uid=test.user,o=test"), mods);
    message = new LDAPMessage(2, modifyRequest);
    w.writeElement(message.encode());
    message = LDAPMessage.decode(r.readElement().decodeAsSequence());
    ModifyResponseProtocolOp modifyResponse =
         message.getModifyResponseProtocolOp();
    assertEquals(modifyResponse.getResultCode(), 0);
    Set<ByteString> currentPasswords =
         TestPasswordValidator.getLastCurrentPasswords();
    assertFalse(currentPasswords.isEmpty());
    assertEquals(currentPasswords.size(), 1);
    assertEquals(currentPasswords.iterator().next().value(),
                 new ASN1OctetString("password").value());
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/core/PasswordPolicyTestCase.java
@@ -2522,7 +2522,7 @@
  {
    PasswordPolicy p = DirectoryServer.getDefaultPasswordPolicy();
    assertNotNull(p.getPasswordValidators());
    assertTrue(p.getPasswordValidators().isEmpty());
    assertFalse(p.getPasswordValidators().isEmpty());
    String dnStr = "cn=Default Password Policy,cn=Password Policies,cn=config";
    String attr  = "ds-cfg-password-validator-dn";
@@ -2567,7 +2567,7 @@
                      "cn=config");
    PasswordPolicy p = DirectoryServer.getPasswordPolicy(dn);
    assertNotNull(p.getPasswordValidators());
    assertTrue(p.getPasswordValidators().isEmpty());
    assertFalse(p.getPasswordValidators().isEmpty());
    String attr  = "ds-cfg-password-validator-dn";
    String valDN = "cn=Length-Based Password Validator," +
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LengthBasedPasswordValidatorTestCase.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.testng.annotations.BeforeClass;
@@ -43,6 +44,7 @@
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.Control;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
@@ -312,13 +314,13 @@
  /**
   * Tests the <CODE>passwordIsValid</CODE> method with no constraints on
   * Tests the <CODE>passwordIsAcceptable</CODE> method with no constraints on
   * password length.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsValidNoConstraints()
  public void testPasswordIsAcceptableNoConstraints()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
@@ -374,8 +376,9 @@
                               DN.decode("cn=uid=test.user,o=test"), mods);
      StringBuilder invalidReason = new StringBuilder();
      assertTrue(validator.passwordIsValid(password, op, userEntry,
                                           invalidReason));
      assertTrue(validator.passwordIsAcceptable(password,
                                                new HashSet<ByteString>(0),
                                                op, userEntry, invalidReason));
    }
    validator.finalizePasswordValidator();
@@ -384,13 +387,13 @@
  /**
   * Tests the <CODE>passwordIsValid</CODE> method with a constraint on the
   * Tests the <CODE>passwordIsAcceptable</CODE> method with a constraint on the
   * minimum password length.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsValidMinLengthConstraint()
  public void testPasswordIsAcceptableMinLengthConstraint()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
@@ -447,8 +450,10 @@
      StringBuilder invalidReason = new StringBuilder();
      assertEquals((buffer.length() >= 10),
                   validator.passwordIsValid(password, op, userEntry,
                                             invalidReason));
                   validator.passwordIsAcceptable(password,
                                                  new HashSet<ByteString>(0),
                                                  op, userEntry,
                                                  invalidReason));
    }
    validator.finalizePasswordValidator();
@@ -457,13 +462,13 @@
  /**
   * Tests the <CODE>passwordIsValid</CODE> method with a constraint on the
   * Tests the <CODE>passwordIsAcceptable</CODE> method with a constraint on the
   * maximum password length.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsValidMaxLengthConstraint()
  public void testPasswordIsAcceptableMaxLengthConstraint()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
@@ -520,8 +525,10 @@
      StringBuilder invalidReason = new StringBuilder();
      assertEquals((buffer.length() <= 10),
                   validator.passwordIsValid(password, op, userEntry,
                                             invalidReason));
                   validator.passwordIsAcceptable(password,
                                                  new HashSet<ByteString>(0),
                                                  op, userEntry,
                                                  invalidReason));
    }
    validator.finalizePasswordValidator();
@@ -530,13 +537,13 @@
  /**
   * Tests the <CODE>passwordIsValid</CODE> method with constraints on both the
   * minimum and maximum password length.
   * Tests the <CODE>passwordIsAcceptable</CODE> method with constraints on both
   * the minimum and maximum password length.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testPasswordIsValidMinAndMaxLengthConstraints()
  public void testPasswordIsAcceptableMinAndMaxLengthConstraints()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
@@ -593,8 +600,10 @@
      StringBuilder invalidReason = new StringBuilder();
      assertEquals(((buffer.length() >= 6) && (buffer.length() <= 10)),
                   validator.passwordIsValid(password, op, userEntry,
                                             invalidReason));
                   validator.passwordIsAcceptable(password,
                                                  new HashSet<ByteString>(0),
                                                  op, userEntry,
                                                  invalidReason));
    }
    validator.finalizePasswordValidator();
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/TestPasswordValidator.java
New file
@@ -0,0 +1,237 @@
/*
 * 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 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.util.Set;
import org.opends.server.api.PasswordValidator;
import org.opends.server.config.ConfigEntry;
import org.opends.server.core.Operation;
import org.opends.server.types.ByteString;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
/**
 * This class provides a very simple password validator that can be used for
 * testing purposes.  It provides the ability to inspect the arguments provided
 * to the password validator, as well as to manipulate the result that it will
 * return.
 */
public class TestPasswordValidator
       extends PasswordValidator
{
  /**
   * The singleton instance of this test password validator.
   */
  private static TestPasswordValidator instance = null;
  // The next value to return from the passwordIsAcceptable method.
  private boolean nextReturnValue;
  // The last new password provided to the passwordIsAcceptable method.
  private ByteString lastNewPassword;
  // The last user entry provided to the passwordIsAcceptable method.
  private Entry lastUserEntry;
  // The last operation provided to the passwordIsAcceptable method.
  private Operation lastOperation;
  // The last set of current passwords provided to the passwordIsAcceptable
  // method.
  private Set<ByteString> lastCurrentPasswords;
  // The next invalid reason that should be used in the passwordIsAcceptable
  // method.
  private String nextInvalidReason;
  /**
   * Creates a new instance of this password validator.
   */
  public TestPasswordValidator()
  {
    super();
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordValidator(ConfigEntry configEntry)
         throws InitializationException
  {
    if (instance == null)
    {
      instance = this;
    }
    else
    {
      throw new InitializationException(1,
           "Cannot configure more than one TestPasswordValidator instance");
    }
    lastNewPassword      = null;
    lastCurrentPasswords = null;
    lastOperation        = null;
    lastUserEntry        = null;
    nextReturnValue      = true;
    nextInvalidReason    = null;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean passwordIsAcceptable(ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      Operation operation, Entry userEntry,
                                      StringBuilder invalidReason)
  {
    lastNewPassword      = newPassword;
    lastCurrentPasswords = currentPasswords;
    lastOperation        = operation;
    lastUserEntry        = userEntry;
    if (nextInvalidReason != null)
    {
      invalidReason.append(nextInvalidReason);
    }
    return nextReturnValue;
  }
  /**
   * Retrieves an instance of this test password validator.
   *
   * @return  An instance of this test password validator, or <CODE>null</CODE>
   *          if no instance has been created.
   */
  public static TestPasswordValidator getInstance()
  {
    return instance;
  }
  /**
   * Retrieves the last <CODE>newPassword</CODE> value provided to the
   * <CODE>passwordIsAcceptable</CODE> method.
   *
   * @return  The last <CODE>newPassword</CODE> value provided to the
   *          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static ByteString getLastNewPassword()
  {
    return instance.lastNewPassword;
  }
  /**
   * Retrieves the last <CODE>currentPasswords</CODE> value provided to the
   * <CODE>passwordIsAcceptable</CODE> method.
   *
   * @return  The last <CODE>currentPasswords</CODE> value provided to the
   *          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static Set<ByteString> getLastCurrentPasswords()
  {
    return instance.lastCurrentPasswords;
  }
  /**
   * Retrieves the last <CODE>operation</CODE> value provided to the
   * <CODE>passwordIsAcceptable</CODE> method.
   *
   * @return  The last <CODE>operation</CODE> value provided to the
   *          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static Operation getLastOperation()
  {
    return instance.lastOperation;
  }
  /**
   * Retrieves the last <CODE>userEntry</CODE> value provided to the
   * <CODE>passwordIsAcceptable</CODE> method.
   *
   * @return  The last <CODE>userEntry</CODE> value provided to the
   *          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static Entry getLastUserEntry()
  {
    return instance.lastUserEntry;
  }
  /**
   * Sets the next value to return from the <CODE>passwordIsAcceptable</CODE>
   * method.
   *
   * @param  nextReturnValue  The next value to return from the
   *                          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static void setNextReturnValue(boolean nextReturnValue)
  {
    instance.nextReturnValue = nextReturnValue;
  }
  /**
   * Sets the next string to append to the <CODE>invalidReason</CODE> buffer in
   * the <CODE>passwordIsAcceptable</CODE> method.
   *
   * @param  nextReturnValue  The next string to append to the
   *                          <CODE>invalidReason</CODE> buffer in the
   *                          <CODE>passwordIsAcceptable</CODE> method.
   */
  public static void setNextInvalidReason(String nextInvalidReason)
  {
    instance.nextInvalidReason = nextInvalidReason;
  }
}