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

neil_a_wilson
10.36.2006 54125ed890ad0f8d83727e3d1cb3c61c8f8ab936
Update the modify processing code so that it performs the appropriate password
policy processing.
4 files modified
830 ■■■■■ changed files
opends/src/server/org/opends/server/core/ModifyOperation.java 530 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java 51 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 178 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Modification.java 71 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -39,6 +39,7 @@
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.plugin.PostOperationPluginResult;
import org.opends.server.api.plugin.PreOperationPluginResult;
@@ -54,6 +55,8 @@
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPException;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.AcceptRejectWarn;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
@@ -65,6 +68,7 @@
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.RDN;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
@@ -112,6 +116,12 @@
  // The modified entry that will be stored in the backend.
  private Entry modifiedEntry;
  // The set of clear-text current passwords (if any were provided).
  private List<AttributeValue> currentPasswords;
  // The set of clear-text new passwords (if any were provided).
  private List<AttributeValue> newPasswords;
  // The set of response controls for this modify operation.
  private List<Control> responseControls;
@@ -176,6 +186,9 @@
    responseControls = new ArrayList<Control>();
    cancelRequest    = null;
    changeNumber     = -1;
    currentPasswords = null;
    newPasswords     = null;
  }
@@ -227,6 +240,9 @@
    responseControls = new ArrayList<Control>();
    cancelRequest    = null;
    changeNumber     = -1;
    currentPasswords = null;
    newPasswords     = null;
  }
@@ -407,6 +423,45 @@
  /**
   * Retrieves the set of clear-text current passwords for the user, if
   * available.  This will only be available if the modify operation contains
   * one or more delete elements that target the password attribute and provide
   * the values to delete in the clear.  It will not be available to pre-parse
   * plugins.
   *
   * @return  The set of clear-text current password values as provided in the
   *          modify request, or <CODE>null</CODE> if there were none or this
   *          information is not yet available.
   */
  public List<AttributeValue> getCurrentPasswords()
  {
    assert debugEnter(CLASS_NAME, "getCurrentPasswords");
    return currentPasswords;
  }
  /**
   * Retrieves the set of clear-text new passwords for the user, if available.
   * This will only be available if the modify operation contains one or more
   * add or replace elements that target the password attribute and provide the
   * values in the clear.  It will not be available to pre-parse plugins.
   *
   * @return  The set of clear-text new passwords as provided in the modify
   *          request, or <CODE>null</CODE> if there were none or this
   *          information is not yet available.
   */
  public List<AttributeValue> getNewPasswords()
  {
    assert debugEnter(CLASS_NAME, "getNewPasswords");
    return newPasswords;
  }
  /**
   * Retrieves the time that processing started for this operation.
   *
   * @return  The time that processing started for this operation.
@@ -1135,6 +1190,27 @@
        }
        // Get the password policy state object for the entry that can be used
        // to perform any appropriate password policy processing.  Also, see if
        // the entry is being updated by the end user or an administrator.
        PasswordPolicyState pwPolicyState;
        boolean selfChange = entryDN.equals(getAuthorizationDN());
        try
        {
          // FIXME -- Need a way to enable debug mode.
          pwPolicyState = new PasswordPolicyState(currentEntry, false, false);
        }
        catch (DirectoryException de)
        {
          assert debugException(CLASS_NAME, "run", de);
          setResultCode(de.getResultCode());
          appendErrorMessage(de.getErrorMessage());
          break modifyProcessing;
        }
        // Create a duplicate of the entry and apply the changes to it.
        modifiedEntry = currentEntry.duplicate();
@@ -1170,6 +1246,75 @@
          }
        }
        // Declare variables used for password policy state processing.
        boolean passwordChanged = false;
        boolean currentPasswordProvided = false;
        int numPasswords;
        if (currentEntry.hasAttribute(pwPolicyState.getPasswordAttribute()))
        {
          // It may actually have more than one, but we can't tell the
          // difference if the values are encoded, and its enough for our
          // purposes just to know that there is at least one.
          numPasswords = 1;
        }
        else
        {
          numPasswords = 0;
        }
        // If it's not an internal or synchronization operation, then iterate
        // through the set of modifications to see if a password is included in
        // the changes.  If so, then add the appropriate state changes to the
        // set of modifications.
        if (! (isInternalOperation() || isSynchronizationOperation()))
        {
          for (Modification m : modifications)
          {
            if (m.getAttribute().getAttributeType().equals(
                     pwPolicyState.getPasswordAttribute()))
            {
              passwordChanged = true;
              break;
            }
          }
          if (passwordChanged)
          {
            // Update the password policy state attributes in the user's entry.
            // If the modification fails, then these changes won't be applied.
            pwPolicyState.setPasswordChangedTime();
            pwPolicyState.clearAuthFailureTimes();
            pwPolicyState.clearFailureLockout();
            pwPolicyState.clearGraceLoginTimes();
            pwPolicyState.clearWarnedTime();
            if ((! selfChange) && pwPolicyState.forceChangeOnReset())
            {
              pwPolicyState.setMustChangePassword(true);
            }
            if (pwPolicyState.getRequiredChangeTime() > 0)
            {
              pwPolicyState.setRequiredChangeTime();
            }
            modifications.addAll(pwPolicyState.getModifications());
          }
          else if(pwPolicyState.mustChangePassword())
          {
            // The user will not be allowed to do anything else before
            // the password gets changed.
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            int msgID = MSGID_MODIFY_MUST_CHANGE_PASSWORD;
            appendErrorMessage(getMessage(msgID));
            break modifyProcessing;
          }
        }
        for (Modification m : modifications)
        {
          Attribute     a = m.getAttribute();
@@ -1181,7 +1326,8 @@
          // synchronization in some way.
          if (t.isNoUserModification())
          {
            if (! (isInternalOperation() || isSynchronizationOperation()))
            if (! (isInternalOperation() || isSynchronizationOperation() ||
                   m.isInternal()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              appendErrorMessage(getMessage(MSGID_MODIFY_ATTR_IS_NO_USER_MOD,
@@ -1192,6 +1338,326 @@
          }
          // If the modification is updating the password attribute, then
          // perform any necessary password policy processing.  This processing
          // should be skipped for internal and synchronization operations.
          boolean isPassword = a.getAttributeType().equals(
                                    pwPolicyState.getPasswordAttribute());
          if (isPassword &&
              (! (isInternalOperation() || isSynchronizationOperation())))
          {
            // If the attribute contains any options, then reject it.  Passwords
            // will not be allowed to have options.
            if (a.hasOptions())
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS;
              appendErrorMessage(getMessage(msgID));
              break modifyProcessing;
            }
            // If it's a self change, then see if that's allowed.
            if (selfChange && (! pwPolicyState.allowUserPasswordChanges()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_MODIFY_NO_USER_PW_CHANGES;
              appendErrorMessage(getMessage(msgID));
              break modifyProcessing;
            }
            // If we require secure password changes, then makes sure it's a
            // secure communication channel.
            if (pwPolicyState.requireSecurePasswordChanges() &&
                (! clientConnection.isSecure()))
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_MODIFY_REQUIRE_SECURE_CHANGES;
              appendErrorMessage(getMessage(msgID));
              break modifyProcessing;
            }
            // If it's a self change and it's not been long enough since the
            // previous change, then reject it.
            if (selfChange && pwPolicyState.isWithinMinimumAge())
            {
              setResultCode(ResultCode.UNWILLING_TO_PERFORM);
              int msgID = MSGID_MODIFY_WITHIN_MINIMUM_AGE;
              appendErrorMessage(getMessage(msgID));
              break modifyProcessing;
            }
            // Check to see whether this will adding, deleting, or replacing
            // password values (increment doesn't make any sense for passwords).
            // Then perform the appropriate type of processing for that kind of
            // modification.
            LinkedHashSet<AttributeValue> pwValues = a.getValues();
            LinkedHashSet<AttributeValue> encodedValues =
                 new LinkedHashSet<AttributeValue>();
            switch (m.getModificationType())
            {
              case ADD:
              case REPLACE:
                int passwordsToAdd = pwValues.size();
                if (m.getModificationType() == ModificationType.ADD)
                {
                  numPasswords += passwordsToAdd;
                }
                else
                {
                  numPasswords = passwordsToAdd;
                }
                // If there were multiple password values provided, then make
                // sure that's OK.
                if ((! pwPolicyState.allowMultiplePasswordValues()) &&
                    (passwordsToAdd > 1))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  int msgID = MSGID_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED;
                  appendErrorMessage(getMessage(msgID));
                  break modifyProcessing;
                }
                // Iterate through the password values and see if any of them
                // are pre-encoded.  If so, then check to see if we'll allow it.
                // Otherwise, store the clear-text values for later validation
                // and update the attribute with the encoded values.
                for (AttributeValue v : pwValues)
                {
                  if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
                  {
                    if (! pwPolicyState.allowPreEncodedPasswords())
                    {
                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                      int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
                      appendErrorMessage(getMessage(msgID));
                      break modifyProcessing;
                    }
                    else
                    {
                      encodedValues.add(v);
                    }
                  }
                  else
                  {
                    if (newPasswords == null)
                    {
                      newPasswords = new LinkedList<AttributeValue>();
                    }
                    newPasswords.add(v);
                    try
                    {
                      for (ByteString s :
                           pwPolicyState.encodePassword(v.getValue()))
                      {
                        encodedValues.add(new AttributeValue(
                                                   a.getAttributeType(), s));
                      }
                    }
                    catch (DirectoryException de)
                    {
                      assert debugException(CLASS_NAME, "run", de);
                      setResultCode(de.getResultCode());
                      appendErrorMessage(de.getErrorMessage());
                      break modifyProcessing;
                    }
                  }
                }
                a.setValues(encodedValues);
                break;
              case DELETE:
                // Iterate through the password values and see if any of them
                // are pre-encoded.  We will never allow pre-encoded passwords
                // for user password changes, but we will allow them for
                // administrators.  For each clear-text value, verify that at
                // least one value in the entry matches and replace the
                // clear-text value with the appropriate encoded forms.
                for (AttributeValue v : pwValues)
                {
                  if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
                  {
                    if (selfChange)
                    {
                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                      int msgID = MSGID_MODIFY_NO_PREENCODED_PASSWORDS;
                      appendErrorMessage(getMessage(msgID));
                      break modifyProcessing;
                    }
                    else
                    {
                      encodedValues.add(v);
                    }
                  }
                  else
                  {
                    List<Attribute> attrList = currentEntry.getAttribute(t);
                    if ((attrList == null) || (attrList.isEmpty()))
                    {
                      setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                      int msgID = MSGID_MODIFY_NO_EXISTING_VALUES;
                      appendErrorMessage(getMessage(msgID));
                      break modifyProcessing;
                    }
                    else
                    {
                      boolean found = false;
                      for (Attribute attr : attrList)
                      {
                        for (AttributeValue av : attr.getValues())
                        {
                          if (pwPolicyState.usesAuthPasswordSyntax())
                          {
                            if (AuthPasswordSyntax.isEncoded(av.getValue()))
                            {
                              try
                              {
                                StringBuilder[] compoenents =
                                     AuthPasswordSyntax.decodeAuthPassword(
                                          av.getStringValue());
                                PasswordStorageScheme scheme =
                                     DirectoryServer.
                                          getAuthPasswordStorageScheme(
                                               compoenents[0].toString());
                                if (scheme != null)
                                {
                                  if (scheme.authPasswordMatches(
                                           v.getValue(),
                                           compoenents[1].toString(),
                                           compoenents[2].toString()))
                                  {
                                    encodedValues.add(av);
                                    found = true;
                                  }
                                }
                              }
                              catch (DirectoryException de)
                              {
                                assert debugException(CLASS_NAME, "run", de);
                                setResultCode(de.getResultCode());
                                int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
                                appendErrorMessage(getMessage(msgID,
                                                        de.getErrorMessage()));
                                break modifyProcessing;
                              }
                            }
                            else
                            {
                              if (av.equals(v))
                              {
                                encodedValues.add(v);
                                found = true;
                              }
                            }
                          }
                          else
                          {
                            if (UserPasswordSyntax.isEncoded(av.getValue()))
                            {
                              try
                              {
                                String[] compoenents =
                                     UserPasswordSyntax.decodeUserPassword(
                                          av.getStringValue());
                                PasswordStorageScheme scheme =
                                     DirectoryServer.getPasswordStorageScheme(
                                          toLowerCase(
                                               compoenents[0].toString()));
                                if (scheme != null)
                                {
                                  if (scheme.passwordMatches(
                                        v.getValue(),
                                        new ASN1OctetString(compoenents[1])))
                                  {
                                    encodedValues.add(av);
                                    found = true;
                                  }
                                }
                              }
                              catch (DirectoryException de)
                              {
                                assert debugException(CLASS_NAME, "run", de);
                                setResultCode(de.getResultCode());
                                int msgID = MSGID_MODIFY_CANNOT_DECODE_PW;
                                appendErrorMessage(getMessage(msgID,
                                                        de.getErrorMessage()));
                                break modifyProcessing;
                              }
                            }
                            else
                            {
                              if (av.equals(v))
                              {
                                encodedValues.add(v);
                                found = true;
                              }
                            }
                          }
                        }
                      }
                      if (found)
                      {
                        if (currentPasswords == null)
                        {
                          currentPasswords = new LinkedList<AttributeValue>();
                        }
                        currentPasswords.add(v);
                        numPasswords--;
                      }
                      else
                      {
                        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                        int msgID = MSGID_MODIFY_INVALID_PASSWORD;
                        appendErrorMessage(getMessage(msgID));
                        break modifyProcessing;
                      }
                      currentPasswordProvided = true;
                    }
                  }
                }
                a.setValues(encodedValues);
                break;
              default:
                setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                int msgID = MSGID_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD;
                appendErrorMessage(getMessage(msgID,
                     String.valueOf(m.getModificationType()), a.getName()));
                break modifyProcessing;
            }
          }
          switch (m.getModificationType())
          {
            case ADD:
@@ -1680,6 +2146,61 @@
        }
        // If there was a password change, then perform any additional checks
        // that may be necessary.
        if (passwordChanged)
        {
          // If it was a self change, then see if the current password was
          // provided and handle accordingly.
          if (selfChange && pwPolicyState.requireCurrentPassword() &&
              (! currentPasswordProvided))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            int msgID = MSGID_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW;
            appendErrorMessage(getMessage(msgID));
            break modifyProcessing;
          }
          // If this change would result in multiple password values, then see
          // if that's OK.
          if ((numPasswords > 1) &&
              (! pwPolicyState.allowMultiplePasswordValues()))
          {
            setResultCode(ResultCode.UNWILLING_TO_PERFORM);
            int msgID = MSGID_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED;
            appendErrorMessage(getMessage(msgID));
            break modifyProcessing;
          }
          // If any of the password values should be validated, then do so now.
          if (selfChange || (! pwPolicyState.skipValidationForAdministrators()))
          {
            if (newPasswords != null)
            {
              for (AttributeValue v : newPasswords)
              {
                StringBuilder invalidReason = new StringBuilder();
                if (! pwPolicyState.passwordIsAcceptable(this, modifiedEntry,
                                                         v.getValue(),
                                                         invalidReason))
                {
                  setResultCode(ResultCode.UNWILLING_TO_PERFORM);
                  int msgID = MSGID_MODIFY_PW_VALIDATION_FAILED;
                  appendErrorMessage(getMessage(msgID,
                                                invalidReason.toString()));
                  break modifyProcessing;
                }
              }
            }
          }
        }
        // Make sure that the new entry is valid per the server schema.
        if (DirectoryServer.checkSchema())
        {
@@ -1695,13 +2216,6 @@
        }
        // Check to see if there are any passwords included in the request and
        // if they are valid in accordance with the password policies associated
        // with the user.  Also perform any encoding that might be required by
        // the password storage schemes.
        // NYI
        // Check for and handle a request to cancel this operation.
        if (cancelRequest != null)
        {
opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -1050,7 +1050,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a));
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
    }
  }
@@ -1192,7 +1192,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a));
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
    }
    else
@@ -1213,7 +1213,7 @@
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
                                           new Attribute(type)));
                                           new Attribute(type), true));
      }
    }
  }
@@ -1430,7 +1430,8 @@
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.DELETE, a));
              modifications.add(new Modification(ModificationType.DELETE, a,
                                                 true));
            }
          }
        }
@@ -1457,7 +1458,7 @@
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
                                             new Attribute(type), true));
        }
      }
    }
@@ -1543,7 +1544,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.ADD, addAttr));
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
  }
@@ -1587,7 +1588,7 @@
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
                                         new Attribute(type), true));
    }
  }
@@ -1737,7 +1738,7 @@
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
                                             new Attribute(type), true));
        }
        if (debug)
@@ -1832,7 +1833,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a));
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
  }
@@ -1873,7 +1874,7 @@
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
                                         new Attribute(type), true));
    }
  }
@@ -2079,7 +2080,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a));
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    if (debug)
@@ -2403,7 +2404,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a));
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
    }
    else
@@ -2422,7 +2423,7 @@
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
                                           new Attribute(type)));
                                           new Attribute(type), true));
      }
    }
  }
@@ -3005,7 +3006,7 @@
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a));
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
    }
  }
@@ -3101,7 +3102,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a));
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    if (debug)
@@ -3130,7 +3131,7 @@
    else
    {
      Attribute a = new Attribute(type);
      modifications.add(new Modification(ModificationType.REPLACE, a));
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    if (debug)
@@ -3207,7 +3208,7 @@
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type)));
                                             new Attribute(type), true));
        }
      }
    }
@@ -3313,7 +3314,7 @@
    }
    else
    {
      modifications.add(new Modification(ModificationType.ADD, addAttr));
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
  }
@@ -3355,7 +3356,7 @@
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type)));
                                         new Attribute(type), true));
    }
  }
@@ -3864,7 +3865,8 @@
          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
          if (! updateEntry)
          {
            modifications.add(new Modification(ModificationType.DELETE, a));
            modifications.add(new Modification(ModificationType.DELETE, a,
                                               true));
          }
          if (! addedValues.isEmpty())
@@ -3873,7 +3875,8 @@
                                         addedValues);
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.ADD, a2));
              modifications.add(new Modification(ModificationType.ADD, a2,
                                                 true));
            }
          }
@@ -4042,7 +4045,8 @@
          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
          if (! updateEntry)
          {
            modifications.add(new Modification(ModificationType.DELETE, a));
            modifications.add(new Modification(ModificationType.DELETE, a,
                                               true));
          }
          if (! addedValues.isEmpty())
@@ -4051,7 +4055,8 @@
                                         addedValues);
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.ADD, a2));
              modifications.add(new Modification(ModificationType.ADD, a2,
                                                 true));
            }
          }
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5578,6 +5578,144 @@
  /**
   * The message ID for the message that will be used if a change to the
   * password attribute included one or more attribute options.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 532;
  /**
   * The message ID for the message that will be used if a user password change
   * is refused because users cannot change their own passwords.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_NO_USER_PW_CHANGES =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 533;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because it was not attempted over a secure channel.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_REQUIRE_SECURE_CHANGES =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 534;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the password was within the minimum age.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_WITHIN_MINIMUM_AGE =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 535;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because multiple password values were provided.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 536;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because the password was pre-encoded.  This does not take any
   * arguments.
   */
  public static final int MSGID_MODIFY_NO_PREENCODED_PASSWORDS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 537;
  /**
   * The message ID for the message that will be used if a password change is
   * rejected because it included an invalid modification type on the password
   * attribute.  This does not take any arguments.
   */
  public static final int MSGID_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 538;
  /**
   * The message ID for the message that will be used if an attempt to delete a
   * user password value is rejected because there are no existing passwords in
   * the user's entry.  This does not take any arguments.
   */
  public static final int MSGID_MODIFY_NO_EXISTING_VALUES =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 539;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to decode a user password.  This takes a single argument, which
   * is a message explaining the problem that occurred.
   */
  public static final int MSGID_MODIFY_CANNOT_DECODE_PW =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 540;
  /**
   * The message ID for the message that will be used if a provided password to
   * delete does not match any passwords in the user's entry.  This does not
   * take any arguments.
   */
  public static final int MSGID_MODIFY_INVALID_PASSWORD =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 541;
  /**
   * The message ID for the message that will be used if the user did not
   * provide the current password.  This does not take any arguments.
   */
  public static final int MSGID_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 542;
  /**
   * The message ID for the message that will be used if the password change
   * would result in multiple passwords.  This does not take any arguments.
   */
  public static final int MSGID_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 543;
  /**
   * The message ID for the message that will be used if password validation
   * fails.  This takes a single argument, which is a message explaining the
   * rejection.
   */
  public static final int MSGID_MODIFY_PW_VALIDATION_FAILED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 544;
  /**
   * The message ID for the message that will be used if the user's password
   * needs to be changed but the modification doesn't update the password.  This
   * does not take any arguments.
   */
  public static final int MSGID_MODIFY_MUST_CHANGE_PASSWORD =
       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 545;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -6642,10 +6780,41 @@
                    "contained a critical control with OID %s that is not " +
                    "supported by the Directory Server for this type of " +
                    "operation.");
    registerMessage(MSGID_MODIFY_MUST_CHANGE_PASSWORD,
                    "You must change your password before you will be " +
                    "allowed to perform any other operations.");
    registerMessage(MSGID_MODIFY_ATTR_IS_NO_USER_MOD,
                    "Entry %s cannot be modified because the modification " +
                    "attempted to update attribute %s which is defined as " +
                    "NO-USER-MODIFICATION in the server schema.");
    registerMessage(MSGID_MODIFY_PASSWORDS_CANNOT_HAVE_OPTIONS,
                    "Attributes used to hold user passwords are not allowed " +
                    "to have any attribute options.");
    registerMessage(MSGID_MODIFY_NO_USER_PW_CHANGES,
                    "Users are not allowed to change their own passwords.");
    registerMessage(MSGID_MODIFY_REQUIRE_SECURE_CHANGES,
                    "Password changes must be performed over a secure " +
                    "authentication channel.");
    registerMessage(MSGID_MODIFY_WITHIN_MINIMUM_AGE,
                    "The password cannot be changed because it has not been " +
                    "long enough since the last password change.");
    registerMessage(MSGID_MODIFY_MULTIPLE_VALUES_NOT_ALLOWED,
                    "Multiple password values are not allowed in user " +
                    "entries.");
    registerMessage(MSGID_MODIFY_NO_PREENCODED_PASSWORDS,
                    "User passwords may not be provided in pre-encoded form.");
    registerMessage(MSGID_MODIFY_NO_EXISTING_VALUES,
                    "The user entry does not have any existing passwords to " +
                    "remove.");
    registerMessage(MSGID_MODIFY_CANNOT_DECODE_PW,
                    "An error occurred while attempting to decode an " +
                    "existing user password:  %s.");
    registerMessage(MSGID_MODIFY_INVALID_PASSWORD,
                    "The provided user password does not match any password " +
                    "in the user's entry.");
    registerMessage(MSGID_MODIFY_INVALID_MOD_TYPE_FOR_PASSWORD,
                    "Invalid modification type %s attempted on password " +
                    "attribute %s.");
    registerMessage(MSGID_MODIFY_ADD_NO_VALUES,
                    "Entry %s cannot be modified because the modification " +
                    "contained an add component for attribute %s but no " +
@@ -6696,6 +6865,15 @@
                    "Entry %s cannot be modified because an attempt was made " +
                    "to increment the value of attribute %s but that " +
                    "attribute did not have any values in the target entry.");
    registerMessage(MSGID_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW,
                    "The password policy requires that user password changes " +
                    "include the current password in the request.");
    registerMessage(MSGID_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED,
                    "The password change would result in multiple password " +
                    "values in the user entry, which is not allowed.");
    registerMessage(MSGID_MODIFY_PW_VALIDATION_FAILED,
                    "The provided password value was rejected by a password " +
                    "validator:  %s.");
    registerMessage(MSGID_MODIFY_INCREMENT_REQUIRES_INTEGER_VALUE,
                    "Entry %s cannot be modified because an attempt was " +
                    "made to increment the value of attribute %s but the " +
opends/src/server/org/opends/server/types/Modification.java
@@ -50,6 +50,11 @@
  // The attribute for this modification.
  private Attribute attribute;
  // Indicates whether this modification was generated by internal
  // processing and therefore should not be subject to
  // no-user-modification and related checks.
  private boolean isInternal;
  // The modification type for this modification.
  private ModificationType modificationType;
@@ -63,7 +68,7 @@
   * @param  attribute         The attribute for this modification.
   */
  public Modification(ModificationType modificationType,
  Attribute attribute)
                      Attribute attribute)
  {
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(modificationType),
@@ -71,6 +76,33 @@
    this.modificationType = modificationType;
    this.attribute        = attribute;
    isInternal = false;
  }
  /**
   * Creates a new modification with the provided information.
   *
   * @param  modificationType  The modification type for this
   *                           modification.
   * @param  attribute         The attribute for this modification.
   * @param  isInternal        Indicates whether this is an internal
   *                           modification and therefore should not
   *                           be subject to no-user-modification and
   *                           related checks.
   */
  public Modification(ModificationType modificationType,
                      Attribute attribute, boolean isInternal)
  {
    assert debugConstructor(CLASS_NAME,
                            String.valueOf(modificationType),
                            String.valueOf(attribute));
    this.modificationType = modificationType;
    this.attribute        = attribute;
    this.isInternal       = isInternal;
  }
@@ -135,6 +167,43 @@
  /**
   * Indicates whether this is modification was created by internal
   * processing and should not be subject to no-user-modification and
   * related checks.
   *
   * @return  <CODE>true</CODE> if this is an internal modification,
   *          or <CODE>false</CODE> if not.
   */
  public boolean isInternal()
  {
    assert debugEnter(CLASS_NAME, "isInternal");
    return isInternal;
  }
  /**
   * Specifies whether this modification was created by internal
   * processing and should not be subject to no-user-modification and
   * related checks.
   *
   * @param  isInternal  Specifies whether this modification was
   *                     created by internal processing and should
   *                     not be subject to no-user-modification and
   *                     related checks.
   */
  public void setInternal(boolean isInternal)
  {
    assert debugEnter(CLASS_NAME, "setInternal",
                      String.valueOf(isInternal));
    this.isInternal = isInternal;
  }
  /**
   * Indicates whether the provided object is equal to this
   * modification.  It will only be considered equal if the object is
   * a modification with the same modification type and an attribute