From 534e0fb5a07d53ee0bc4b66e0dcf1261d271296b Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 21 Jul 2011 19:29:23 +0000
Subject: [PATCH] Fix OPENDJ-236: Support dn: and u: authid notation in SambaPasswordPlugin

---
 opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java                  |   55 ++++++++++
 opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java                                 |  184 ++++++++++--------------------------
 opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java |   45 ++++++---
 3 files changed, 131 insertions(+), 153 deletions(-)

diff --git a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 169a050..c80f58d 100644
--- a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -81,6 +81,43 @@
        implements ConfigurationChangeListener<
                     PasswordModifyExtendedOperationHandlerCfg>
 {
+  // The following attachments may be used by post-op plugins (e.g. Samba) in
+  // order to avoid re-decoding the request parameters and also to enforce
+  // atomicity.
+
+  /**
+   * The name of the attachment which will be used to store the fully resolved
+   * target entry.
+   */
+  public static final String AUTHZ_DN_ATTACHMENT;
+
+  /**
+   * The name of the attachment which will be used to store the password
+   * attribute.
+   */
+  public static final String PWD_ATTRIBUTE_ATTACHMENT;
+
+  /**
+   * The clear text password, which may not be present if the provided password
+   * was pre-encoded.
+   */
+  public static final String CLEAR_PWD_ATTACHMENT;
+
+  /**
+   * A list containing the encoded passwords: plugins can perform changes
+   * atomically via CAS.
+   */
+  public static final String ENCODED_PWD_ATTACHMENT;
+
+  static
+  {
+    final String PREFIX = PasswordModifyExtendedOperation.class.getName();
+    AUTHZ_DN_ATTACHMENT = PREFIX + ".AUTHZ_DN";
+    PWD_ATTRIBUTE_ATTACHMENT = PREFIX + ".PWD_ATTRIBUTE";
+    CLEAR_PWD_ATTACHMENT = PREFIX + ".CLEAR_PWD";
+    ENCODED_PWD_ATTACHMENT = PREFIX + ".ENCODED_PWD";
+  }
+
   /**
    * The tracer object for the debug logger.
    */
@@ -567,7 +604,7 @@
         operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
         operation.appendErrorMessage(message);
 
-          return;
+        return;
       }
 
 
@@ -716,7 +753,7 @@
 
 
 
-      // If the a new password was provided, then peform any appropriate
+      // If the a new password was provided, then perform any appropriate
       // validation on it.  If not, then see if we can generate one.
       boolean generatedPassword = false;
       boolean isPreEncoded      = false;
@@ -1087,10 +1124,20 @@
 
 
         // If we've gotten here, then everything is OK, so indicate that the
-        // operation was successful.  If a password was generated, then include
-        // it in the response.
+        // operation was successful.
         operation.setResultCode(ResultCode.SUCCESS);
 
+        // Save attachments for post-op plugins (e.g. Samba password plugin).
+        operation.setAttachment(AUTHZ_DN_ATTACHMENT, userDN);
+        operation.setAttachment(PWD_ATTRIBUTE_ATTACHMENT, pwPolicyState
+            .getPolicy().getPasswordAttribute());
+        if (!isPreEncoded)
+        {
+          operation.setAttachment(CLEAR_PWD_ATTACHMENT, newPassword);
+        }
+        operation.setAttachment(ENCODED_PWD_ATTACHMENT, encodedPasswords);
+
+        // If a password was generated, then include it in the response.
         if (generatedPassword)
         {
           ByteStringBuilder builder = new ByteStringBuilder();
diff --git a/opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java b/opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java
index e15a47e..268e2c0 100644
--- a/opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java
+++ b/opends/src/server/org/opends/server/plugins/SambaPasswordPlugin.java
@@ -30,7 +30,6 @@
 
 
 import static org.opends.messages.PluginMessages.*;
-import static org.opends.server.extensions.ExtensionsConstants.*;
 import static org.opends.server.loggers.ErrorLogger.logError;
 import static org.opends.server.util.StaticUtils.bytesToHexNoSpace;
 import static org.opends.server.util.StaticUtils.toLowerCase;
@@ -54,14 +53,14 @@
 import org.opends.server.api.plugin.PluginResult;
 import org.opends.server.api.plugin.PluginType;
 import org.opends.server.config.ConfigException;
+import org.opends.server.controls.LDAPAssertionRequestControl;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
+import org.opends.server.extensions.PasswordModifyExtendedOperation;
 import org.opends.server.loggers.debug.DebugLogger;
 import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.protocols.asn1.ASN1;
-import org.opends.server.protocols.asn1.ASN1Exception;
-import org.opends.server.protocols.asn1.ASN1Reader;
 import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.ldap.LDAPFilter;
 import org.opends.server.types.*;
 import org.opends.server.types.operation.PostOperationExtendedOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
@@ -694,69 +693,38 @@
       }
     }
 
-    // Get the request and response value from the operation.
-    final ByteString requestValue = extendedOperation.getRequestValue();
-    final ByteString responseValue = extendedOperation.getResponseValue();
-    ASN1Reader asn1Reader = ASN1.getReader(requestValue.asReader());
+    // Get the name of the entry and clear passwords from the operation
+    // attachments.
+    final DN dn = (DN) extendedOperation
+        .getAttachment(PasswordModifyExtendedOperation.AUTHZ_DN_ATTACHMENT);
+    if (dn == null)
+    {
+      // The attachment is missing which should never happen.
+      if (DebugLogger.debugEnabled())
+      {
+        TRACER.debugInfo("SambaPasswordPlugin: missing DN attachment");
+      }
+      return PluginResult.PostOperation.continueOperationProcessing();
+    }
 
-    DN dn = null;
-    String password = null;
+    final String password = extendedOperation.getAttachment(
+        PasswordModifyExtendedOperation.CLEAR_PWD_ATTACHMENT).toString();
+    if (password == null)
+    {
+      if (DebugLogger.debugEnabled())
+      {
+        TRACER.debugInfo("SambaPasswordPlugin: skipping syncing "
+            + "pre-encoded password");
+      }
+      return PluginResult.PostOperation.continueOperationProcessing();
+    }
 
-    // @formatter:off
-
-    /*
-     * Request and response are defined like this:
-     *
-     * passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1
-     *
-     * PasswdModifyRequestValue ::= SEQUENCE
-     * {
-     *   userIdentity [0] OCTET STRING OPTIONAL
-     *   oldPasswd    [1] OCTET STRING OPTIONAL
-     *   newPasswd    [2] OCTET STRING OPTIONAL
-     * }
-     *
-     * PasswdModifyResponseValue ::= SEQUENCE
-     * {
-     *   genPasswd    [0] OCTET STRING OPTIONAL
-     * }
-     */
-
-    // @formatter:on
+    @SuppressWarnings("unchecked")
+    final List<ByteString> encPasswords = (List<ByteString>) extendedOperation
+        .getAttachment(PasswordModifyExtendedOperation.ENCODED_PWD_ATTACHMENT);
 
     try
     {
-      // FIXME: need to handle dn: and a: notation.
-
-      // Read the request value first.
-      asn1Reader.readStartSequence();
-
-      // First get the userIdentity field.
-      if (asn1Reader.hasNextElement()
-          && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_USER_ID)
-      {
-        dn = DN.decode(asn1Reader.readOctetString());
-        if (DebugLogger.debugEnabled())
-        {
-          TRACER.debugInfo("Processing DN:" + dn.toNormalizedString());
-        }
-      }
-
-      if (DebugLogger.debugEnabled())
-      {
-        TRACER.debugInfo("Authorization DN: " + authDN);
-      }
-
-      if (dn == null)
-      {
-        dn = authDN;
-        if (DebugLogger.debugEnabled())
-        {
-          TRACER.debugInfo("The DN was not found in the request, but "
-              + "we can use the authorization DN.");
-        }
-      }
-
       // Before proceeding make sure this entry has samba object class.
       final Entry entry = DirectoryServer.getEntry(dn);
       if (!isSynchronizable(entry))
@@ -768,94 +736,42 @@
         return PluginResult.PostOperation.continueOperationProcessing();
       }
 
-      // Read the old password field if it exists. Skip it either way.
-      if (asn1Reader.hasNextElement()
-          && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_OLD_PASSWORD)
-      {
-        asn1Reader.skipElement();
-      }
-
-      // Read the new password field.
-      if (asn1Reader.hasNextElement()
-          && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_NEW_PASSWORD)
-      {
-        password = asn1Reader.readOctetStringAsString();
-        if (DebugLogger.debugEnabled())
-        {
-          TRACER.debugInfo("Got new password from the request.");
-        }
-      }
-
-      // Finish reading the request value.
-      asn1Reader.readEndSequence();
-
-      /*
-       * If the new password was not found in the request, look into the
-       * response - this usually the case when the password is changed by the
-       * directory manager or when the password is generated.
-       */
-      if (password == null)
-      {
-        if (DebugLogger.debugEnabled())
-        {
-          TRACER.debugInfo("Password couldn't be found in the"
-              + " request, getting it from the response.");
-        }
-
-        // Start reading the response value.
-        asn1Reader = ASN1.getReader(responseValue.asReader());
-        asn1Reader.readStartSequence();
-
-        // Read the generated password field.
-        if (asn1Reader.hasNextElement()
-            && asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD)
-        {
-          password = asn1Reader.readOctetStringAsString();
-          if (DebugLogger.debugEnabled())
-          {
-            TRACER.debugInfo("Got the new password from the " + "response.");
-          }
-        }
-
-        // Finish reading.
-        asn1Reader.readEndSequence();
-      }
-
       /*
        * Make an internal connection to process the password modification. It
        * will not trigger this plugin again with the pre-operation modify since
        * the password passed would be encoded hence the pre operation part would
        * skip it.
        */
-
-      // FIXME: This should be performed with a write lock taken when processing
-      // the extended operation. However, this is not yet supported by OpenDJ
-      // See OPENDJ-235 - https://bugster.forgerock.org/jira/browse/OPENDJ-235
       final InternalClientConnection connection = InternalClientConnection
           .getRootConnection();
 
+      final List<Modification> modifications = getModifications(password);
+
+      // Use an assertion control to avoid race conditions since extended
+      // operation post-ops are done outside of the write lock.
+      List<Control> controls = null;
+      if (!encPasswords.isEmpty())
+      {
+        final AttributeType pwdAttribute = (AttributeType) extendedOperation
+            .getAttachment(
+                PasswordModifyExtendedOperation.PWD_ATTRIBUTE_ATTACHMENT);
+        final LDAPFilter filter = RawFilter.createEqualityFilter(
+            pwdAttribute.getNameOrOID(), encPasswords.get(0));
+        final Control assertionControl = new LDAPAssertionRequestControl(true,
+            filter);
+
+        // FIXME: see OPENDJ-241
+        // controls = Collections.singletonList(assertionControl);
+      }
+
       final ModifyOperation modifyOperation = connection.processModify(dn,
-          getModifications(password));
+          modifications, controls);
 
       if (DebugLogger.debugEnabled())
       {
         TRACER.debugInfo(modifyOperation.getResultCode().toString());
       }
     }
-    catch (final ASN1Exception e)
-    {
-      /*
-       * The ASN1 reader was not able to process the request because: - it
-       * started reading an element which is not a sequence; or - it was not
-       * able to decode an element; or - it was not able to determine the BER
-       * type of an element; or - it was not able to decode the value as octet
-       * string. Either way, this should not happen, so we just log it.
-       */
-      if (DebugLogger.debugEnabled())
-      {
-        TRACER.debugCaught(DebugLogLevel.WARNING, e);
-      }
-    }
     catch (final DirectoryException e)
     {
       /*
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java
index cb016c2..969fe95 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/SambaPasswordPluginTestCase.java
@@ -426,16 +426,31 @@
 
 
   /**
+   * Return test authz IDs for password modify extended operations.
+   *
+   * @return The test authz IDs.
+   */
+  @DataProvider
+  public Object[][] authzID()
+  {
+    return new Object[][] { { "uid=test.user,o=test" },
+        { "dn:uid=test.user,o=test" }, { "u:test.user" } };
+  }
+
+
+
+  /**
    * Test the Password Modify Extended Operation as ROOT.
    *
+   * @param authzID The authz ID.
    * @throws Exception
    *           if the test fails.
    */
-  @Test
-  public void testPWEOAsRoot() throws Exception
+  @Test(dataProvider="authzID")
+  public void testPWEOAsRoot(String authzID) throws Exception
   {
     // Test entry
-    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user5,o=test",
+    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user,o=test",
         "objectClass: top", "objectClass: person",
         "objectClass: organizationalPerson", "objectClass: inetOrgPerson",
         "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User",
@@ -462,10 +477,9 @@
 
     writer.writeStartSequence();
 
-    // Write the DN of the entry we are changing.
-
+    // Write the authzID of the entry we are changing.
     writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID,
-        testEntry.getDN().toString());
+        authzID);
 
     /*
      * Since we perform the operation as ROOT, we don't have to put the old
@@ -474,7 +488,6 @@
      */
 
     // Write the new password
-
     writer.writeOctetString(
         ExtensionsConstants.TYPE_PASSWORD_MODIFY_NEW_PASSWORD, "password");
 
@@ -499,14 +512,15 @@
    * Test the Password Modify Extended Operation as Samba administrative user.
    * This operation should be skipped.
    *
+   * @param authzID The authz ID.
    * @throws Exception
    *           if the test fails.
    */
-  @Test
-  public void testPWEOAsSambaAdmin() throws Exception
+  @Test(dataProvider="authzID")
+  public void testPWEOAsSambaAdmin(String authzID) throws Exception
   {
     // Test entry
-    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user6,o=test",
+    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user,o=test",
         "objectClass: top", "objectClass: person",
         "objectClass: organizationalPerson", "objectClass: inetOrgPerson",
         "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User",
@@ -541,7 +555,7 @@
     // Write the DN of the entry we are changing.
 
     writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID,
-        testEntry.getDN().toString());
+        authzID);
 
     /*
      * Since we perform the operation as Samba admininistrative user, we don't
@@ -584,14 +598,15 @@
   /**
    * Test the Password Modify Extended Operation as normal user.
    *
+   * @param authzID The authz ID.
    * @throws Exception
    *           if the test fails.
    */
-  @Test
-  public void testPWEOAsUser() throws Exception
+  @Test(dataProvider="authzID")
+  public void testPWEOAsUser(String authzID) throws Exception
   {
     // Test entry
-    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user7,o=test",
+    Entry testEntry = TestCaseUtils.makeEntry("dn: uid=test.user,o=test",
         "objectClass: top", "objectClass: person",
         "objectClass: organizationalPerson", "objectClass: inetOrgPerson",
         "objectClass: sambaSAMAccount", "uid: test.user", "cn: Test User",
@@ -622,7 +637,7 @@
     // Write the DN of the entry we are changing.
 
     writer.writeOctetString(ExtensionsConstants.TYPE_PASSWORD_MODIFY_USER_ID,
-        testEntry.getDN().toString());
+        authzID);
 
     // Write the old password
 

--
Gitblit v1.10.0