mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
21.29.2011 534e0fb5a07d53ee0bc4b66e0dcf1261d271296b
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();
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)
    {
      /*
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