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

neil_a_wilson
04.28.2006 17eaa1ec294407aedf295965be72854d5a570179
Update the password modify extended operation so that it includes all
appropriate password policy processing.
3 files modified
951 ■■■■■ changed files
opends/src/server/org/opends/server/core/PasswordPolicyState.java 280 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java 513 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ExtensionsMessages.java 158 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -180,6 +180,9 @@
  // The password policy with which the account is associated.
  private PasswordPolicy passwordPolicy;
  // The string representation of the current time.
  private String currentGeneralizedTime;
  // The string representation of the user's DN.
  private String userDNString;
@@ -213,6 +216,7 @@
    userDNString           = userEntry.getDN().toString();
    passwordPolicy         = getPasswordPolicyInternal();
    currentGeneralizedTime = TimeThread.getGeneralizedTime();
    currentTime            = TimeThread.getTime();
    modifications          = new LinkedList<Modification>();
    isDisabled             = ConditionResult.UNDEFINED;
@@ -770,6 +774,27 @@
  /**
   * Retrieves the set of values for the password attribute from the user entry.
   *
   * @return  The set of values for the password attribute from the user entry.
   */
  public LinkedHashSet<AttributeValue> getPasswordValues()
  {
    assert debugEnter(CLASS_NAME, "getPasswordValues");
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    for (Attribute a : attrList)
    {
      return a.getValues();
    }
    return new LinkedHashSet<AttributeValue>(0);
  }
  /**
   * Indicates whether the associated password policy requires that
   * authentication be performed in a secure manner.
   *
@@ -854,6 +879,23 @@
  /**
   * Indicates whether users will be required to provide their current password
   * when choosing a new one.
   *
   * @return  <CODE>true</CODE> if users will be required to provide their
   *          current password when choosing a new one, or <CODE>false</CODE>
   *          if not.
   */
  public boolean requireCurrentPassword()
  {
    assert debugEnter(CLASS_NAME, "requireCurrentPassword");
    return passwordPolicy.requireCurrentPassword();
  }
  /**
   * Indicates whether administrative password resets should be allowed to
   * bypass validity checks for the new password.
   *
@@ -937,6 +979,36 @@
  /**
   * Retrieves time that this password policy state object was created.
   *
   * @return  The time that this password policy state object was created.
   */
  public long getCurrentTime()
  {
    assert debugEnter(CLASS_NAME, "getCurrentTime");
    return currentTime;
  }
  /**
   * Retrieves the generalized time representation of the time that this
   * password policy state object was created.
   *
   * @return  The generalized time representation of the time that this
   *          password policy state object was created.
   */
  public String getCurrentGeneralizedTime()
  {
    assert debugEnter(CLASS_NAME, "getCurrentGeneralizedTime");
    return currentGeneralizedTime;
  }
  /**
   * Sets a new value for the password changed time equal to the current time.
   */
  public void setPasswordChangedTime()
@@ -1140,7 +1212,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.DELETE,
        modifications.add(new Modification(ModificationType.REPLACE,
                                           new Attribute(type)));
      }
    }
@@ -1384,7 +1456,7 @@
        }
        else
        {
          modifications.add(new Modification(ModificationType.DELETE,
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
        }
      }
@@ -1514,7 +1586,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.DELETE,
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
    }
  }
@@ -1664,7 +1736,7 @@
        }
        else
        {
          modifications.add(new Modification(ModificationType.DELETE,
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
        }
@@ -1800,7 +1872,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.DELETE,
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
    }
  }
@@ -2336,7 +2408,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.DELETE,
        modifications.add(new Modification(ModificationType.REPLACE,
                                           new Attribute(type)));
      }
    }
@@ -2625,6 +2697,87 @@
  /**
   * Indicates whether users will be allowed to change their passwords if they
   * are expired.
   *
   * @return  <CODE>true</CODE> if users will be allowed to change their
   *          passwords if they are expired, or <CODE>false</CODE> if not.
   */
  public boolean allowExpiredPasswordChanges()
  {
    assert debugEnter(CLASS_NAME, "allowExpiredPasswordChanges");
    return passwordPolicy.allowExpiredPasswordChanges();
  }
  /**
   * Indicates whether the user's last password change was within the minimum
   * password age.
   *
   * @return  <CODE>true</CODE> if the password minimum age is nonzero, the
   *          account is not in force-change mode, and the last password change
   *          was within the minimum age, or <CODE>false</CODE> otherwise.
   */
  public boolean isWithinMinimumAge()
  {
    assert debugEnter(CLASS_NAME, "isWithinMinimumAge");
    int minAge = passwordPolicy.getMinimumPasswordAge();
    if (minAge <= 0)
    {
      // There is no minimum age, so the user isn't in it.
      if (debug)
      {
        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
                     CLASS_NAME, "isWithinMinimumAge",
                     "Returning false because there is no minimum age.");
      }
      return false;
    }
    else if ((passwordChangedTime + (minAge*1000)) < currentTime)
    {
      // It's been long enough since the user changed their password.
      if (debug)
      {
        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
                     CLASS_NAME, "isWithinMinimumAge",
                     "Returning false because the minimum age has expired.");
      }
      return false;
    }
    else if (mustChangePassword())
    {
      // The user is in a must-change mode, so the minimum age doesn't apply.
      if (debug)
      {
        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
                     CLASS_NAME, "isWithinMinimumAge",
                     "Returning false because the account is in a " +
                     "must-change state.");
      }
      return false;
    }
    else
    {
      // The user is within the minimum age.
      if (debug)
      {
        debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.WARNING,
                     CLASS_NAME, "isWithinMinimumAge", "Returning true.");
      }
      return true;
    }
  }
  /**
   * Indicates whether the user may use a grace login if the password is expired
   * and there is at least one grace login remaining.  Note that this does not
   * check to see if the user's password is expired, does not verify that there
@@ -2949,6 +3102,35 @@
  /**
   * Updates the user entry to clear the warned time.
   */
  public void clearWarnedTime()
  {
    assert debugEnter(CLASS_NAME, "clearWarnedTime");
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      Attribute a = new Attribute(type);
      modifications.add(new Modification(ModificationType.REPLACE, a));
    }
    if (debug)
    {
      debugMessage(DebugLogCategory.PASSWORD_POLICY, DebugLogSeverity.INFO,
                   CLASS_NAME, "clearWarnedTime",
                   "Cleared the warned time for user " + userDNString);
    }
  }
  /**
   * Retrieves the maximum number of grace logins that the user will be allowed
   * according to the associated password policy.
   *
@@ -3011,7 +3193,7 @@
        }
        else
        {
          modifications.add(new Modification(ModificationType.DELETE,
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
        }
      }
@@ -3159,7 +3341,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.DELETE,
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
    }
  }
@@ -3336,6 +3518,88 @@
  /**
   * Indicates whether the user's password is stored using the auth password
   * syntax or the user password syntax.
   *
   * @return  <CODE>true</CODE> if the user's password is stored using the auth
   *          password syntax, or <CODE>false</CODE> if it is stored using the
   *          user password syntax.
   */
  public boolean usesAuthPasswordSyntax()
  {
    assert debugEnter(CLASS_NAME, "usesAuthPasswordSyntax");
    return passwordPolicy.usesAuthPasswordSyntax();
  }
  /**
   * Indicates whether the provided password value is pre-encoded.
   *
   * @param  passwordValue  The value for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the provided password value is pre-encoded,
   *          or <CODE>false</CODE> if it is not.
   */
  public boolean passwordIsPreEncoded(ByteString passwordValue)
  {
    assert debugEnter(CLASS_NAME, "isPreEncoded", "ByteString");
    if (passwordPolicy.usesAuthPasswordSyntax())
    {
      return AuthPasswordSyntax.isEncoded(passwordValue);
    }
    else
    {
      return UserPasswordSyntax.isEncoded(passwordValue);
    }
  }
  /**
   * Encodes the provided password using the default storage schemes (using the
   * appropriate syntax for the password attribute).
   *
   * @param  password  The password to be encoded.
   *
   * @return  The password encoded using the default schemes.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the password.
   */
  public List<ByteString> encodePassword(ByteString password)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "encodePassword", "ByteString");
    List<PasswordStorageScheme> schemes =
         passwordPolicy.getDefaultStorageSchemes();
    List<ByteString> encodedPasswords =
         new ArrayList<ByteString>(schemes.size());
    if (passwordPolicy.usesAuthPasswordSyntax())
    {
      for (PasswordStorageScheme s : schemes)
      {
        encodedPasswords.add(s.encodeAuthPassword(password));
      }
    }
    else
    {
      for (PasswordStorageScheme s : schemes)
      {
        encodedPasswords.add(s.encodePasswordWithScheme(password));
      }
    }
    return encodedPasswords;
  }
  /**
   * Indicates whether the provided password appears to be acceptable according
   * to the password validators.
   *
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -29,12 +29,14 @@
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ExtendedOperationHandler;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryException;
@@ -43,15 +45,16 @@
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
@@ -59,11 +62,13 @@
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.extensions.ExtensionsConstants.*;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.ExtensionsMessages.*;
@@ -126,12 +131,6 @@
    assert debugEnter(CLASS_NAME, "initializeExtendedOperationHandler",
                      String.valueOf(configEntry));
    // NYI -- parse the config entry for any settings that might be defined
    // This can include:
    // - Whether to require the old password
    // - Whether to allow automatic generation of a new password
    // - The class name for an algorithm to generate new passwords
    DirectoryServer.registerSupportedExtension(OID_PASSWORD_MODIFY_REQUEST,
                                               this);
  }
@@ -163,9 +162,9 @@
    // Initialize the variables associated with components that may be included
    // in the request.
    ASN1OctetString userIdentity = null;
    ASN1OctetString oldPassword  = null;
    ASN1OctetString newPassword  = null;
    ByteString userIdentity = null;
    ByteString oldPassword  = null;
    ByteString newPassword  = null;
    // Parse the encoded request, if there is one.
@@ -246,14 +245,6 @@
        }
        // If the user is connected over an insecure channel, then determine
        // whether we should attempt to proceed.
        if (! clientConnection.isSecure())
        {
          // NYI
        }
        // Retrieve a write lock on that user's entry.
        userDN = requestorDN;
@@ -343,75 +334,467 @@
      }
      // At this point, we should have the user entry.  If a current password
      // was provided, then validate it.  If not, then see if that's OK.
      AttributeType pwType = DirectoryServer.getAttributeType("userpassword");
      if (pwType == null)
      // At this point, we should have the user entry.  Get the associated
      // password policy.
      PasswordPolicyState pwPolicyState;
      try
      {
        pwType = DirectoryServer.getDefaultAttributeType("userPassword");
        pwPolicyState = new PasswordPolicyState(userEntry, false, false);
      }
      catch (DirectoryException de)
      {
        assert debugException(CLASS_NAME, "processExtendedOperation", de);
        operation.setResultCode(DirectoryServer.getServerErrorResultCode());
        int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY;
        operation.appendErrorMessage(getMessage(msgID, String.valueOf(userDN),
                                                de.getErrorMessage()));
        return;
      }
      // Determine whether the user is changing his own password or if it's an
      // administrative reset.
      boolean selfChange = ((userIdentity == null) ||
                            userDN.equals(requestorDN));
      // If the current password was provided, then we'll need to verify whether
      // it was correct.  If it wasn't provided but this is a self change, then
      // make sure that's OK.
      if (oldPassword == null)
      {
        // NYI -- Confirm that this is allowed.
        if (selfChange && pwPolicyState.requireCurrentPassword())
        {
          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          int msgID = MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW;
          operation.appendErrorMessage(getMessage(msgID));
          return;
        }
      }
      else
      {
        // FIXME -- Use a more generic check to determine the correct attribute
        List<Attribute> pwAttrList = userEntry.getAttribute(pwType);
        if ((pwAttrList == null) || pwAttrList.isEmpty())
        if (pwPolicyState.requireSecureAuthentication() &&
            (! operation.getClientConnection().isSecure()))
        {
          // There were no existing passwords, so the validation will fail.
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
          operation.appendErrorMessage(getMessage(msgID));
          int msgID = MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED;
          operation.appendAdditionalLogMessage(getMessage(msgID));
          return;
        }
        AttributeValue matchValue = new AttributeValue(pwType, oldPassword);
        boolean matchFound = false;
        for (Attribute a : pwAttrList)
        if (! pwPolicyState.passwordMatches(oldPassword))
        {
          if (a.hasValue(matchValue))
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
          operation.appendAdditionalLogMessage(getMessage(msgID));
          return;
        }
      }
      // If it is a self password change and we don't allow that, then reject
      // the request.
      if (selfChange && (! pwPolicyState.allowUserPasswordChanges()))
      {
        if (oldPassword == null)
        {
          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          int msgID = MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED;
          operation.appendErrorMessage(getMessage(msgID));
        }
        else
        {
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED;
          operation.appendAdditionalLogMessage(getMessage(msgID));
        }
        return;
      }
      // If we require secure password changes and the connection isn't secure,
      // then reject the request.
      if (pwPolicyState.requireSecurePasswordChanges() &&
          (! operation.getClientConnection().isSecure()))
      {
        if (oldPassword == null)
        {
          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          int msgID = MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED;
          operation.appendErrorMessage(getMessage(msgID));
        }
        else
        {
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED;
          operation.appendAdditionalLogMessage(getMessage(msgID));
        }
        return;
      }
      // If it's a self-change request and the user is within the minimum age,
      // then reject it.
      if (selfChange && pwPolicyState.isWithinMinimumAge())
      {
        if (oldPassword == null)
        {
          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          int msgID = MSGID_EXTOP_PASSMOD_IN_MIN_AGE;
          operation.appendErrorMessage(getMessage(msgID));
        }
        else
        {
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_IN_MIN_AGE;
          operation.appendAdditionalLogMessage(getMessage(msgID));
        }
        return;
      }
      // If the user's password is expired and it's a self-change request, then
      // see if that's OK.
      if ((selfChange && pwPolicyState.isPasswordExpired() &&
          (! pwPolicyState.allowExpiredPasswordChanges())))
      {
        if (oldPassword == null)
        {
          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
          int msgID = MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED;
          operation.appendErrorMessage(getMessage(msgID));
        }
        else
        {
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED;
          operation.appendAdditionalLogMessage(getMessage(msgID));
        }
        return;
      }
      // If the a new password was provided, then peform any appropriate
      // validation on it.  If not, then see if we can generate one.
      boolean generatedPassword = false;
      boolean isPreEncoded      = false;
      if (newPassword == null)
      {
        try
        {
          newPassword = pwPolicyState.generatePassword();
          if (newPassword == null)
          {
            matchFound = true;
            break;
            if (oldPassword == null)
            {
              operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR;
              operation.appendErrorMessage(getMessage(msgID));
            }
            else
            {
              operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
              int msgID = MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR;
              operation.appendAdditionalLogMessage(getMessage(msgID));
            }
            return;
          }
          else
          {
            generatedPassword = true;
          }
        }
        catch (DirectoryException de)
        {
          assert debugException(CLASS_NAME, "processExtendedOperation", de);
          if (oldPassword == null)
          {
            operation.setResultCode(de.getResultCode());
            int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW;
            operation.appendErrorMessage(getMessage(msgID,
                                                    de.getErrorMessage()));
          }
          else
          {
            operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
            int msgID = MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW;
            operation.appendAdditionalLogMessage(getMessage(msgID,
                                                      de.getErrorMessage()));
          }
          return;
        }
      }
      else
      {
        if (pwPolicyState.passwordIsPreEncoded(newPassword))
        {
          // The password modify extended operation isn't intended to be invoked
          // by an internal operation or during synchronization, so we don't
          // need to check for those cases.
          isPreEncoded = true;
          if (! pwPolicyState.allowPreEncodedPasswords())
          {
            if (oldPassword == null)
            {
              operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED;
              operation.appendErrorMessage(getMessage(msgID));
            }
            else
            {
              operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
              int msgID = MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED;
              operation.appendAdditionalLogMessage(getMessage(msgID));
            }
            return;
          }
        }
        else
        {
          if (selfChange || (! pwPolicyState.skipValidationForAdministrators()))
          {
            StringBuilder invalidReason = new StringBuilder();
            if (! pwPolicyState.passwordIsAcceptable(operation, userEntry,
                                                     newPassword,
                                                     invalidReason))
            {
              if (oldPassword == null)
              {
                operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                int msgID = MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW;
                operation.appendErrorMessage(getMessage(msgID,
                               String.valueOf(invalidReason)));
              }
              else
              {
                operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
                int msgID = MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW;
                operation.appendAdditionalLogMessage(getMessage(msgID,
                               String.valueOf(invalidReason)));
              }
              return;
            }
          }
        }
      }
      // Get the encoded forms of the new password.
      List<ByteString> encodedPasswords;
      if (isPreEncoded)
      {
        encodedPasswords = new ArrayList<ByteString>(1);
        encodedPasswords.add(newPassword);
      }
      else
      {
        try
        {
          encodedPasswords = pwPolicyState.encodePassword(newPassword);
        }
        catch (DirectoryException de)
        {
          assert debugException(CLASS_NAME, "processExtendedOperation", de);
          if (oldPassword == null)
          {
            operation.setResultCode(de.getResultCode());
            int msgID = MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD;
            operation.appendErrorMessage(getMessage(msgID,
                                                    de.getErrorMessage()));
          }
          else
          {
            operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
            int msgID = MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD;
            operation.appendAdditionalLogMessage(getMessage(msgID,
                                                      de.getErrorMessage()));
          }
          return;
        }
      }
      // If the current password was provided, then remove all matching values
      // from the user's entry and replace them with the new password.
      // Otherwise replace all password values.
      AttributeType attrType = pwPolicyState.getPasswordAttribute();
      List<Modification> modList = new ArrayList<Modification>();
      if (oldPassword != null)
      {
        // Remove all existing encoded values that match the old password.
        LinkedHashSet<AttributeValue> existingValues =
             pwPolicyState.getPasswordValues();
        LinkedHashSet<AttributeValue> deleteValues =
             new LinkedHashSet<AttributeValue>(existingValues.size());
        if (pwPolicyState.usesAuthPasswordSyntax())
        {
          for (AttributeValue v : existingValues)
          {
            try
            {
              StringBuilder[] components =
                   AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
              PasswordStorageScheme scheme =
                   DirectoryServer.getAuthPasswordStorageScheme(
                        components[0].toString());
              if (scheme == null)
              {
                // The password is encoded using an unknown scheme.  Remove it
                // from the user's entry.
                deleteValues.add(v);
              }
              else
              {
                if (scheme.authPasswordMatches(oldPassword,
                                               components[1].toString(),
                                               components[2].toString()))
                {
                  deleteValues.add(v);
                }
              }
            }
            catch (DirectoryException de)
            {
              assert debugException(CLASS_NAME, "processExtendedOperation", de);
              // We couldn't decode the provided password value, so remove it
              // from the user's entry.
              deleteValues.add(v);
            }
          }
        }
        else
        {
          for (AttributeValue v : existingValues)
          {
            try
            {
              String[] components =
                   UserPasswordSyntax.decodeUserPassword(v.getStringValue());
              PasswordStorageScheme scheme =
                   DirectoryServer.getPasswordStorageScheme(
                        toLowerCase(components[0].toString()));
              if (scheme == null)
              {
                // The password is encoded using an unknown scheme.  Remove it
                // from the user's entry.
                deleteValues.add(v);
              }
              else
              {
                if (scheme.passwordMatches(oldPassword,
                                           new ASN1OctetString(components[1])))
                {
                  deleteValues.add(v);
                }
              }
            }
            catch (DirectoryException de)
            {
              assert debugException(CLASS_NAME, "processExtendedOperation", de);
              // We couldn't decode the provided password value, so remove it
              // from the user's entry.
              deleteValues.add(v);
            }
          }
        }
        if (! matchFound)
        Attribute deleteAttr = new Attribute(attrType, attrType.getNameOrOID(),
                                             deleteValues);
        modList.add(new Modification(ModificationType.DELETE, deleteAttr));
        // Add the new encoded values.
        LinkedHashSet<AttributeValue> addValues =
             new LinkedHashSet<AttributeValue>(encodedPasswords.size());
        for (ByteString s : encodedPasswords)
        {
          // None of the password values matched what the user provided.
          operation.setResultCode(ResultCode.INVALID_CREDENTIALS);
          int msgID = MSGID_EXTOP_PASSMOD_INVALID_OLD_PASSWORD;
          operation.appendErrorMessage(getMessage(msgID));
          return;
          addValues.add(new AttributeValue(attrType, s));
        }
        Attribute addAttr = new Attribute(attrType, attrType.getNameOrOID(),
                                          addValues);
        modList.add(new Modification(ModificationType.ADD, addAttr));
      }
      // See if a new password was provided.  If not, then generate one.
      boolean generatedPassword = false;
      if (newPassword == null)
      else
      {
        // FIXME -- use an extensible algorithm for generating the new password.
        newPassword = new ASN1OctetString("newpassword");
        generatedPassword = true;
        LinkedHashSet<AttributeValue> replaceValues =
             new LinkedHashSet<AttributeValue>(encodedPasswords.size());
        for (ByteString s : encodedPasswords)
        {
          replaceValues.add(new AttributeValue(attrType, s));
        }
        Attribute addAttr = new Attribute(attrType, attrType.getNameOrOID(),
                                          replaceValues);
        modList.add(new Modification(ModificationType.REPLACE, addAttr));
      }
      ArrayList<ASN1OctetString> newPWValues =
           new ArrayList<ASN1OctetString>(1);
      newPWValues.add(newPassword);
      // Update the password changed time for the user entry.
      pwPolicyState.setPasswordChangedTime();
      // If the password was changed by an end user, then clear any reset flag
      // that might exist.  If the password was changed by an administrator,
      // then see if we need to set the reset flag.
      if (selfChange)
      {
        pwPolicyState.setMustChangePassword(false);
      }
      else
      {
        pwPolicyState.setMustChangePassword(pwPolicyState.forceChangeOnReset());
      }
      // Create the modification to update the user's password.
      LDAPAttribute pwAttr = new LDAPAttribute("userPassword", newPWValues);
      ArrayList<LDAPModification> mods = new ArrayList<LDAPModification>(1);
      mods.add(new LDAPModification(ModificationType.REPLACE, pwAttr));
      // Clear any record of grace logins, auth failures, and expiration
      // warnings.
      pwPolicyState.clearAuthFailureTimes();
      pwPolicyState.clearFailureLockout();
      pwPolicyState.clearGraceLoginTimes();
      pwPolicyState.clearWarnedTime();
      // Get the list of modifications from the password policy state and add
      // them to the existing password modifications.
      modList.addAll(pwPolicyState.getModifications());
      // Get an internal connection and use it to perform the modification.
@@ -421,8 +804,7 @@
           InternalClientConnection(authInfo);
      ModifyOperation modifyOperation =
           internalConnection.processModify(
                new ASN1OctetString(userDN.toString()), mods);
           internalConnection.processModify(userDN, modList);
      ResultCode resultCode = modifyOperation.getResultCode();
      if (resultCode != resultCode.SUCCESS)
      {
@@ -441,8 +823,11 @@
      if (generatedPassword)
      {
        ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(1);
        newPassword.setType(TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD);
        valueElements.add(newPassword);
        ASN1OctetString newPWString =
             new ASN1OctetString(TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD,
                                 newPassword.value());
        valueElements.add(newPWString);
        ASN1Sequence valueSequence = new ASN1Sequence(valueElements);
        operation.setResponseValue(new ASN1OctetString(valueSequence.encode()));
opends/src/server/org/opends/server/messages/ExtensionsMessages.java
@@ -3744,6 +3744,128 @@
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to retrieve the password policy for the user.  This takes two
   * arguments, which are the user DN and a message explaining the problem that
   * occurred.
   */
  public static final int MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 354;
  /**
   * The message ID for the message that will be used if a user password change
   * is rejected because the current password was not provided.  This does not
   * take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 355;
  /**
   * The message ID for the message that will be used if a user password change
   * is rejected because the current password was provided over an insecure
   * communication channel.  This does not take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 356;
  /**
   * The message ID for the message that will be used if a user password change
   * is rejected because users are not allowed to change their passwords.  This
   * does not take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 357;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the new password was provided over an insecure
   * communication channel.  This does not take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 358;
  /**
   * The message ID for the message that will be used if a user password change
   * is rejected because the current password is too young.  This does not take
   * any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_IN_MIN_AGE =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 359;
  /**
   * The message ID for the message that will be used if a user password change
   * is rejected because the current password is expired.  This does not take
   * any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 360;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because no new password was given and there is no password
   * generator defined.  This does not take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 361;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to use the password generator to create a new password.  This takes
   * a single argument, which is a message explaining the problem that occurred.
   */
  public static final int MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 362;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the new password provided was pre-encoded.  This does not
   * take any arguments.
   */
  public static final int MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 363;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the new password was rejected by a password validator.
   * This takes a single argument, which is a message explaining the rejection.
   */
  public static final int MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 364;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the new password could not be encoded using the default
   * schemes.  This takes a single argument, which is a message explaining the
   * problem that occurred.
   */
  public static final int MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD =
       CATEGORY_MASK_EXTENSIONS | SEVERITY_MASK_INFORMATIONAL | 365;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -3969,6 +4091,42 @@
                    "The password modify extended operation cannot be " +
                    "processed because the current password provided for the " +
                    "use is invalid.");
    registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_GET_PW_POLICY,
                    "An error occurred while attempting to get the " +
                    "password policy for user %s:  %s.");
    registerMessage(MSGID_EXTOP_PASSMOD_REQUIRE_CURRENT_PW,
                    "The current password must be provided for self password " +
                    "changes.");
    registerMessage(MSGID_EXTOP_PASSMOD_SECURE_AUTH_REQUIRED,
                    "Password modify operations that supply the user's " +
                    "current password must be performed over a secure " +
                    "communication channel.");
    registerMessage(MSGID_EXTOP_PASSMOD_USER_PW_CHANGES_NOT_ALLOWED,
                    "End users are not allowed to change their passwords.");
    registerMessage(MSGID_EXTOP_PASSMOD_SECURE_CHANGES_REQUIRED,
                    "Password changes must be performed over a secure " +
                    "communication channel.");
    registerMessage(MSGID_EXTOP_PASSMOD_IN_MIN_AGE,
                    "The password cannot be changed because the previous " +
                    "password change was too recent.");
    registerMessage(MSGID_EXTOP_PASSMOD_PASSWORD_IS_EXPIRED,
                    "The password cannot be changed because it is expired.");
    registerMessage(MSGID_EXTOP_PASSMOD_NO_PW_GENERATOR,
                    "No new password was provided, and no password generator " +
                    "has been defined that may be used to automatically " +
                    "create a new password.");
    registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_GENERATE_PW,
                    "An error occurred while attempting to create a new " +
                    "password using the password generator:  %s.");
    registerMessage(MSGID_EXTOP_PASSMOD_PRE_ENCODED_NOT_ALLOWED,
                    "The password policy does not allow users to supply " +
                    "pre-encoded passwords.");
    registerMessage(MSGID_EXTOP_PASSMOD_UNACCEPTABLE_PW,
                    "The provided new password failed the validation checks " +
                    "defined in the server:  %s.");
    registerMessage(MSGID_EXTOP_PASSMOD_CANNOT_ENCODE_PASSWORD,
                    "Unable to encode the provided password using the " +
                    "default scheme(s):  %s.");
    registerMessage(MSGID_NULL_KEYMANAGER_NO_MANAGER,