From 6f005d31bfdf6e0030712cceee8c9d0ce6dc13a0 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Thu, 10 Aug 2006 18:36:21 +0000
Subject: [PATCH] Update the modify processing code so that it performs the appropriate password policy processing.

---
 opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java |   51 ++-
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java    |  178 ++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/types/Modification.java       |   71 +++++
 opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java     |  530 +++++++++++++++++++++++++++++++++++++
 4 files changed, 798 insertions(+), 32 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java b/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
index f48d289..f0de197 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/opendj-sdk/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)
         {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index f697a42..9518f50 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opendj-sdk/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));
             }
           }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index cb90115..636b779 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/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 " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Modification.java b/opendj-sdk/opends/src/server/org/opends/server/types/Modification.java
index d8b27da..1457bc5 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Modification.java
+++ b/opendj-sdk/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

--
Gitblit v1.10.0