From 272e306217cfa3e394574d9a1a4e69ff9e3a9600 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 11 Apr 2007 21:07:24 +0000
Subject: [PATCH] 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/resource/schema/02-config.ldif                                                                             |   12 
 opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java                                 |  309 ++++++++++++++++++
 opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml                  |   64 +++
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java |  552 ++++++++++++++++++++++++++++++++
 opends/src/server/org/opends/server/util/LDIFReader.java                                                          |   60 ++-
 opends/resource/config/config.ldif                                                                                |   13 
 6 files changed, 992 insertions(+), 18 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 12bc4f6..b219bd5 100644
--- a/opends/resource/config/config.ldif
+++ b/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
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 2c85851..bc9bf86 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/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' )
 
+
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml
new file mode 100644
index 0000000..d633b04
--- /dev/null
+++ b/opends/src/admin/defn/org/opends/server/admin/std/CharacterSetPasswordValidatorConfiguration.xml
@@ -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>
+
diff --git a/opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java b/opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java
new file mode 100644
index 0000000..ea655c7
--- /dev/null
+++ b/opends/src/server/org/opends/server/extensions/CharacterSetPasswordValidator.java
@@ -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);
+  }
+}
+
diff --git a/opends/src/server/org/opends/server/util/LDIFReader.java b/opends/src/server/org/opends/server/util/LDIFReader.java
index b8eecbc..ea638f3 100644
--- a/opends/src/server/org/opends/server/util/LDIFReader.java
+++ b/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.
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
new file mode 100644
index 0000000..db2989d
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/CharacterSetPasswordValidatorTestCase.java
@@ -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();
+  }
+}
+

--
Gitblit v1.10.0