From 2a88383d7affa766d19f7157fccdcc20000f5384 Mon Sep 17 00:00:00 2001
From: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Mar 2026 10:31:45 +0000
Subject: [PATCH] Fix AttributeValuePasswordValidator: inverted substring logic and reversed password not checked in substrings

---
 opendj-server-legacy/src/test/java/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java |   96 ++++++++++++++++++++++++++++++++++++++++++++++++
 opendj-server-legacy/src/main/java/org/opends/server/extensions/AttributeValuePasswordValidator.java         |    9 ++--
 2 files changed, 101 insertions(+), 4 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/AttributeValuePasswordValidator.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/AttributeValuePasswordValidator.java
index c15055c..fd490ef 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/AttributeValuePasswordValidator.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/AttributeValuePasswordValidator.java
@@ -19,6 +19,7 @@
 import org.forgerock.i18n.LocalizableMessage;
 
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 import org.forgerock.opendj.config.server.ConfigurationChangeListener;
@@ -87,11 +88,10 @@
     {
       for (int j = i + minSubstringLength; j <= passwordLength; j++)
       {
-        Attribute substring = Attributes.create(a.getAttributeDescription().getAttributeType(),
-            password.substring(i, j));
+        final String pwdSubstring = password.substring(i, j).toLowerCase(Locale.ROOT);
         for (ByteString val : a)
         {
-          if (substring.contains(val))
+          if (val.toString().toLowerCase(Locale.ROOT).contains(pwdSubstring))
           {
             return true;
           }
@@ -141,7 +141,8 @@
         if (a.contains(vf) ||
             (config.isTestReversedPassword() && a.contains(vr)) ||
             (config.isCheckSubstrings() &&
-                containsSubstring(password, minSubstringLength, a)))
+                (containsSubstring(password, minSubstringLength, a) ||
+                 (config.isTestReversedPassword() && containsSubstring(reversed, minSubstringLength, a)))))
         {
           invalidReason.append(ERR_ATTRVALUE_VALIDATOR_PASSWORD_IN_ENTRY.get());
           return false;
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
index 6a19300..41aaaad 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/extensions/AttributeValuePasswordValidatorTestCase.java
@@ -371,6 +371,102 @@
 
 
   /**
+   * Retrieves test data for substring and reversed-password substring checks
+   * using a user entry with uid=USN123.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @DataProvider(name = "substringTestData")
+  public Object[][] getSubstringTestData()
+         throws Exception
+  {
+    Entry configEntry = TestCaseUtils.makeEntry(
+         "dn: cn=Attribute Value,cn=Password Validators,cn=config",
+         "objectClass: top",
+         "objectClass: ds-cfg-password-validator",
+         "objectClass: ds-cfg-attribute-value-password-validator",
+         "cn: Attribute Value",
+         "ds-cfg-java-class: org.opends.server.extensions." +
+              "AttributeValuePasswordValidator",
+         "ds-cfg-enabled: true",
+         "ds-cfg-match-attribute: uid",
+         "ds-cfg-check-substrings: true",
+         "ds-cfg-min-substring-length: 3",
+         "ds-cfg-test-reversed-password: true");
+
+    return new Object[][]
+    {
+      // Password containing a forward substring of the attribute value ("USN" is in "USN123") → rejected
+      new Object[] { configEntry, "USN123aa", false },
+
+      // Password containing another forward substring of the attribute value ("123" is in "USN123") → rejected
+      new Object[] { configEntry, "U1sn123b", false },
+
+      // Password whose reverse contains a substring of the attribute value:
+      // reversed("NsU321ab") = "ba123UsN"; "123" is in "USN123" → rejected
+      new Object[] { configEntry, "NsU321ab", false },
+
+      // Password with no substrings of the attribute value → accepted
+      new Object[] { configEntry, "Sun3RiseA", true },
+    };
+  }
+
+
+
+  /**
+   * Tests substring and reversed-password substring checks against a user
+   * entry with uid=USN123.
+   *
+   * @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 = "substringTestData")
+  public void testSubstringPasswordIsAcceptable(Entry configEntry,
+                                                String password,
+                                                boolean acceptable)
+         throws Exception
+  {
+    TestCaseUtils.initializeTestBackend(true);
+    Entry userEntry = TestCaseUtils.makeEntry(
+         "dn: uid=USN123,o=test",
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: USN123",
+         "givenName: USN",
+         "sn: 123",
+         "cn: USN 123",
+         "userPassword: doesntmatter");
+
+    AttributeValuePasswordValidator validator = initializePasswordValidator(configEntry);
+
+    ByteString pwOS = ByteString.valueOfUtf8(password);
+    ArrayList<Modification> mods = CollectionUtils.newArrayList(
+        new Modification(ModificationType.REPLACE, Attributes.create("userpassword", password)));
+
+    ModifyOperationBasis modifyOperation =
+         new ModifyOperationBasis(getRootConnection(), nextOperationID(), nextMessageID(),
+                             new ArrayList<Control>(),
+                             DN.valueOf("uid=USN123,o=test"), mods);
+
+    LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder();
+    assertEquals(validator.passwordIsAcceptable(pwOS,
+                              new HashSet<ByteString>(0), modifyOperation,
+                              userEntry, invalidReason),
+                 acceptable, invalidReason.toString());
+
+    validator.finalizePasswordValidator();
+  }
+
+
+
+  /**
    * Tests the {@code passwordIsAcceptable} method using the provided
    * information.
    *

--
Gitblit v1.10.0