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

neil_a_wilson
11.07.2007 272e306217cfa3e394574d9a1a4e69ff9e3a9600
Add a new password validator that can be used to require that passwords have
a specified number of characters from various user-defined character sets. It
is also possible to control whether passwords will be allowed to contain
characters outside of any defined character set.

OpenDS Issue Number: 337
3 files added
3 files modified
1010 ■■■■■ changed files
opends/resource/config/config.ldif 13 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 12 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml 64 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java 309 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/LDIFReader.java 60 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java 552 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1097,6 +1097,19 @@
ds-cfg-password-validator-enabled: true
ds-cfg-test-reversed-password: true
dn: cn=Character Set,cn=Password Validators,cn=config
objectClass: top
objectClass: ds-cfg-password-validator
objectClass: ds-cfg-character-set-password-validator
cn: Character Set
ds-cfg-password-validator-class: org.opends.server.extensions.CharacterSetPasswordValidator
ds-cfg-password-validator-enabled: true
ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz
ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ
ds-cfg-character-set: 1:0123456789
ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?
ds-cfg-allow-unclassified-characters: true
dn: cn=Dictionary,cn=Password Validators,cn=config
objectClass: top
objectClass: ds-cfg-password-validator
opends/resource/schema/02-config.ldif
@@ -1131,6 +1131,13 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.337
  NAME 'ds-cfg-test-reversed-password' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.338
  NAME 'ds-cfg-character-set' EQUALITY caseExactMatch
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.339
  NAME 'ds-cfg-allow-unclassified-characters'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.340
  NAME 'ds-task-rebuild-base-dn'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE
@@ -1586,9 +1593,14 @@
  NAME 'ds-cfg-attribute-value-password-validator'
  SUP ds-cfg-password-validator STRUCTURAL MUST ds-cfg-test-reversed-password
  MAY ds-cfg-match-attribute X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.97
  NAME 'ds-cfg-character-set-password-validator' SUP ds-cfg-password-validator
  STRUCTURAL MUST ( ds-cfg-character-set $
  ds-cfg-allow-unclassified-characters ) X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.98
  NAME 'ds-task-rebuild' SUP ds-task
  MUST ( ds-task-rebuild-base-dn $ ds-task-rebuild-index )
  MAY ( ds-task-rebuild-max-threads )
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml
New file
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<adm:managed-object name="character-set-password-validator"
plural-name="character-set-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 by
    determining whether it contains a sufficient number of characters from one
    or more user-defined character sets (e.g., passwords must have at least
    one lowercase letter, one uppercase letter, one digit, and one symbol).
  </adm:synopsis>
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:oid>1.3.6.1.4.1.26027.1.2.97</ldap:oid>
      <ldap:name>ds-cfg-character-set-password-validator</ldap:name>
      <ldap:superior>ds-cfg-password-validator</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:property name="character-set" mandatory="true" multi-valued="true">
    <adm:synopsis>
      Specifies a character set containing characters that a password may
      contain and a value indicating the minimum number of characters required
      from that set.  The value must be an integer (indicating the minimum
      required characters from the set) followed by a colon and the characters
      to include in that set (e.g., "3:abcdefghijklmnopqrstuvwxyz" indicates
      that a user password must contain at least three characters from the set
      of lowercase ASCII letters).  Multiple character sets may be defined in
      separate values, although no character may appear in more than one
      character set.
    </adm:synopsis>
    <adm:syntax>
      <adm:string case-insensitive="false" />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.338</ldap:oid>
        <ldap:name>ds-cfg-character-set</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
  <adm:property name="allow-unclassified-characters" mandatory="true">
    <adm:synopsis>
      Indicates whether this password validator allows passwords to contain
      characters outside of any of the user-defined character sets.  If this is
      "false", then only those characters in the user-defined character sets
      may be used in passwords.
    </adm:synopsis>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.339</ldap:oid>
        <ldap:name>ds-cfg-allow-unclassified-characters</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java
New file
@@ -0,0 +1,309 @@
/*
 * 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.HashMap;
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.CharacterSetPasswordValidatorCfg;
import org.opends.server.api.PasswordValidator;
import org.opends.server.config.ConfigException;
import org.opends.server.core.Operation;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryConfig;
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.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides an OpenDS password validator that may be used to ensure
 * that proposed passwords contain at least a specified number of characters
 * from one or more user-defined character sets.
 */
public class CharacterSetPasswordValidator
       extends PasswordValidator<CharacterSetPasswordValidatorCfg>
       implements ConfigurationChangeListener<CharacterSetPasswordValidatorCfg>
{
  // The current configuration for this password validator.
  private CharacterSetPasswordValidatorCfg currentConfig;
  // A mapping between the character sets and the minimum number of characters
  // required for each.
  private HashMap<String,Integer> characterSets;
  /**
   * Creates a new instance of this character set password validator.
   */
  public CharacterSetPasswordValidator()
  {
    super();
    // No implementation is required here.  All initialization should be
    // performed in the initializePasswordValidator() method.
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void initializePasswordValidator(
                   CharacterSetPasswordValidatorCfg configuration)
         throws ConfigException
  {
    configuration.addCharacterSetChangeListener(this);
    currentConfig = configuration;
    // Make sure that each of the character set definitions are acceptable.
    characterSets = processCharacterSets(configuration);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizePasswordValidator()
  {
    currentConfig.removeCharacterSetChangeListener(this);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public boolean passwordIsAcceptable(ByteString newPassword,
                                      Set<ByteString> currentPasswords,
                                      Operation operation, Entry userEntry,
                                      StringBuilder invalidReason)
  {
    // Get a handle to the current configuration.
    CharacterSetPasswordValidatorCfg config = currentConfig;
    HashMap<String,Integer> characterSets = this.characterSets;
    // Process the provided password.
    String password = newPassword.stringValue();
    HashMap<String,Integer> counts = new HashMap<String,Integer>();
    for (int i=0; i < password.length(); i++)
    {
      char c = password.charAt(i);
      boolean found = false;
      for (String characterSet : characterSets.keySet())
      {
        if (characterSet.indexOf(c) >= 0)
        {
          Integer count = counts.get(characterSet);
          if (count == null)
          {
            counts.put(characterSet, 1);
          }
          else
          {
            counts.put(characterSet, count+1);
          }
          found = true;
          break;
        }
      }
      if ((! found) && (! config.isAllowUnclassifiedCharacters()))
      {
        int msgID = MSGID_CHARSET_VALIDATOR_ILLEGAL_CHARACTER;
        invalidReason.append(getMessage(msgID, String.valueOf(c)));
        return false;
      }
    }
    for (String characterSet : characterSets.keySet())
    {
      int minimumCount = characterSets.get(characterSet);
      Integer passwordCount = counts.get(characterSet);
      if ((passwordCount == null) || (passwordCount < minimumCount))
      {
        int msgID = MSGID_CHARSET_VALIDATOR_TOO_FEW_CHARS_FROM_SET;
        invalidReason.append(getMessage(msgID, characterSet, minimumCount));
        return false;
      }
    }
    // If we've gotten here, then the password is acceptable.
    return true;
  }
  /**
   * Parses the provided configuration and extracts the character set
   * definitions and associated minimum counts from them.
   *
   * @param  configuration  the configuration for this password validator.
   *
   * @return  The mapping between strings of character set values and the
   *          minimum number of characters required from those sets.
   *
   * @throws  ConfigException  If any of the character set definitions cannot be
   *                           parsed, or if there are any characters present in
   *                           multiple sets.
   */
  private HashMap<String,Integer>
               processCharacterSets(
                    CharacterSetPasswordValidatorCfg configuration)
          throws ConfigException
  {
    HashMap<String,Integer> characterSets  = new HashMap<String,Integer>();
    HashSet<Character>      usedCharacters = new HashSet<Character>();
    for (String definition : configuration.getCharacterSet())
    {
      int colonPos = definition.indexOf(':');
      if (colonPos <= 0)
      {
        int    msgID   = MSGID_CHARSET_VALIDATOR_NO_COLON;
        String message = getMessage(msgID, definition);
        throw new ConfigException(msgID, message);
      }
      else if (colonPos == (definition.length() - 1))
      {
        int    msgID   = MSGID_CHARSET_VALIDATOR_NO_CHARS;
        String message = getMessage(msgID, definition);
        throw new ConfigException(msgID, message);
      }
      int minCount;
      try
      {
        minCount = Integer.parseInt(definition.substring(0, colonPos));
      }
      catch (Exception e)
      {
        int    msgID   = MSGID_CHARSET_VALIDATOR_INVALID_COUNT;
        String message = getMessage(msgID, definition);
        throw new ConfigException(msgID, message);
      }
      if (minCount <= 0)
      {
        int    msgID   = MSGID_CHARSET_VALIDATOR_INVALID_COUNT;
        String message = getMessage(msgID, definition);
        throw new ConfigException(msgID, message);
      }
      String characterSet = definition.substring(colonPos+1);
      for (int i=0; i < characterSet.length(); i++)
      {
        char c = characterSet.charAt(i);
        if (usedCharacters.contains(c))
        {
          int    msgID   = MSGID_CHARSET_VALIDATOR_DUPLICATE_CHAR;
          String message = getMessage(msgID, definition, String.valueOf(c));
          throw new ConfigException(msgID, message);
        }
        usedCharacters.add(c);
      }
      characterSets.put(characterSet, minCount);
    }
    return characterSets;
  }
  /**
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      CharacterSetPasswordValidatorCfg configuration,
                      List<String> unacceptableReasons)
  {
    // Make sure that we can process the defined character sets.  If so, then
    // we'll accept the new configuration.
    try
    {
      processCharacterSets(configuration);
    }
    catch (ConfigException ce)
    {
      unacceptableReasons.add(ce.getMessage());
      return false;
    }
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
                      CharacterSetPasswordValidatorCfg configuration)
  {
    ResultCode        resultCode          = ResultCode.SUCCESS;
    boolean           adminActionRequired = false;
    ArrayList<String> messages            = new ArrayList<String>();
    // Make sure that we can process the defined character sets.  If so, then
    // activate the new configuration.
    try
    {
      characterSets = processCharacterSets(configuration);
      currentConfig = configuration;
    }
    catch (Exception e)
    {
      resultCode = DirectoryConfig.getServerErrorResultCode();
      messages.add(stackTraceToSingleLineString(e));
    }
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
}
opends/src/server/org/opends/server/util/LDIFReader.java
@@ -79,9 +79,6 @@
 */
public final class LDIFReader
{
  // The reader that will be used to read the data.
  private BufferedReader reader;
@@ -243,7 +240,7 @@
        for (StringBuilder line : lines)
        {
          readAttribute(lines, line, entryDN, objectClasses, userAttributes,
                        operationalAttributes);
                        operationalAttributes, checkSchema);
        }
      }
      catch (LDIFException e)
@@ -814,6 +811,8 @@
   *                                for the current entry.
   * @param  operationalAttributes  The set of operational attributes decoded so
   *                                far for the current entry.
   * @param  checkSchema            Indicates whether to perform schema
   *                                validation for the attribute.
   *
   * @throws  LDIFException  If a problem occurs while trying to decode the
   *                         attribute contained in the provided entry.
@@ -822,7 +821,8 @@
       StringBuilder line, DN entryDN,
       HashMap<ObjectClass,String> objectClasses,
       HashMap<AttributeType,List<Attribute>> userAttributes,
       HashMap<AttributeType,List<Attribute>> operationalAttributes)
       HashMap<AttributeType,List<Attribute>> operationalAttributes,
       boolean checkSchema)
          throws LDIFException
  {
    // Parse the attribute type description.
@@ -934,14 +934,40 @@
          LinkedHashSet<AttributeValue> valueSet = a.getValues();
          if (valueSet.contains(attributeValue))
          {
            int    msgID   = MSGID_LDIF_DUPLICATE_ATTR;
            String message = getMessage(msgID, String.valueOf(entryDN),
                                        lastEntryLineNumber, attrName,
                                        value.stringValue());
            logToRejectWriter(lines, message);
            throw new LDIFException(msgID, message, lastEntryLineNumber, true);
            if (! checkSchema)
            {
              // If we're not doing schema checking, then it is possible that
              // the attribute type should use case-sensitive matching and the
              // values differ in capitalization.  Only reject the proposed
              // value if we find another value that is exactly the same as the
              // one that was provided.
              for (AttributeValue v : valueSet)
              {
                if (v.getValue().equals(attributeValue.getValue()))
                {
                  int    msgID   = MSGID_LDIF_DUPLICATE_ATTR;
                  String message = getMessage(msgID, String.valueOf(entryDN),
                                              lastEntryLineNumber, attrName,
                                              value.stringValue());
                  logToRejectWriter(lines, message);
                  throw new LDIFException(msgID, message, lastEntryLineNumber,
                                          true);
                }
              }
            }
            else
            {
              int    msgID   = MSGID_LDIF_DUPLICATE_ATTR;
              String message = getMessage(msgID, String.valueOf(entryDN),
                                          lastEntryLineNumber, attrName,
                                          value.stringValue());
              logToRejectWriter(lines, message);
              throw new LDIFException(msgID, message, lastEntryLineNumber,
                                      true);
            }
          }
          else if (attrType.isSingleValue() && (! valueSet.isEmpty()))
          if (attrType.isSingleValue() && (! valueSet.isEmpty()) && checkSchema)
          {
            int    msgID   = MSGID_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR;
            String message = getMessage(msgID, String.valueOf(entryDN),
@@ -949,11 +975,9 @@
            logToRejectWriter(lines, message);
            throw new LDIFException(msgID, message, lastEntryLineNumber, true);
          }
          else
          {
            valueSet.add(attributeValue);
            return;
          }
          valueSet.add(attributeValue);
          return;
        }
      }
@@ -1469,7 +1493,7 @@
    for(StringBuilder line : lines)
    {
      readAttribute(lines, line, entryDN, objectClasses,
          attributes, attributes);
          attributes, attributes, importConfig.validateSchema());
    }
    // Reconstruct the object class attribute.
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
New file
@@ -0,0 +1,552 @@
/*
 * 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.io.File;
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.CharacterSetPasswordValidatorCfgDefn;
import org.opends.server.admin.std.server.CharacterSetPasswordValidatorCfg;
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 character set password validator.
 */
public class CharacterSetPasswordValidatorTestCase
       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=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
         "ds-cfg-character-set: 1:0123456789",
         "ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
         "ds-cfg-character-set: 1:0123456789",
         "ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?",
         "ds-cfg-allow-unclassified-characters: 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", groups= { "slow" })
  public void testInitializeWithValidConfigs(Entry e)
         throws Exception
  {
    CharacterSetPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              CharacterSetPasswordValidatorCfgDefn.getInstance(), e);
    CharacterSetPasswordValidator validator =
         new CharacterSetPasswordValidator();
    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(
         // Malformed character set definition -- no colon.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: malformed",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- colon first.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: :malformed",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- colon last.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- non-integer count.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: noninteger:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- zero count.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 0:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- negative count.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: -1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- duplicate character in the
         // same character set.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyza",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed character set definition -- duplicate character in
         // different character sets.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYz",
         "ds-cfg-allow-unclassified-characters: true",
         "",
         // Malformed allow unclassified characters.
         "dn: cn=Character Set,cn=Password Validators,cn=config",
         "objectClass: top",
         "objectClass: ds-cfg-password-validator",
         "objectClass: ds-cfg-character-set-password-validator",
         "cn: Character Set",
         "ds-cfg-password-validator-class: org.opends.server.extensions." +
              "CharacterSetPasswordValidator",
         "ds-cfg-password-validator-enabled: true",
         "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
         "ds-cfg-allow-unclassified-characters: malformed");
    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
  {
    CharacterSetPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              CharacterSetPasswordValidatorCfgDefn.getInstance(), e);
    CharacterSetPasswordValidator validator =
         new CharacterSetPasswordValidator();
    validator.initializePasswordValidator(configuration);
    StringBuilder buffer = new StringBuilder();
    for (StringBuilder line : e.toLDIF())
    {
      buffer.append(line);
      buffer.append("\n");
    }
    fail(buffer.toString());
  }
  /**
   * Retrieves a set of data to use when testing a given password with a
   * provided configuration.  Each element of the returned array should be an
   * array of a configuration entry, a test password string, and an indication
   * as to whether the provided password should be acceptable.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @DataProvider(name = "testData")
  public Object[][] getTestData()
         throws Exception
  {
    return new Object[][]
    {
      // Default configuration, missing characters from multiple character sets.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
             "ds-cfg-character-set: 1:0123456789",
             "ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?",
             "ds-cfg-allow-unclassified-characters: true"),
        "password",
        false
      },
      // Default configuration, including characters from all of multiple
      // character sets.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
             "ds-cfg-character-set: 1:0123456789",
             "ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?",
             "ds-cfg-allow-unclassified-characters: true"),
        "PaS$w0rD",
        true
      },
      // Default configuration, including characters from some (but not all) of
      // multiple character sets.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-character-set: 1:ABCDEFGHIJKLMNOPQRSTUVWXYZ",
             "ds-cfg-character-set: 1:0123456789",
             "ds-cfg-character-set: 1:~!@#$%^&*()-_=+[]{}|;:,.<>/?",
             "ds-cfg-allow-unclassified-characters: true"),
        "PaS$worD",
        false
      },
      // Default configuration, including enough characters from a single
      // character set.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-allow-unclassified-characters: true"),
        "password",
        true
      },
      // Default configuration, including too few characters from a single
      // character set.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 6:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-allow-unclassified-characters: true"),
        "short",
        false
      },
      // Default configuration, allowing characters outside of any defined
      // character set.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-allow-unclassified-characters: true"),
        "PaS$w0rD",
        true
      },
      // Default configuration, rejecting characters outside of any defined
      // character set.
      new Object[]
      {
        TestCaseUtils.makeEntry(
             "dn: cn=Character Set,cn=Password Validators,cn=config",
             "objectClass: top",
             "objectClass: ds-cfg-password-validator",
             "objectClass: ds-cfg-character-set-password-validator",
             "cn: Character Set",
             "ds-cfg-password-validator-class: org.opends.server.extensions." +
                  "CharacterSetPasswordValidator",
             "ds-cfg-password-validator-enabled: true",
             "ds-cfg-character-set: 1:abcdefghijklmnopqrstuvwxyz",
             "ds-cfg-allow-unclassified-characters: false"),
        "PaS$w0rD",
        false
      },
    };
  }
  /**
   * Tests the {@code passwordIsAcceptable} method using the provided
   * information.
   *
   * @param  configEntry  The configuration entry to use for the password
   *                      validator.
   * @param  password     The password to test with the validator.
   * @param  acceptable   Indicates whether the provided password should be
   *                      considered acceptable.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(dataProvider = "testData")
  public void testPasswordIsAcceptable(Entry configEntry, String password,
                                       boolean acceptable)
         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");
    CharacterSetPasswordValidatorCfg configuration =
         AdminTestCaseUtils.getConfiguration(
              CharacterSetPasswordValidatorCfgDefn.getInstance(),
              configEntry);
    CharacterSetPasswordValidator validator =
         new CharacterSetPasswordValidator();
    validator.initializePasswordValidator(configuration);
    ASN1OctetString pwOS = 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();
    assertEquals(validator.passwordIsAcceptable(pwOS,
                              new HashSet<ByteString>(0), modifyOperation,
                              userEntry, invalidReason),
                 acceptable, invalidReason.toString());
    validator.finalizePasswordValidator();
  }
}