From 439a48e88a9891e3e3e813ca06bfbcbf54bcb140 Mon Sep 17 00:00:00 2001
From: Ludovic Poitou <ludovic.poitou@forgerock.com>
Date: Wed, 20 Jul 2011 09:37:34 +0000
Subject: [PATCH] Fix issue OPENDJ-237: Password modification by deleting the value and adding a new one fails with unwilling to perform (would result in multiple password in the entry). Make sure we update the passwords counters when deleting values. Unit-tests added to make sure we do not have regressions in the future

---
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java |   32 +++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java     |  191 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 222 insertions(+), 1 deletions(-)

diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index e37efe2..858bd6a 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -1323,6 +1323,12 @@
     // encoded forms.
     Attribute pwAttr = m.getAttribute();
     AttributeBuilder builder = new AttributeBuilder(pwAttr, true);
+    if (pwAttr.isEmpty())
+    {
+      // Removing all current password values.
+      numPasswords = 0;
+    }
+
     for (AttributeValue v : pwAttr)
     {
       if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
@@ -1335,7 +1341,31 @@
         }
         else
         {
-          builder.add(v);
+          // We still need to check if the pre-encoded password matches
+          // an existing value, to decrease the number of passwords.
+          List<Attribute> attrList = currentEntry.getAttribute(pwAttr
+              .getAttributeType());
+          if ((attrList == null) || (attrList.isEmpty()))
+          {
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                ERR_MODIFY_NO_EXISTING_VALUES.get());
+          }
+          boolean found = false;
+          for (Attribute attr : attrList)
+          {
+            for (AttributeValue av : attr)
+            {
+              if (av.equals(v))
+              {
+                builder.add(v);
+                found = true;
+              }
+            }
+          }
+          if (found)
+          {
+            numPasswords--;
+          }
         }
       }
       else
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
index e80ba20..a99dff8 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/ModifyOperationTestCase.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2011 Sun Microsystems, Inc.
+ *      Portions Copyright 2011 ForgeRock AS
  */
 package org.opends.server.core;
 
@@ -4776,6 +4777,196 @@
     assertTrue(modifyOperation.getResultCode() == ResultCode.SUCCESS);
     retrieveSuccessfulOperationElements(modifyOperation);
   }
+  
+    /**
+   * Tests a modify operation that attempts change the user password doing
+   * a delete of all values followed of an add of a new value.
+   *
+   * @param  baseDN  The base DN for the test backend.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "baseDNs")
+  public void testModifyDelAddPasswordAttribute(String baseDN)
+         throws Exception
+  {
+    TestCaseUtils.clearJEBackend(true,"userRoot",baseDN);
+
+     Entry entry = TestCaseUtils.makeEntry(
+         "dn: uid=testPassword01.user," + baseDN,
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: test.user",
+         "givenName: Test",
+         "sn: User",
+         "cn: Test User",
+         "displayName: Test User",
+         "userPassword: password");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    AddOperation addOperation =
+         conn.processAdd(entry.getDN(), entry.getObjectClasses(),
+                         entry.getUserAttributes(),
+                         entry.getOperationalAttributes());
+    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+    
+    String path = TestCaseUtils.createTempFile(
+         "dn: uid=testPassword01.user," + baseDN,
+         "changetype: modify",
+         "delete: userPassword",
+         "-",
+         "add: userPassword",
+         "userPassword: aNewPassword");
+ 
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+  }
+
+   /**
+   * Tests a modify operation that attempts change the user password doing
+   * a delete of a clear text value followed of an add of a new value.
+   *
+   * @param  baseDN  The base DN for the test backend.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "baseDNs")
+  public void testModifyDelOneAddOnePasswordAttribute(String baseDN)
+         throws Exception
+  {
+    TestCaseUtils.clearJEBackend(true,"userRoot",baseDN);
+
+     Entry entry = TestCaseUtils.makeEntry(
+         "dn: uid=testPassword02.user," + baseDN,
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: test.user",
+         "givenName: Test",
+         "sn: User",
+         "cn: Test User",
+         "displayName: Test User",
+         "userPassword: password");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    AddOperation addOperation =
+         conn.processAdd(entry.getDN(), entry.getObjectClasses(),
+                         entry.getUserAttributes(),
+                         entry.getOperationalAttributes());
+    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+    
+    String path = TestCaseUtils.createTempFile(
+         "dn: uid=testPassword02.user," + baseDN,
+         "changetype: modify",
+         "delete: userPassword",
+         "userPassword: password",
+         "-",
+         "add: userPassword",
+         "userPassword: aNewPassword");
+ 
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+  }
+
+   /**
+   * Tests a modify operation that attempts change the user password doing
+   * a delete of an encrypted value followed of an add of a new value.
+   *
+   * @param  baseDN  The base DN for the test backend.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(dataProvider = "baseDNs")
+  public void testModifyDelEncryptedAddOnePasswordAttribute(String baseDN)
+         throws Exception
+  {
+    TestCaseUtils.clearJEBackend(true,"userRoot",baseDN);
+
+     Entry entry = TestCaseUtils.makeEntry(
+         "dn: uid=testPassword03.user," + baseDN,
+         "objectClass: top",
+         "objectClass: person",
+         "objectClass: organizationalPerson",
+         "objectClass: inetOrgPerson",
+         "uid: test.user",
+         "givenName: Test",
+         "sn: User",
+         "cn: Test User",
+         "displayName: Test User",
+         "userPassword: password");
+
+    InternalClientConnection conn =
+         InternalClientConnection.getRootConnection();
+
+    AddOperation addOperation =
+         conn.processAdd(entry.getDN(), entry.getObjectClasses(),
+                         entry.getUserAttributes(),
+                         entry.getOperationalAttributes());
+    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
+
+    Entry e = DirectoryServer.getEntry(
+            DN.decode("uid=testPassword03.user," + baseDN));
+    List<Attribute> attrList =
+         e.getAttribute(DirectoryServer.getAttributeType("userpassword", true));
+
+    assertNotNull(attrList);
+
+    String passwd = null;
+    for (Attribute a : attrList)
+    {
+      for (AttributeValue v : a)
+      {
+        passwd = v.toString();
+      }
+    }
+    
+    assertNotNull(passwd);
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: uid=testPassword03.user," + baseDN,
+         "changetype: modify",
+         "delete: userPassword",
+         "userPassword: " + passwd,
+         "-",
+         "add: userPassword",
+         "userPassword: aNewPassword");
+ 
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+  }
 
 }
 

--
Gitblit v1.10.0