From 6c119aa77f97501357fa8e7bb5fbf556b1f9f53d Mon Sep 17 00:00:00 2001
From: Nemanja Lukic <nemanja.lukic@forgerock.com>
Date: Thu, 03 Nov 2011 10:18:53 +0000
Subject: [PATCH] Patch for OPENDJ-295

---
 opends/resource/schema/02-config.ldif                                                                           |   13 ++
 opends/src/server/org/opends/server/extensions/DictionaryPasswordValidator.java                                 |   60 ++++++++--
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java |  180 +++++++++++++++++++++++++++++
 opends/src/admin/defn/org/opends/server/admin/std/DictionaryPasswordValidatorConfiguration.xml                  |   49 ++++++++
 opends/resource/config/config.ldif                                                                              |    1 
 opends/src/messages/messages/extension.properties                                                               |    2 
 6 files changed, 289 insertions(+), 16 deletions(-)

diff --git a/opends/resource/config/config.ldif b/opends/resource/config/config.ldif
index 836689e..9909f4c 100644
--- a/opends/resource/config/config.ldif
+++ b/opends/resource/config/config.ldif
@@ -1666,6 +1666,7 @@
 ds-cfg-dictionary-file: config/wordlist.txt
 ds-cfg-case-sensitive-validation: false
 ds-cfg-test-reversed-password: true
+ds-cfg-check-substrings: false
 
 dn: cn=Length-Based Password Validator,cn=Password Validators,cn=config
 objectClass: top
diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 803cd48..3567f18 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -23,6 +23,7 @@
 #
 #      Copyright 2006-2010 Sun Microsystems, Inc.
 #      Portions Copyright 2010-2011 ForgeRock AS.
+#      Portions Copyright 2011 profiq, s.r.o.
 #
 #
 # This file contains the attribute type and objectclass definitions for use
@@ -2712,6 +2713,14 @@
   NAME 'ds-cfg-log-control-oids'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.52
+  NAME 'ds-cfg-check-substrings'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
+  X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.53
+  NAME 'ds-cfg-min-substring-length'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
+  X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
   NAME 'ds-cfg-access-control-handler'
   SUP top
@@ -3666,7 +3675,9 @@
   STRUCTURAL
   MUST ( ds-cfg-dictionary-file $
          ds-cfg-case-sensitive-validation $
-         ds-cfg-test-reversed-password )
+         ds-cfg-test-reversed-password $
+         ds-cfg-check-substrings )
+  MAY ds-cfg-min-substring-length
   X-ORIGIN 'OpenDS Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.26027.1.2.95
   NAME 'ds-cfg-attribute-value-password-validator'
diff --git a/opends/src/admin/defn/org/opends/server/admin/std/DictionaryPasswordValidatorConfiguration.xml b/opends/src/admin/defn/org/opends/server/admin/std/DictionaryPasswordValidatorConfiguration.xml
index 227c0a3..ae3e5e2 100644
--- a/opends/src/admin/defn/org/opends/server/admin/std/DictionaryPasswordValidatorConfiguration.xml
+++ b/opends/src/admin/defn/org/opends/server/admin/std/DictionaryPasswordValidatorConfiguration.xml
@@ -24,6 +24,7 @@
   !
   !
   !      Copyright 2007-2008 Sun Microsystems, Inc.
+  |      Portions Copyright 2011 profiq, s.r.o.
   ! -->
 <adm:managed-object name="dictionary-password-validator"
   plural-name="dictionary-password-validators"
@@ -145,4 +146,52 @@
       </ldap:attribute>
     </adm:profile>
   </adm:property>
+  <adm:property name="check-substrings" mandatory="true">
+    <adm:synopsis>
+      Indicates wheather this password validator is to match portions of
+      the password string against dictionary words.
+    </adm:synopsis>
+    <adm:description>
+      If "false" then only match the entire password against words
+      otherwise ("true") check whether the password contains words. 
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>false</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:boolean />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-check-substrings</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+  <adm:property name="min-substring-length" mandatory="false">
+    <adm:synopsis>
+      Indicates the minimal length of the substring within the password
+      in case substring checking is enabled.
+    </adm:synopsis>
+    <adm:description>
+      If "check-substrings" option is set to true, then this parameter
+      defines the length of the smallest word which should be used for
+      substring matching. Use with caution because values below 3 might
+      disqualify valid passwords.
+    </adm:description>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>5</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:integer />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-min-substring-length</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
 </adm:managed-object>
diff --git a/opends/src/messages/messages/extension.properties b/opends/src/messages/messages/extension.properties
index 2412677..94e5306 100644
--- a/opends/src/messages/messages/extension.properties
+++ b/opends/src/messages/messages/extension.properties
@@ -1123,7 +1123,7 @@
 MILD_ERR_VATTR_NOT_SEARCHABLE_459=The %s attribute is not \
  searchable and should not be included in otherwise unindexed search filters
 MILD_ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY_460=The provided \
- password was found in the server's dictionary
+ password contained a word from the server's dictionary
 MILD_ERR_DICTIONARY_VALIDATOR_NO_SUCH_FILE_461=The specified dictionary file \
  %s does not exist
 MILD_ERR_DICTIONARY_VALIDATOR_CANNOT_READ_FILE_462=An error occurred while \
diff --git a/opends/src/server/org/opends/server/extensions/DictionaryPasswordValidator.java b/opends/src/server/org/opends/server/extensions/DictionaryPasswordValidator.java
index 172e3b2..c85bc72 100644
--- a/opends/src/server/org/opends/server/extensions/DictionaryPasswordValidator.java
+++ b/opends/src/server/org/opends/server/extensions/DictionaryPasswordValidator.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2011 profiq, s.r.o.
  */
 package org.opends.server.extensions;
 import org.opends.messages.Message;
@@ -124,9 +125,7 @@
   {
     // Get a handle to the current configuration.
     DictionaryPasswordValidatorCfg config = currentConfig;
-    HashSet<String> dictionary = this.dictionary;
-
-
+    
     // Check to see if the provided password is in the dictionary in the order
     // that it was provided.
     String password = newPassword.toString();
@@ -135,26 +134,43 @@
       password = toLowerCase(password);
     }
 
-    if (dictionary.contains(password))
+    // Check to see if we should verify the whole password or the substrings.
+    // Either way, we initialise the minSubstringLength to the length of
+    // the password which is the default behaviour ('check-substrings: false')
+    int minSubstringLength = password.length();
+
+    if (config.isCheckSubstrings())
+    {
+      // We apply the minimal substring length only if the provided value
+      // is smaller then the actual password length
+      if (config.getMinSubstringLength() < password.length())
+      {
+        minSubstringLength = config.getMinSubstringLength();
+      }
+    }
+  
+    // Verify if the dictionary contains the word(s) in the password
+    if (isDictionaryBased(password, minSubstringLength))
     {
       invalidReason.append(
-              ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
+        ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
       return false;
     }
-
-
-    // If we should try the reversed value, then do that as well.
+    
+    // If the reverse password checking is enabled, then verify if the
+    // reverse value of the password is in the dictionary.
     if (config.isTestReversedPassword())
     {
-      if (dictionary.contains(new StringBuilder(password).reverse().toString()))
+      if (isDictionaryBased(
+        new StringBuilder(password).reverse().toString(), minSubstringLength))
       {
         invalidReason.append(
-                ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
+          ERR_DICTIONARY_VALIDATOR_PASSWORD_IN_DICTIONARY.get());
         return false;
       }
     }
 
-
+    
     // If we've gotten here, then the password is acceptable.
     return true;
   }
@@ -306,5 +322,25 @@
 
     return new ConfigChangeResult(resultCode, adminActionRequired, messages);
   }
-}
 
+  private boolean isDictionaryBased(String password,
+                                    int minSubstringLength)
+  {
+    HashSet<String> dictionary = this.dictionary;
+    final int passwordLength = password.length();
+
+    for (int i = 0; i < passwordLength; i++)
+    {
+      for (int j = i + minSubstringLength; j <= passwordLength; j++)
+      {
+        String substring = password.substring(i, j);
+        if (dictionary.contains(substring))
+        {
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
index 1197145..c661fc6 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/DictionaryPasswordValidatorTestCase.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2011 profiq, s.r.o.
  */
 package org.opends.server.extensions;
 
@@ -127,6 +128,8 @@
          "ds-cfg-dictionary-file: " + dictionaryFile,
          "ds-cfg-case-sensitive-validation: false",
          "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: true",
+         "ds-cfg-min-substring-length: 3",
          "",
          "dn: cn=Dictionary,cn=Password Validators,cn=config",
          "objectClass: top",
@@ -139,6 +142,7 @@
          "ds-cfg-dictionary-file: " + dictionaryFile,
          "ds-cfg-case-sensitive-validation: true",
          "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: false",
          "",
          "dn: cn=Dictionary,cn=Password Validators,cn=config",
          "objectClass: top",
@@ -150,7 +154,8 @@
          "ds-cfg-enabled: true",
          "ds-cfg-dictionary-file: " + dictionaryFile,
          "ds-cfg-case-sensitive-validation: false",
-         "ds-cfg-test-reversed-password: true");
+         "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: true");
 
     Object[][] array = new Object[entries.size()][1];
     for (int i=0; i < array.length; i++)
@@ -208,6 +213,7 @@
          "ds-cfg-dictionary-file: invalid",
          "ds-cfg-case-sensitive-validation: false",
          "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: false",
          "",
          // Dictionary file not a file.
          "dn: cn=Dictionary,cn=Password Validators,cn=config",
@@ -221,6 +227,7 @@
          "ds-cfg-dictionary-file: config",
          "ds-cfg-case-sensitive-validation: false",
          "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: false",
          "",
          // Invalid case-sensitive-validation
          "dn: cn=Dictionary,cn=Password Validators,cn=config",
@@ -234,6 +241,7 @@
          "ds-cfg-dictionary-file: " + dictionaryFile,
          "ds-cfg-case-sensitive-validation: invalid",
          "ds-cfg-test-reversed-password: true",
+         "ds-cfg-check-substrings: false",
          "",
          // Invalid test-reversed-password
          "dn: cn=Dictionary,cn=Password Validators,cn=config",
@@ -246,7 +254,37 @@
          "ds-cfg-enabled: true",
          "ds-cfg-dictionary-file: " + dictionaryFile,
          "ds-cfg-case-sensitive-validation: false",
-         "ds-cfg-test-reversed-password: invalid");
+         "ds-cfg-test-reversed-password: invalid",
+         "ds-cfg-check-substrings: false",
+         "",
+         // Invalid check-substrings
+         "dn: cn=Dictionary,cn=Password Validators,cn=config",
+         "objectClass: top",
+         "objectClass: ds-cfg-password-validator",
+         "objectClass: ds-cfg-dictionary-password-validator",
+         "cn: Dictionary",
+         "ds-cfg-java-class: org.opends.server.extensions." +
+              "DictionaryPasswordValidator",
+         "ds-cfg-enabled: true",
+         "ds-cfg-dictionary-file: " + dictionaryFile,
+         "ds-cfg-case-sensitive-validation: false",
+         "ds-cfg-test-reversed-password: invalid",
+         "ds-cfg-check-substrings: invalid",
+         "",
+         // Invalid min-substring-length
+         "dn: cn=Dictionary,cn=Password Validators,cn=config",
+         "objectClass: top",
+         "objectClass: ds-cfg-password-validator",
+         "objectClass: ds-cfg-dictionary-password-validator",
+         "cn: Dictionary",
+         "ds-cfg-java-class: org.opends.server.extensions." +
+              "DictionaryPasswordValidator",
+         "ds-cfg-enabled: true",
+         "ds-cfg-dictionary-file: " + dictionaryFile,
+         "ds-cfg-case-sensitive-validation: false",
+         "ds-cfg-test-reversed-password: invalid",
+         "ds-cfg-check-substrings: true",
+         "ds-cfg-min-substring-length: invalid");
 
     Object[][] array = new Object[entries.size()][1];
     for (int i=0; i < array.length; i++)
@@ -311,6 +349,7 @@
              "ds-cfg-enabled: true",
              "ds-cfg-dictionary-file: " + dictionaryFile,
              "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: false",
              "ds-cfg-test-reversed-password: true"),
         "notindictionary",
         true
@@ -330,6 +369,7 @@
              "ds-cfg-enabled: true",
              "ds-cfg-dictionary-file: " + dictionaryFile,
              "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: false",
              "ds-cfg-test-reversed-password: true"),
         "password",
         false
@@ -350,6 +390,7 @@
              "ds-cfg-enabled: true",
              "ds-cfg-dictionary-file: " + dictionaryFile,
              "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: false",
              "ds-cfg-test-reversed-password: true"),
         "PaSsWoRd",
         false
@@ -370,6 +411,7 @@
              "ds-cfg-enabled: true",
              "ds-cfg-dictionary-file: " + dictionaryFile,
              "ds-cfg-case-sensitive-validation: true",
+             "ds-cfg-check-substrings: false",
              "ds-cfg-test-reversed-password: true"),
         "PaSsWoRd",
         true
@@ -454,6 +496,140 @@
         "dRoWsSaP",
         true
       },
+   
+      // Substrings checking configuration with a word in the dictionary,
+      // case-sensitive matching enabled
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: true",
+             "ds-cfg-check-substrings: true",
+             "ds-cfg-min-substring-length: 3",
+             "ds-cfg-test-reversed-password: true"),
+        "oldpassword",
+        false
+      },
+      
+      // Substrings checking configuration with a word in the dictionary,
+      // case-sensitive matching disabled
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: true",
+             "ds-cfg-min-substring-length: 3",
+             "ds-cfg-test-reversed-password: true"),
+        "NewPassword",
+        false
+      },
+      
+      // Substrings checking configuration with a word in the dictionary,
+      // case-sensitive matching enabled (dictionary word is lower case)
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: true",
+             "ds-cfg-check-substrings: true",
+             "ds-cfg-min-substring-length: 3",
+             "ds-cfg-test-reversed-password: true"),
+        "NewPassword",
+        true
+      },
+      
+      // Substrings checking configuration with a word in the dictionary,
+      // case-sensitive matching disabled, and minimal substring length
+      // of 5 while the password is only 3 characters
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: true",
+             "ds-cfg-min-substring-length: 5",
+             "ds-cfg-test-reversed-password: true"),
+        "god",
+        false
+      },
+      
+      // Substrings checking configuration with a word in the dictionary,
+      // case-sensitive matching disabled, and minimal substring length
+      // of 5 while the word in the dictionary is only 3 characters
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-check-substrings: true",
+             "ds-cfg-min-substring-length: 5",
+             "ds-cfg-test-reversed-password: true"),
+        "godblessus",
+        true
+      },
+      
+      // Substring checking configuration with a reverse of a word in the 
+      // dictionary, reversed matching enabled and case-insensitive 
+      // matching enabled
+      new Object[]
+      {
+        TestCaseUtils.makeEntry(
+             "dn: cn=Dictionary,cn=Password Validators,cn=config",
+             "objectClass: top",
+             "objectClass: ds-cfg-password-validator",
+             "objectClass: ds-cfg-dictionary-password-validator",
+             "cn: Dictionary",
+             "ds-cfg-java-class: org.opends.server.extensions." +
+                  "DictionaryPasswordValidator",
+             "ds-cfg-enabled: true",
+             "ds-cfg-dictionary-file: " + dictionaryFile,
+             "ds-cfg-case-sensitive-validation: false",
+             "ds-cfg-test-reversed-password: true",
+             "ds-cfg-check-substrings: true"),
+        "sdfdRoWsSaPqwerty",
+        false
+      },
     };
   }
 

--
Gitblit v1.10.0