From 54125ed890ad0f8d83727e3d1cb3c61c8f8ab936 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.

---
 opends/src/server/org/opends/server/core/ModifyOperation.java |  530 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 522 insertions(+), 8 deletions(-)

diff --git a/opends/src/server/org/opends/server/core/ModifyOperation.java b/opends/src/server/org/opends/server/core/ModifyOperation.java
index f48d289..f0de197 100644
--- a/opends/src/server/org/opends/server/core/ModifyOperation.java
+++ b/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)
         {

--
Gitblit v1.10.0