From 3883d2297c3422d8aec2b40530c2d2b0a00ee57d Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 25 Aug 2011 16:27:28 +0000
Subject: [PATCH] Final refactoring work for OPENDJ-262: Implement pass through authentication (PTA)

---
 opends/src/server/org/opends/server/api/AuthenticationPolicy.java                                                            |  244 +++++++
 opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java                                                       |   28 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java                            |  166 ++--
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java                              |  349 +++++----
 opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java                                     |   21 
 opends/src/server/org/opends/server/extensions/SASLContext.java                                                              |   54 +
 opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java                                                  |   10 
 opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java                           |   10 
 opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java                         |   11 
 opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java                                              |    8 
 opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java                                                       |   56 
 opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java                               |   56 -
 opends/src/server/org/opends/server/core/SearchOperationBasis.java                                                           |   67 +
 opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java                                          |   56 +
 opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java                                              |   20 
 opends/src/server/org/opends/server/core/PasswordPolicyState.java                                                            |  598 ++---------------
 opends/src/server/org/opends/server/api/AuthenticationPolicyState.java                                                       |  143 ++++
 opends/src/server/org/opends/server/core/PasswordPolicy.java                                                                 |   30 
 opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java                                                |   41 
 opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java |   11 
 opends/src/messages/messages/extension.properties                                                                            |   11 
 opends/src/server/org/opends/server/core/CoreConfigManager.java                                                              |    2 
 opends/src/server/org/opends/server/types/AccountStatusNotification.java                                                     |    2 
 23 files changed, 1,016 insertions(+), 978 deletions(-)

diff --git a/opends/src/messages/messages/extension.properties b/opends/src/messages/messages/extension.properties
index b70ff7e..c5736f2 100644
--- a/opends/src/messages/messages/extension.properties
+++ b/opends/src/messages/messages/extension.properties
@@ -587,7 +587,7 @@
 INFO_SASLCRAMMD5_UPDATED_USER_BASE_DN_191=Attribute ds-cfg-user-base-dn in \
  configuration entry %s has been updated.  The DN %s will now be used as the \
  search base when looking up user entries based on their username
- INFO_SASL_UNSUPPORTED_CALLBACK_192=An unsupported or unexpected callback was \
+INFO_SASL_UNSUPPORTED_CALLBACK_192=An unsupported or unexpected callback was \
  provided to the SASL server for use during %s authentication:  %s
 MILD_ERR_SASL_NO_CREDENTIALS_193=The client connection included \
  %s state information, indicating that the client was in the process \
@@ -1401,7 +1401,7 @@
 SEVERE_ERR_SASLDIGESTMD5_PROTOCOL_ERROR_570=SASL DIGEST MD5 protocol error: %s
 INFO_LOG_EXTENSION_INFORMATION_571=Loaded extension from file '%s' (build %s, \
  revision %s)
- SEVERE_ERR_SASL_CREATE_SASL_SERVER_FAILED_572=Failed to create a SASL server \
+SEVERE_ERR_SASL_CREATE_SASL_SERVER_FAILED_572=Failed to create a SASL server \
  for SASL mechanism %s using a server FQDN of %s
 SEVERE_ERR_SASL_GSSAPI_KEYTAB_INVALID_573=GSSAPI SASL mechanism handler initalization \
 failed because the keytab file %s does not exist
@@ -1427,3 +1427,10 @@
  character sets: %s
 MILD_ERR_STATICMEMBERS_CANNOT_DECODE_DN_582=An error occurred while \
  attempting to decode member's DN %s of static group %s:  %s
+MILD_ERR_SASL_ACCOUNT_NOT_LOCAL_583=SASL %s authentication \
+ is not supported for user %s because the account is not managed locally
+MILD_ERR_EXTOP_PASSMOD_ACCOUNT_NOT_LOCAL_584=Password modification is not \
+ supported for user %s because the account is not managed locally
+MILD_ERR_EXTOP_PWPSTATE_ACCOUNT_NOT_LOCAL_585=The password policy state \
+ extended operation is not supported for user %s because the account is not \
+ managed locally
diff --git a/opends/src/server/org/opends/server/api/AuthenticationPolicy.java b/opends/src/server/org/opends/server/api/AuthenticationPolicy.java
index aeb4ef2..5054186 100644
--- a/opends/src/server/org/opends/server/api/AuthenticationPolicy.java
+++ b/opends/src/server/org/opends/server/api/AuthenticationPolicy.java
@@ -29,7 +29,20 @@
 
 
 
-import org.opends.server.types.DN;
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
+import static org.opends.server.loggers.ErrorLogger.logError;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugLogger;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.*;
+import org.opends.server.util.TimeThread;
 
 
 
@@ -39,6 +52,173 @@
 public abstract class AuthenticationPolicy
 {
   /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = DebugLogger.getTracer();
+
+
+
+  /**
+   * Returns the authentication policy for the user provided user. The following
+   * algorithm is used in order to obtain the appropriate authentication policy:
+   * <ul>
+   * <li>if the user entry contains the {@code ds-pwp-password-policy-dn}
+   * attribute (whether real or virtual), then the referenced authentication
+   * policy will be returned
+   * <li>otherwise, a search is performed in order to find the nearest
+   * applicable password policy sub-entry to the user entry,
+   * <li>otherwise, the default password policy will be returned.
+   * </ul>
+   *
+   * @param userEntry
+   *          The user entry.
+   * @param useDefaultOnError
+   *          Indicates whether the server should fall back to using the default
+   *          password policy if there is a problem with the configured policy
+   *          for the user.
+   * @return The password policy for the user.
+   * @throws DirectoryException
+   *           If a problem occurs while attempting to determine the password
+   *           policy for the user.
+   */
+  public final static AuthenticationPolicy forUser(Entry userEntry,
+      boolean useDefaultOnError) throws DirectoryException
+  {
+    // First check to see if the ds-pwp-password-policy-dn is present.
+    String userDNString = userEntry.getDN().toString();
+    AttributeType type = DirectoryServer.getAttributeType(
+        OP_ATTR_PWPOLICY_POLICY_DN, true);
+    List<Attribute> attrList = userEntry.getAttribute(type);
+
+    if (attrList != null)
+    {
+      for (Attribute a : attrList)
+      {
+        if (a.isEmpty()) continue;
+
+        AttributeValue v = a.iterator().next();
+        DN subentryDN;
+        try
+        {
+          subentryDN = DN.decode(v.getValue());
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+
+          if (debugEnabled())
+          {
+            TRACER.debugError("Could not parse password policy subentry "
+                + "DN %s for user %s: %s", v.getValue().toString(),
+                userDNString, stackTraceToSingleLineString(e));
+          }
+
+          Message message = ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN
+              .get(v.getValue().toString(), userDNString, e.getMessage());
+          if (useDefaultOnError)
+          {
+            logError(message);
+            return DirectoryServer.getDefaultPasswordPolicy();
+          }
+          else
+          {
+            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message,
+                e);
+          }
+        }
+
+        AuthenticationPolicy policy = DirectoryServer
+            .getAuthenticationPolicy(subentryDN);
+        if (policy == null)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugError("Password policy subentry %s for user %s "
+                + "is not defined in the Directory Server.",
+                String.valueOf(subentryDN), userDNString);
+          }
+
+          Message message = ERR_PWPSTATE_NO_SUCH_POLICY.get(userDNString,
+              String.valueOf(subentryDN));
+          if (useDefaultOnError)
+          {
+            logError(message);
+            return DirectoryServer.getDefaultPasswordPolicy();
+          }
+          else
+          {
+            throw new DirectoryException(
+                DirectoryServer.getServerErrorResultCode(), message);
+          }
+        }
+
+        if (debugEnabled())
+        {
+          TRACER.debugInfo("Using password policy subentry %s for user %s.",
+              String.valueOf(subentryDN), userDNString);
+        }
+
+        return policy;
+      }
+    }
+
+    // The ds-pwp-password-policy-dn attribute was not present, so instead
+    // search for the nearest applicable sub-entry.
+    List<SubEntry> pwpSubEntries = DirectoryServer.getSubentryManager()
+        .getSubentries(userEntry);
+    if ((pwpSubEntries != null) && (!pwpSubEntries.isEmpty()))
+    {
+      for (SubEntry subentry : pwpSubEntries)
+      {
+        try
+        {
+          if (subentry.getEntry().isPasswordPolicySubentry())
+          {
+            AuthenticationPolicy policy = DirectoryServer
+                .getAuthenticationPolicy(subentry.getDN());
+            if (policy == null)
+            {
+              // This shouldn't happen but if it does debug log
+              // this problem and fall back to default policy.
+              if (debugEnabled())
+              {
+                TRACER.debugError("Found unknown password policy subentry "
+                    + "DN %s for user %s", subentry.getDN().toString(),
+                    userDNString);
+              }
+              break;
+            }
+            return policy;
+          }
+        }
+        catch (Exception e)
+        {
+          if (debugEnabled())
+          {
+            TRACER.debugError("Could not parse password policy subentry "
+                + "DN %s for user %s: %s", subentry.getDN().toString(),
+                userDNString, stackTraceToSingleLineString(e));
+          }
+        }
+      }
+    }
+
+    // No authentication policy found, so use the global default.
+    if (debugEnabled())
+    {
+      TRACER.debugInfo("Using the default password policy for user %s",
+          userDNString);
+    }
+
+    return DirectoryServer.getDefaultPasswordPolicy();
+  }
+
+
+
+  /**
    * Creates a new abstract authentication policy.
    */
   protected AuthenticationPolicy()
@@ -60,6 +240,68 @@
 
 
   /**
+   * Returns {@code true} if this authentication policy is a password policy and
+   * the methods {@link #createAuthenticationPolicyState(Entry)} and
+   * {@link #createAuthenticationPolicyState(Entry, long)} will return a
+   * {@code PasswordPolicyState}.
+   * <p>
+   * The default implementation is to return {@code false}.
+   *
+   * @return {@code true} if this authentication policy is a password policy,
+   *         otherwise {@code false}.
+   */
+  public boolean isPasswordPolicy()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Returns the authentication policy state object for the provided user using
+   * the current time as the basis for all time-based state logic (such as
+   * expiring passwords).
+   * <p>
+   * The default implementation is to call
+   * {@link #createAuthenticationPolicyState(Entry, long)} with the current
+   * time.
+   *
+   * @param userEntry
+   *          The user's entry.
+   * @return The authentication policy state object for the provided user.
+   * @throws DirectoryException
+   *           If a problem occurs while attempting to initialize the state
+   *           object from the provided user entry.
+   */
+  public AuthenticationPolicyState createAuthenticationPolicyState(
+      Entry userEntry) throws DirectoryException
+  {
+    return createAuthenticationPolicyState(userEntry, TimeThread.getTime());
+  }
+
+
+
+  /**
+   * Returns an authentication policy state object for the provided user using
+   * the specified time as the basis for all time-based state logic (such as
+   * expiring passwords).
+   *
+   * @param userEntry
+   *          The user's entry.
+   * @param time
+   *          The time since the epoch to use for all time-based state logic
+   *          (such as expiring passwords).
+   * @return The authentication policy state object for the provided user.
+   * @throws DirectoryException
+   *           If a problem occurs while attempting to initialize the state
+   *           object from the provided user entry.
+   */
+  public abstract AuthenticationPolicyState createAuthenticationPolicyState(
+      Entry userEntry, long time) throws DirectoryException;
+
+
+
+  /**
    * Performs any necessary work to finalize this authentication policy.
    * <p>
    * The default implementation is to do nothing.
diff --git a/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java b/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
new file mode 100644
index 0000000..b71ae6c
--- /dev/null
+++ b/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
@@ -0,0 +1,143 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2011 ForgeRock AS.
+ */
+
+package org.opends.server.api;
+
+
+
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+
+
+
+/**
+ * The authentication policy context associated with a user's entry, which is
+ * responsible for managing the user's account, their password, as well as
+ * authenticating the user.
+ */
+public abstract class AuthenticationPolicyState
+{
+  /**
+   * Returns the authentication policy state for the user provided user. This
+   * method is equivalent to the following:
+   *
+   * <pre>
+   * AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+   *     useDefaultOnError);
+   * AuthenticationPolicyState state = policy
+   *     .createAuthenticationPolicyState(userEntry);
+   * </pre>
+   *
+   * See the documentation of {@link AuthenticationPolicy#forUser} for a
+   * description of the algorithm used to find a user's authentication policy.
+   *
+   * @param userEntry
+   *          The user entry.
+   * @param useDefaultOnError
+   *          Indicates whether the server should fall back to using the default
+   *          password policy if there is a problem with the configured policy
+   *          for the user.
+   * @return The password policy for the user.
+   * @throws DirectoryException
+   *           If a problem occurs while attempting to determine the password
+   *           policy for the user.
+   * @see AuthenticationPolicy#forUser(Entry, boolean)
+   */
+  public final static AuthenticationPolicyState forUser(Entry userEntry,
+      boolean useDefaultOnError) throws DirectoryException
+  {
+    AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+        useDefaultOnError);
+    return policy.createAuthenticationPolicyState(userEntry);
+  }
+
+
+
+  /**
+   * Creates a new abstract authentication policy context.
+   */
+  protected AuthenticationPolicyState()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Returns {@code true} if the provided password value matches any of the
+   * user's passwords.
+   *
+   * @param password
+   *          The user-provided password to verify.
+   * @return {@code true} if the provided password value matches any of the
+   *         user's passwords.
+   * @throws DirectoryException
+   *           If verification unexpectedly failed.
+   */
+  public abstract boolean passwordMatches(ByteString password)
+      throws DirectoryException;
+
+
+
+  /**
+   * Returns the authentication policy associated with this state.
+   *
+   * @return The authentication policy associated with this state.
+   */
+  public abstract AuthenticationPolicy getAuthenticationPolicy();
+
+
+
+  /**
+   * Performs any finalization required after a bind operation has completed.
+   * Implementations may perform internal operations in order to persist
+   * internal state to the user's entry if needed.
+   *
+   * @throws DirectoryException
+   *           If a problem occurs during finalization.
+   */
+  public void finalizeStateAfterBind() throws DirectoryException
+  {
+    // Do nothing by default.
+  }
+
+
+
+  /**
+   * Returns {@code true} if this authentication policy state is associated with
+   * a password policy and the method {@link #getAuthenticationPolicy} will
+   * return a {@code PasswordPolicy}.
+   *
+   * @return {@code true} if this authentication policy state is associated with
+   *         a password policy, otherwise {@code false}.
+   */
+  public boolean isPasswordPolicy()
+  {
+    return getAuthenticationPolicy().isPasswordPolicy();
+  }
+}
diff --git a/opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java b/opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java
index b45f309..3f85360 100644
--- a/opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java
+++ b/opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2009 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 package org.opends.server.controls;
 import org.opends.messages.Message;
@@ -189,12 +190,7 @@
    */
   public PasswordPolicyResponseControl()
   {
-    super(OID_PASSWORD_POLICY_CONTROL, false);
-
-
-    warningType  = null;
-    errorType    = null;
-    warningValue = -1;
+    this(false, null, -1, null);
   }
 
 
diff --git a/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java b/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
index 54ab358..dd4103a 100644
--- a/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
+++ b/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 package org.opends.server.controls;
 import org.opends.messages.Message;
@@ -31,6 +32,7 @@
 import java.util.concurrent.locks.Lock;
 import java.io.IOException;
 
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.protocols.asn1.*;
@@ -323,19 +325,25 @@
 
       // FIXME -- We should provide some mechanism for enabling debug
       // processing.
-      PasswordPolicyState pwpState = new PasswordPolicyState(userEntry, false);
-      if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
-          pwpState.lockedDueToFailures() ||
-          pwpState.lockedDueToIdleInterval() ||
-          pwpState.lockedDueToMaximumResetAge() ||
-          pwpState.isPasswordExpired())
+      AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+          false);
+      if (policy.isPasswordPolicy())
       {
-        Message message =
-            ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
-        throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
+        PasswordPolicyState pwpState = (PasswordPolicyState) policy
+            .createAuthenticationPolicyState(userEntry);
+        if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
+            pwpState.lockedDueToFailures() ||
+            pwpState.lockedDueToIdleInterval() ||
+            pwpState.lockedDueToMaximumResetAge() ||
+            pwpState.isPasswordExpired())
+        {
+          Message message = ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String
+              .valueOf(authzDN));
+          throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+              message);
+        }
       }
 
-
       // If we've made it here, then the user is acceptable.
       return userEntry;
     }
diff --git a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
index d5f576d..079bceb 100644
--- a/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
+++ b/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 package org.opends.server.controls;
 import org.opends.messages.Message;
@@ -32,6 +33,7 @@
 import java.util.concurrent.locks.Lock;
 import java.io.IOException;
 
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.api.IdentityMapper;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PasswordPolicyState;
@@ -274,20 +276,7 @@
 
           // FIXME -- We should provide some mechanism for enabling debug
           // processing.
-          PasswordPolicyState pwpState =
-               new PasswordPolicyState(userEntry, false);
-          if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
-              pwpState.lockedDueToFailures() ||
-              pwpState.lockedDueToIdleInterval() ||
-              pwpState.lockedDueToMaximumResetAge() ||
-              pwpState.isPasswordExpired())
-          {
-            Message message =
-                ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
-            throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
-                                         message);
-          }
-
+          checkAccountIsUsable(userEntry);
 
           // If we've made it here, then the user is acceptable.
           return userEntry;
@@ -327,19 +316,7 @@
       {
         // FIXME -- We should provide some mechanism for enabling debug
         // processing.
-        PasswordPolicyState pwpState =
-             new PasswordPolicyState(userEntry, false);
-        if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
-            pwpState.lockedDueToFailures() ||
-            pwpState.lockedDueToIdleInterval() ||
-            pwpState.lockedDueToMaximumResetAge() ||
-            pwpState.isPasswordExpired())
-        {
-          Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(
-              String.valueOf(userEntry.getDN()));
-          throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
-                                       message);
-        }
+        checkAccountIsUsable(userEntry);
 
         return userEntry;
       }
@@ -353,6 +330,31 @@
 
 
 
+  private void checkAccountIsUsable(Entry userEntry)
+      throws DirectoryException
+  {
+    AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+        false);
+    if (policy.isPasswordPolicy())
+    {
+      PasswordPolicyState pwpState = (PasswordPolicyState) policy
+          .createAuthenticationPolicyState(userEntry);
+      if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
+          pwpState.lockedDueToFailures() ||
+          pwpState.lockedDueToIdleInterval() ||
+          pwpState.lockedDueToMaximumResetAge() ||
+          pwpState.isPasswordExpired())
+      {
+        Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String
+            .valueOf(userEntry.getDN()));
+        throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
+            message);
+      }
+    }
+  }
+
+
+
   /**
    * Appends a string representation of this proxied auth v2 control to the
    * provided buffer.
diff --git a/opends/src/server/org/opends/server/core/CoreConfigManager.java b/opends/src/server/org/opends/server/core/CoreConfigManager.java
index 2aebb66..3dfabb8 100644
--- a/opends/src/server/org/opends/server/core/CoreConfigManager.java
+++ b/opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -420,7 +420,7 @@
     DN defaultPasswordPolicyDN = configuration.getDefaultPasswordPolicyDN();
     AuthenticationPolicy policy = DirectoryServer
         .getAuthenticationPolicy(defaultPasswordPolicyDN);
-    if (!(policy instanceof PasswordPolicy))
+    if (!policy.isPasswordPolicy())
     {
       Message message =
         ERR_CONFIG_PWPOLICY_CANNOT_CHANGE_DEFAULT_POLICY_WRONG_TYPE
diff --git a/opends/src/server/org/opends/server/core/PasswordPolicy.java b/opends/src/server/org/opends/server/core/PasswordPolicy.java
index e937944..884839b 100644
--- a/opends/src/server/org/opends/server/core/PasswordPolicy.java
+++ b/opends/src/server/org/opends/server/core/PasswordPolicy.java
@@ -37,6 +37,9 @@
 import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn.*;
 import org.opends.server.api.*;
 import org.opends.server.types.AttributeType;
+import org.opends.server.types.DN;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
 
 
 
@@ -58,6 +61,13 @@
 
 
   /**
+   * {@inheritDoc}
+   */
+  public abstract DN getDN();
+
+
+
+  /**
    * Indicates whether the associated password attribute uses the auth password
    * syntax.
    *
@@ -607,4 +617,24 @@
    */
   public abstract StateUpdateFailurePolicy getStateUpdateFailurePolicy();
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isPasswordPolicy()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public PasswordPolicyState createAuthenticationPolicyState(Entry userEntry,
+      long time) throws DirectoryException
+  {
+    return new PasswordPolicyState(this, userEntry, time);
+  }
 }
diff --git a/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index 441445d..4ad0705 100644
--- a/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -46,10 +46,7 @@
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
-import org.opends.server.api.AccountStatusNotificationHandler;
-import org.opends.server.api.PasswordGenerator;
-import org.opends.server.api.PasswordStorageScheme;
-import org.opends.server.api.PasswordValidator;
+import org.opends.server.api.*;
 import org.opends.server.loggers.ErrorLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.protocols.internal.InternalClientConnection;
@@ -58,7 +55,6 @@
 import org.opends.server.schema.GeneralizedTimeSyntax;
 import org.opends.server.schema.UserPasswordSyntax;
 import org.opends.server.types.*;
-import org.opends.server.util.TimeThread;
 
 import static org.opends.server.config.ConfigConstants.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
@@ -72,7 +68,7 @@
  * This class provides a data structure for holding password policy state
  * information for a user account.
  */
-public final class PasswordPolicyState
+public final class PasswordPolicyState extends AuthenticationPolicyState
 {
   /**
    * The tracer object for the debug logger.
@@ -84,10 +80,6 @@
   // The user entry with which this state information is associated.
   private final Entry userEntry;
 
-  // Indicates whether the user entry itself should be updated or if the updates
-  // should be stored as modifications.
-  private final boolean updateEntry;
-
   // The string representation of the user's DN.
   private final String userDNString;
 
@@ -162,56 +154,29 @@
 
   /**
    * Creates a new password policy state object with the provided information.
-   *
-   * @param  userEntry    The entry with the user account.
-   * @param  updateEntry  Indicates whether changes should update the provided
-   *                      user entry directly or whether they should be
-   *                      collected as a set of modifications.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to
-   *                              determine the password policy for the user or
-   *                              perform any other state initialization.
-   */
-  public PasswordPolicyState(Entry userEntry, boolean updateEntry)
-       throws DirectoryException
-  {
-    this(userEntry, updateEntry, TimeThread.getTime(), false);
-  }
-
-
-
-  /**
-   * Creates a new password policy state object with the provided information.
    * Note that this version of the constructor should only be used for testing
    * purposes when the tests should be evaluated with a fixed time rather than
-   * the actual current time.  For all other purposes, the other constructor
+   * the actual current time. For all other purposes, the other constructor
    * should be used.
    *
-   * @param  userEntry          The entry with the user account.
-   * @param  updateEntry        Indicates whether changes should update the
-   *                            provided user entry directly or whether they
-   *                            should be collected as a set of modifications.
-   * @param  currentTime        The time to use as the current time for all
-   *                            time-related determinations.
-   * @param  useDefaultOnError  Indicates whether the server should fall back to
-   *                            using the default password policy if there is a
-   *                            problem with the configured policy for the user.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to
-   *                              determine the password policy for the user or
-   *                              perform any other state initialization.
+   * @param policy
+   *          The password policy associated with the state.
+   * @param userEntry
+   *          The entry with the user account.
+   * @param currentTime
+   *          The time to use as the current time for all time-related
+   *          determinations.
+   * @throws DirectoryException
+   *           If a problem occurs while attempting to determine the password
+   *           policy for the user or perform any other state initialization.
    */
-  public PasswordPolicyState(Entry userEntry, boolean updateEntry,
-                             long currentTime, boolean useDefaultOnError)
-       throws DirectoryException
+  PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime)
+      throws DirectoryException
   {
     this.userEntry   = userEntry;
-    this.updateEntry = updateEntry;
     this.currentTime = currentTime;
-
-    userDNString     = userEntry.getDN().toString();
-    passwordPolicy   = getPasswordPolicy(this.userEntry,
-                                         useDefaultOnError);
+    this.userDNString     = userEntry.getDN().toString();
+    this.passwordPolicy   = policy;
 
     // Get the password changed time for the user.
     AttributeType type
@@ -250,163 +215,6 @@
 
 
 
-  /**
-   * Retrieves the password policy for the user. If the user entry contains the
-   * ds-pwp-password-policy-dn attribute (whether real or virtual), that
-   * password policy is returned, otherwise applicable to the user entry
-   * subentry password policy is returned, if any, otherwise the default
-   * password policy is returned.
-   *
-   * @param  userEntry          The user entry.
-   * @param  useDefaultOnError  Indicates whether the server should fall back to
-   *                            using the default password policy if there is a
-   *                            problem with the configured policy for the user.
-   *
-   * @return  The password policy for the user.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to
-   *                              determine the password policy for the user.
-   */
-  public static PasswordPolicy getPasswordPolicy(Entry userEntry,
-                                     boolean useDefaultOnError)
-       throws DirectoryException
-  {
-    String userDNString = userEntry.getDN().toString();
-    AttributeType type = DirectoryServer.getAttributeType(
-            OP_ATTR_PWPOLICY_POLICY_DN, true);
-    List<Attribute> attrList = userEntry.getAttribute(type);
-
-    if (attrList != null)
-    {
-      for (Attribute a : attrList)
-      {
-        if (a.isEmpty()) continue;
-
-        AttributeValue v = a.iterator().next();
-        DN subentryDN;
-        try
-        {
-          subentryDN = DN.decode(v.getValue());
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          if (debugEnabled())
-          {
-            TRACER.debugError("Could not parse password policy subentry " +
-                "DN %s for user %s: %s",
-                       v.getValue().toString(), userDNString,
-                       stackTraceToSingleLineString(e));
-          }
-
-          Message message = ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN.get(
-              v.getValue().toString(), userDNString, e.getMessage());
-          if (useDefaultOnError)
-          {
-            ErrorLogger.logError(message);
-            return DirectoryServer.getDefaultPasswordPolicy();
-          }
-          else
-          {
-            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message,
-                                         e);
-          }
-        }
-
-        PasswordPolicy policy = (PasswordPolicy) DirectoryServer
-            .getAuthenticationPolicy(subentryDN);
-        if (policy == null)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugError("Password policy subentry %s for user %s " +
-                 "is not defined in the Directory Server.",
-                       String.valueOf(subentryDN), userDNString);
-          }
-
-          Message message = ERR_PWPSTATE_NO_SUCH_POLICY.get(
-              userDNString, String.valueOf(subentryDN));
-          if (useDefaultOnError)
-          {
-            ErrorLogger.logError(message);
-            return DirectoryServer.getDefaultPasswordPolicy();
-          }
-          else
-          {
-            throw new DirectoryException(
-                 DirectoryServer.getServerErrorResultCode(), message);
-          }
-        }
-
-        if (debugEnabled())
-        {
-          TRACER.debugInfo("Using password policy subentry %s for user %s.",
-              String.valueOf(subentryDN), userDNString);
-        }
-
-        return policy;
-      }
-    }
-
-    // No attribute defined password policy: try locating and using the
-    // closest to this entry password policy subentry defined, if any.
-    List<SubEntry> pwpSubEntries =
-            DirectoryServer.getSubentryManager().getSubentries(userEntry);
-    if ((pwpSubEntries != null) && (!pwpSubEntries.isEmpty()))
-    {
-      for (SubEntry subentry : pwpSubEntries)
-      {
-        try
-        {
-          if (subentry.getEntry().isPasswordPolicySubentry())
-          {
-            PasswordPolicy policy = (PasswordPolicy) DirectoryServer
-                .getAuthenticationPolicy(subentry.getDN());
-            if (policy == null)
-            {
-              // This shouldnt happen but if it does debug log
-              // this problem and fall back to default policy.
-              if (debugEnabled())
-              {
-                TRACER.debugError(
-                        "Found unknown password policy subentry "
-                        + "DN %s for user %s",
-                        subentry.getDN().toString(), userDNString);
-              }
-              break;
-            }
-            return policy;
-          }
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugError("Could not parse password policy subentry "
-                    + "DN %s for user %s: %s",
-                    subentry.getDN().toString(), userDNString,
-                    stackTraceToSingleLineString(e));
-          }
-        }
-      }
-    }
-
-    // There is no policy subentry defined: use the default.
-    if (debugEnabled())
-    {
-      TRACER.debugInfo("Using the default password policy for user %s",
-          userDNString);
-    }
-
-    return DirectoryServer.getDefaultPasswordPolicy();
-  }
-
-
-
    /**
     * Retrieves the value of the specified attribute as a string.
     *
@@ -667,11 +475,9 @@
 
 
   /**
-   * Retrieves the password policy associated with this state information.
-   *
-   * @return  The password policy associated with this state information.
+   * {@inheritDoc}
    */
-  public PasswordPolicy getPolicy()
+  public PasswordPolicy getAuthenticationPolicy()
   {
     return passwordPolicy;
   }
@@ -770,16 +576,7 @@
       Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_TIME,
           timeValue);
 
-      if (updateEntry)
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(a);
-        userEntry.putAttribute(a.getAttributeType(), attrList);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE, a, true));
-      }
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
     }
   }
 
@@ -801,15 +598,8 @@
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC,
                                        true);
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      Attribute a = Attributes.empty(type);
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    Attribute a = Attributes.empty(type);
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
 
 
     // Fall back to using the entry creation time as the password changed time,
@@ -930,30 +720,13 @@
     if (isDisabled)
     {
       Attribute a = Attributes.create(type, String.valueOf(true));
-
-      if (updateEntry)
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(a);
-        userEntry.putAttribute(type, attrList);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE, a, true));
-      }
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
     }
     else
     {
       // erase
-      if (updateEntry)
-      {
-        userEntry.removeAttribute(type);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE,
+      modifications.add(new Modification(ModificationType.REPLACE,
                                            Attributes.empty(type), true));
-      }
     }
   }
 
@@ -1089,16 +862,7 @@
                                             true);
 
       Attribute a = Attributes.create(type, timeStr);
-      if (updateEntry)
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(a);
-        userEntry.putAttribute(type, attrList);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE, a, true));
-      }
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
     }
   }
 
@@ -1121,15 +885,8 @@
          DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                           true);
 
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
           Attributes.empty(type), true));
-    }
   }
 
 
@@ -1187,15 +944,8 @@
 
       authFailureTimes = new ArrayList<Long>();
 
-      if (updateEntry)
-      {
-        userEntry.removeAttribute(type);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE,
+      modifications.add(new Modification(ModificationType.REPLACE,
             Attributes.empty(type), true));
-      }
 
       return authFailureTimes;
     }
@@ -1245,33 +995,11 @@
 
       if (valuesToRemove != null)
       {
-        if (updateEntry)
-        {
-          if (authFailureTimes.isEmpty())
-          {
-            userEntry.removeAttribute(type);
-          }
-          else
-          {
-            AttributeBuilder builder = new AttributeBuilder(type);
-            for (Long l : authFailureTimes)
-            {
-              builder.add(
-                 AttributeValues.create(type, GeneralizedTimeSyntax.format(l)));
-            }
-            ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
-            keepList.add(builder.toAttribute());
-            userEntry.putAttribute(type, keepList);
-          }
-        }
-        else
-        {
-          AttributeBuilder builder = new AttributeBuilder(type);
-          builder.addAll(valuesToRemove);
-          Attribute a = builder.toAttribute();
-          modifications.add(new Modification(ModificationType.DELETE, a,
-                                             true));
-        }
+        AttributeBuilder builder = new AttributeBuilder(type);
+        builder.addAll(valuesToRemove);
+        Attribute a = builder.toAttribute();
+        modifications.add(new Modification(ModificationType.DELETE, a,
+            true));
       }
     }
 
@@ -1344,14 +1072,7 @@
     Attribute addAttr = Attributes.create(type, AttributeValues.create(type,
         GeneralizedTimeSyntax.format(highestFailureTime)));
 
-    if (updateEntry)
-    {
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
-    }
+    modifications.add(new Modification(ModificationType.ADD, addAttr, true));
 
     // Now check to see if there have been sufficient failures to lock the
     // account.
@@ -1403,16 +1124,7 @@
     }
     Attribute a = builder.toAttribute();
 
-    if (updateEntry)
-    {
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
 
     // Now check to see if there have been sufficient failures to lock the
     // account.
@@ -1458,15 +1170,8 @@
                                   OP_ATTR_PWPOLICY_FAILURE_TIME);
     }
 
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -1544,16 +1249,7 @@
     Attribute a = Attributes.create(type, AttributeValues.create(type,
         GeneralizedTimeSyntax.format(failureLockedTime)));
 
-    if (updateEntry)
-    {
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
   }
 
 
@@ -1585,15 +1281,8 @@
                                   OP_ATTR_PWPOLICY_LOCKED_TIME);
     }
 
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -1936,16 +1625,7 @@
 
 
     Attribute a = Attributes.create(type, timestamp);
-    if (updateEntry)
-    {
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
 
     if (debugEnabled())
     {
@@ -1972,15 +1652,8 @@
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
 
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -2195,29 +1868,12 @@
     if (mustChangePassword)
     {
       Attribute a = Attributes.create(type, String.valueOf(true));
-      if (updateEntry)
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(a);
-        userEntry.putAttribute(type, attrList);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE, a, true));
-      }
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
     }
     else
     {
-      // erase
-      if (updateEntry)
-      {
-        userEntry.removeAttribute(type);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE,
+      modifications.add(new Modification(ModificationType.REPLACE,
                                            Attributes.empty(type), true));
-      }
     }
   }
 
@@ -2732,16 +2388,7 @@
       String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime);
       Attribute a = Attributes.create(type, timeValue);
 
-      if (updateEntry)
-      {
-        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-        attrList.add(a);
-        userEntry.putAttribute(type, attrList);
-      }
-      else
-      {
-        modifications.add(new Modification(ModificationType.REPLACE, a, true));
-      }
+      modifications.add(new Modification(ModificationType.REPLACE, a, true));
     }
   }
 
@@ -2763,15 +2410,8 @@
 
     AttributeType type = DirectoryServer.getAttributeType(
                              OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -2861,16 +2501,7 @@
     Attribute a = Attributes.create(type, GeneralizedTimeSyntax
         .createGeneralizedTimeValue(currentTime));
 
-    if (updateEntry)
-    {
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
 
     if (debugEnabled())
     {
@@ -2898,15 +2529,8 @@
 
     AttributeType type =
          DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      Attribute a = Attributes.empty(type);
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    Attribute a = Attributes.empty(type);
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
 
     if (debugEnabled())
     {
@@ -2955,15 +2579,8 @@
 
         graceLoginTimes = new ArrayList<Long>();
 
-        if (updateEntry)
-        {
-          userEntry.removeAttribute(type);
-        }
-        else
-        {
-          modifications.add(new Modification(ModificationType.REPLACE,
+        modifications.add(new Modification(ModificationType.REPLACE,
               Attributes.empty(type), true));
-        }
       }
     }
 
@@ -3036,27 +2653,10 @@
                                   OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
     }
 
-    if (updateEntry)
-    {
-      AttributeBuilder builder = new AttributeBuilder(type);
-      for (Long l : graceTimes)
-      {
-        builder.add(AttributeValues.create(type, GeneralizedTimeSyntax
-            .format(l)));
-      }
+    Attribute addAttr = Attributes.create(type, AttributeValues.create(
+        type, GeneralizedTimeSyntax.format(highestGraceTime)));
 
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(builder.toAttribute());
-
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      Attribute addAttr = Attributes.create(type, AttributeValues.create(
-          type, GeneralizedTimeSyntax.format(highestGraceTime)));
-
-      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
-    }
+    modifications.add(new Modification(ModificationType.ADD, addAttr, true));
   }
 
 
@@ -3094,17 +2694,7 @@
     }
     Attribute a = builder.toAttribute();
 
-    if (updateEntry)
-    {
-      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
-      attrList.add(a);
-
-      userEntry.putAttribute(type, attrList);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE, a, true));
-    }
+    modifications.add(new Modification(ModificationType.REPLACE, a, true));
   }
 
 
@@ -3135,15 +2725,8 @@
                                   OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
     }
 
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -3240,13 +2823,7 @@
 
 
   /**
-   * Indicates whether the provided password value matches any of the stored
-   * passwords in the user entry.
-   *
-   * @param  password  The user-provided password to verify.
-   *
-   * @return  <CODE>true</CODE> if the provided password matches any of the
-   *          stored password values, or <CODE>false</CODE> if not.
+   * {@inheritDoc}
    */
   public boolean passwordMatches(ByteString password)
   {
@@ -3644,28 +3221,17 @@
       return;
     }
 
-    if (updateEntry)
-    {
-      AttributeBuilder builder = new AttributeBuilder(type);
-      builder.addAll(updatedValues);
-      ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
-      newList.add(builder.toAttribute());
-      userEntry.putAttribute(type, newList);
-    }
-    else
-    {
-      AttributeBuilder builder = new AttributeBuilder(type);
-      builder.addAll(removedValues);
-      Attribute a = builder.toAttribute();
-      modifications.add(new Modification(ModificationType.DELETE, a, true));
+    AttributeBuilder builder = new AttributeBuilder(type);
+    builder.addAll(removedValues);
+    Attribute a = builder.toAttribute();
+    modifications.add(new Modification(ModificationType.DELETE, a, true));
 
-      if (! addedValues.isEmpty())
-      {
-        builder = new AttributeBuilder(type);
-        builder.addAll(addedValues);
-        Attribute a2 = builder.toAttribute();
-        modifications.add(new Modification(ModificationType.ADD, a2, true));
-      }
+    if (! addedValues.isEmpty())
+    {
+      builder = new AttributeBuilder(type);
+      builder.addAll(addedValues);
+      Attribute a2 = builder.toAttribute();
+      modifications.add(new Modification(ModificationType.ADD, a2, true));
     }
 
     if (debugEnabled())
@@ -4155,26 +3721,13 @@
 
     // Apply the changes, either by adding modifications or by directly updating
     // the entry.
-    if (updateEntry)
+    for (Attribute a : removeAttrs)
     {
-      LinkedList<AttributeValue> valueList = new LinkedList<AttributeValue>();
-      for (Attribute a : removeAttrs)
-      {
-        userEntry.removeAttribute(a, valueList);
-      }
-
-      userEntry.addAttribute(newHistAttr, valueList);
+      modifications.add(new Modification(ModificationType.DELETE, a, true));
     }
-    else
-    {
-      for (Attribute a : removeAttrs)
-      {
-        modifications.add(new Modification(ModificationType.DELETE, a, true));
-      }
 
-      modifications.add(new Modification(ModificationType.ADD, newHistAttr,
-                                         true));
-    }
+    modifications.add(new Modification(ModificationType.ADD, newHistAttr,
+        true));
   }
 
 
@@ -4221,15 +3774,8 @@
 
     AttributeType type = DirectoryServer.getAttributeType(
                              OP_ATTR_PWPOLICY_HISTORY_LC, true);
-    if (updateEntry)
-    {
-      userEntry.removeAttribute(type);
-    }
-    else
-    {
-      modifications.add(new Modification(ModificationType.REPLACE,
+    modifications.add(new Modification(ModificationType.REPLACE,
                                          Attributes.empty(type), true));
-    }
   }
 
 
@@ -4322,13 +3868,9 @@
 
 
   /**
-   * Performs an internal modification to update the user's entry, if necessary.
-   * This will do nothing if no modifications are required.
-   *
-   * @throws  DirectoryException  If a problem occurs while processing the
-   *                              internal modification.
+   * {@inheritDoc}
    */
-  public void updateUserEntry()
+  public void finalizeStateAfterBind()
          throws DirectoryException
   {
     // If there are no modifications, then there's nothing to do.
diff --git a/opends/src/server/org/opends/server/core/SearchOperationBasis.java b/opends/src/server/org/opends/server/core/SearchOperationBasis.java
index 4f708f8..f2e46de 100644
--- a/opends/src/server/org/opends/server/core/SearchOperationBasis.java
+++ b/opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -36,6 +36,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.plugin.PluginResult;
 import org.opends.server.controls.AccountUsableResponseControl;
@@ -639,47 +641,54 @@
       }
     }
 
-    // Determine whether to include the account usable control.  If so, then
+    // Determine whether to include the account usable control. If so, then
     // create it now.
     if (isIncludeUsableControl())
     {
       try
       {
         // FIXME -- Need a way to enable PWP debugging.
-        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false);
-
-        boolean isInactive           = pwpState.isDisabled() ||
-                                       pwpState.isAccountExpired();
-        boolean isLocked             = pwpState.lockedDueToFailures() ||
-                                       pwpState.lockedDueToMaximumResetAge() ||
-                                       pwpState.lockedDueToIdleInterval();
-        boolean isReset              = pwpState.mustChangePassword();
-        boolean isExpired            = pwpState.isPasswordExpired();
-
-        if (isInactive || isLocked || isReset || isExpired)
+        AuthenticationPolicy policy = AuthenticationPolicy
+            .forUser(entry, false);
+        if (policy.isPasswordPolicy())
         {
-          int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
-          int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
+          PasswordPolicyState pwpState = (PasswordPolicyState) policy
+              .createAuthenticationPolicyState(entry);
 
-          if (controls == null)
+          boolean isInactive = pwpState.isDisabled()
+              || pwpState.isAccountExpired();
+          boolean isLocked = pwpState.lockedDueToFailures()
+              || pwpState.lockedDueToMaximumResetAge()
+              || pwpState.lockedDueToIdleInterval();
+          boolean isReset = pwpState.mustChangePassword();
+          boolean isExpired = pwpState.isPasswordExpired();
+
+          if (isInactive || isLocked || isReset || isExpired)
           {
-            controls = new ArrayList<Control>(1);
-          }
+            int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock();
+            int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
 
-          controls.add(new AccountUsableResponseControl(isInactive, isReset,
-                                isExpired, remainingGraceLogins, isLocked,
-                                secondsBeforeUnlock));
-        }
-        else
-        {
-          if (controls == null)
+            if (controls == null)
+            {
+              controls = new ArrayList<Control>(1);
+            }
+
+            controls
+                .add(new AccountUsableResponseControl(isInactive, isReset,
+                    isExpired, remainingGraceLogins, isLocked,
+                    secondsBeforeUnlock));
+          }
+          else
           {
-            controls = new ArrayList<Control>(1);
-          }
+            if (controls == null)
+            {
+              controls = new ArrayList<Control>(1);
+            }
 
-          int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
-          controls.add(new AccountUsableResponseControl(
-                                secondsBeforeExpiration));
+            int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
+            controls.add(new AccountUsableResponseControl(
+                secondsBeforeExpiration));
+          }
         }
       }
       catch (Exception e)
diff --git a/opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
index 0f1b8d6..e494529 100644
--- a/opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2006-2009 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 package org.opends.server.extensions;
 
@@ -40,9 +41,7 @@
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.std.server.CramMD5SASLMechanismHandlerCfg;
 import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.IdentityMapper;
-import org.opends.server.api.SASLMechanismHandler;
+import org.opends.server.api.*;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.BindOperation;
 import org.opends.server.core.DirectoryServer;
@@ -441,8 +440,19 @@
     List<ByteString> clearPasswords;
     try
     {
-      PasswordPolicyState pwPolicyState =
-           new PasswordPolicyState(userEntry, false);
+      AuthenticationPolicyState authState = AuthenticationPolicyState.forUser(
+          userEntry, false);
+
+      if (!authState.isPasswordPolicy())
+      {
+        bindOperation.setResultCode(ResultCode.INAPPROPRIATE_AUTHENTICATION);
+        Message message = ERR_SASL_ACCOUNT_NOT_LOCAL
+            .get(SASL_MECHANISM_CRAM_MD5, String.valueOf(userEntry.getDN()));
+        bindOperation.setAuthFailureReason(message);
+        return;
+      }
+
+      PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState;
       clearPasswords = pwPolicyState.getClearPasswords();
       if ((clearPasswords == null) || clearPasswords.isEmpty())
       {
diff --git a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
index 1ebbb1d..3502229 100644
--- a/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
+++ b/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -40,10 +40,7 @@
 import org.opends.server.admin.std.server.ExtendedOperationHandlerCfg;
 import org.opends.server.admin.std.server.
             PasswordModifyExtendedOperationHandlerCfg;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.ExtendedOperationHandler;
-import org.opends.server.api.IdentityMapper;
-import org.opends.server.api.PasswordStorageScheme;
+import org.opends.server.api.*;
 import org.opends.server.config.ConfigException;
 import org.opends.server.controls.PasswordPolicyResponseControl;
 import org.opends.server.controls.PasswordPolicyWarningType;
@@ -514,7 +511,17 @@
       PasswordPolicyState pwPolicyState;
       try
       {
-        pwPolicyState = new PasswordPolicyState(userEntry, false);
+        AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+            false);
+        if (!policy.isPasswordPolicy())
+        {
+          operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+          operation.appendErrorMessage(ERR_EXTOP_PASSMOD_ACCOUNT_NOT_LOCAL
+              .get(String.valueOf(userDN)));
+          return;
+        }
+        pwPolicyState = (PasswordPolicyState) policy
+          .createAuthenticationPolicyState(userEntry);
       }
       catch (DirectoryException de)
       {
@@ -533,6 +540,7 @@
       }
 
 
+
       // Determine whether the user is changing his own password or if it's an
       // administrative reset.  If it's an administrative reset, then the
       // requester must have the PASSWORD_RESET privilege.
@@ -614,7 +622,7 @@
       if (oldPassword == null)
       {
         if (selfChange
-            && pwPolicyState.getPolicy()
+            && pwPolicyState.getAuthenticationPolicy()
                 .isPasswordChangeRequiresCurrentPassword())
         {
           operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
@@ -637,8 +645,9 @@
       }
       else
       {
-        if (pwPolicyState.getPolicy().isRequireSecureAuthentication() &&
-            (! operation.getClientConnection().isSecure()))
+        if (pwPolicyState.getAuthenticationPolicy()
+            .isRequireSecureAuthentication()
+            && (!operation.getClientConnection().isSecure()))
         {
           operation.setResultCode(ResultCode.CONFIDENTIALITY_REQUIRED);
           operation.addAdditionalLogItem(AdditionalLogItem.quotedKeyValue(
@@ -674,8 +683,9 @@
 
       // If it is a self password change and we don't allow that, then reject
       // the request.
-      if (selfChange &&
-           (! pwPolicyState.getPolicy().isAllowUserPasswordChanges()))
+      if (selfChange
+          && (!pwPolicyState.getAuthenticationPolicy()
+              .isAllowUserPasswordChanges()))
       {
         if (pwPolicyRequested)
         {
@@ -697,10 +707,10 @@
 
       // If we require secure password changes and the connection isn't secure,
       // then reject the request.
-      if (pwPolicyState.getPolicy().isRequireSecurePasswordChanges() &&
-          (! operation.getClientConnection().isSecure()))
+      if (pwPolicyState.getAuthenticationPolicy()
+          .isRequireSecurePasswordChanges()
+          && (!operation.getClientConnection().isSecure()))
       {
-
         operation.setResultCode(ResultCode.CONFIDENTIALITY_REQUIRED);
 
         operation.appendErrorMessage(
@@ -733,8 +743,8 @@
 
       // If the user's password is expired and it's a self-change request, then
       // see if that's OK.
-      if ((selfChange && pwPolicyState.isPasswordExpired() &&
-          (! pwPolicyState.getPolicy().isAllowExpiredPasswordChanges())))
+      if ((selfChange && pwPolicyState.isPasswordExpired() && (!pwPolicyState
+          .getAuthenticationPolicy().isAllowExpiredPasswordChanges())))
       {
         if (pwPolicyRequested)
         {
@@ -800,7 +810,8 @@
           // by an internal operation or during synchronization, so we don't
           // need to check for those cases.
           isPreEncoded = true;
-          if (! pwPolicyState.getPolicy().isAllowPreEncodedPasswords())
+          if (!pwPolicyState.getAuthenticationPolicy()
+              .isAllowPreEncodedPasswords())
           {
             operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
 
@@ -813,7 +824,7 @@
         {
           // Run the new password through the set of password validators.
           if (selfChange
-              || (!pwPolicyState.getPolicy()
+              || (!pwPolicyState.getAuthenticationPolicy()
                   .isSkipValidationForAdministrators()))
           {
             HashSet<ByteString> clearPasswords;
@@ -866,7 +877,7 @@
           {
             if (pwPolicyState.isPasswordInHistory(newPassword))
             {
-              if (selfChange || (! pwPolicyState.getPolicy().
+              if (selfChange || (! pwPolicyState.getAuthenticationPolicy().
                                       isSkipValidationForAdministrators()))
               {
                 operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
@@ -918,7 +929,8 @@
       // If the current password was provided, then remove all matching values
       // from the user's entry and replace them with the new password.
       // Otherwise replace all password values.
-      AttributeType attrType = pwPolicyState.getPolicy().getPasswordAttribute();
+      AttributeType attrType = pwPolicyState.getAuthenticationPolicy()
+          .getPasswordAttribute();
       List<Modification> modList = new ArrayList<Modification>();
       if (oldPassword != null)
       {
@@ -926,7 +938,7 @@
         Set<AttributeValue> existingValues = pwPolicyState.getPasswordValues();
         LinkedHashSet<AttributeValue> deleteValues =
              new LinkedHashSet<AttributeValue>(existingValues.size());
-        if (pwPolicyState.getPolicy().isAuthPasswordSyntax())
+        if (pwPolicyState.getAuthenticationPolicy().isAuthPasswordSyntax())
         {
           for (AttributeValue v : existingValues)
           {
@@ -1056,7 +1068,7 @@
       else
       {
         pwPolicyState.setMustChangePassword(
-             pwPolicyState.getPolicy().isForceChangeOnReset());
+             pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset());
       }
 
 
@@ -1133,7 +1145,7 @@
         // Save attachments for post-op plugins (e.g. Samba password plugin).
         operation.setAttachment(AUTHZ_DN_ATTACHMENT, userDN);
         operation.setAttachment(PWD_ATTRIBUTE_ATTACHMENT, pwPolicyState
-            .getPolicy().getPasswordAttribute());
+            .getAuthenticationPolicy().getPasswordAttribute());
         if (!isPreEncoded)
         {
           operation.setAttachment(CLEAR_PWD_ATTACHMENT, newPassword);
diff --git a/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java b/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java
index ff12942..3ea552b 100644
--- a/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java
+++ b/opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java
@@ -37,6 +37,7 @@
 import org.opends.messages.Message;
 import org.opends.server.admin.std.server.
             PasswordPolicyStateExtendedOperationHandlerCfg;
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.ExtendedOperationHandler;
 import org.opends.server.config.ConfigException;
@@ -600,11 +601,19 @@
     }
     // Get the password policy state for the user entry.
     PasswordPolicyState pwpState;
-    PasswordPolicy      policy;
     try
     {
-      pwpState = new PasswordPolicyState(userEntry, false);
-      policy   = pwpState.getPolicy();
+      AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
+          false);
+      if (!policy.isPasswordPolicy())
+      {
+        operation.setResultCode(ResultCode.UNWILLING_TO_PERFORM);
+        operation.appendErrorMessage(ERR_EXTOP_PWPSTATE_ACCOUNT_NOT_LOCAL
+            .get(String.valueOf(userEntry)));
+        return;
+      }
+      pwpState = (PasswordPolicyState) policy
+          .createAuthenticationPolicyState(userEntry);
     }
     catch (DirectoryException de)
     {
@@ -617,6 +626,7 @@
       return;
     }
 
+    PasswordPolicy policy = pwpState.getAuthenticationPolicy();
     isAccountSetDisabled = false;
     isAccountSetEnabled = false;
     // Create a hash set that will be used to hold the types of the return
@@ -708,8 +718,9 @@
         // And it's updated password policy state
         try
         {
-          pwpState = new PasswordPolicyState(userEntry, false);
-          policy = pwpState.getPolicy();
+          // We should not need to re-fetch the password policy.
+          pwpState = (PasswordPolicyState) policy
+              .createAuthenticationPolicyState(userEntry);
         }
         catch (DirectoryException de)
         {
diff --git a/opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java b/opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java
index 612a6b6..52f3a4a 100644
--- a/opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java
+++ b/opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java
@@ -34,12 +34,11 @@
 import org.opends.messages.Message;
 import org.opends.server.admin.std.server.
         PasswordPolicySubentryVirtualAttributeCfg;
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.api.VirtualAttributeProvider;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.PasswordPolicy;
-import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.loggers.ErrorLogger;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.*;
@@ -111,12 +110,11 @@
 
     if (!entry.isSubentry() && !entry.isLDAPSubentry())
     {
-      PasswordPolicy policy = null;
+      AuthenticationPolicy policy = null;
 
       try
       {
-        policy = PasswordPolicyState.getPasswordPolicy(
-                entry, false);
+        policy = AuthenticationPolicy.forUser(entry, false);
       }
       catch (DirectoryException de)
       {
@@ -133,7 +131,7 @@
         }
       }
 
-      if (policy != null)
+      if (policy != null && policy.isPasswordPolicy())
       {
         AttributeType dnAttrType = DirectoryServer.getAttributeType(
                 "1.3.6.1.4.1.42.2.27.8.1.23");
diff --git a/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java b/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
index 8671c72..aa06ad0 100644
--- a/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
+++ b/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
@@ -23,44 +23,35 @@
  *
  *
  *      Copyright 2006-2009 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 package org.opends.server.extensions;
-import org.opends.messages.Message;
 
 
 
+import static org.opends.messages.ExtensionMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
+import static org.opends.server.loggers.debug.DebugLogger.getTracer;
+import static org.opends.server.util.ServerConstants.SASL_MECHANISM_PLAIN;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.locks.Lock;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.admin.std.server.PlainSASLMechanismHandlerCfg;
 import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
+import org.opends.server.api.AuthenticationPolicyState;
 import org.opends.server.api.IdentityMapper;
 import org.opends.server.api.SASLMechanismHandler;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.BindOperation;
 import org.opends.server.core.DirectoryServer;
-import org.opends.server.core.PasswordPolicyState;
-import org.opends.server.protocols.internal.InternalClientConnection;
-import org.opends.server.types.AuthenticationInfo;
-import org.opends.server.types.ByteString;
-import org.opends.server.types.ConfigChangeResult;
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DN;
-import org.opends.server.types.Entry;
-import org.opends.server.types.InitializationException;
-import org.opends.server.types.LockManager;
-import org.opends.server.types.Privilege;
-import org.opends.server.types.ResultCode;
-
-import static org.opends.server.loggers.debug.DebugLogger.*;
 import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.server.types.DebugLogLevel;
-import static org.opends.messages.ExtensionMessages.*;
-
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.types.*;
 
 
 
@@ -508,12 +499,14 @@
     // provided password was correct.
     try
     {
-      PasswordPolicyState pwPolicyState =
-           new PasswordPolicyState(userEntry, false);
-      if (! pwPolicyState.passwordMatches(ByteString.valueOf(password)))
+      // FIXME: we should store store the auth state in with the bind operation
+      // so that any state updates, such as cached passwords, are persisted to
+      // the user's entry when the bind completes.
+      AuthenticationPolicyState authState = AuthenticationPolicyState.forUser(
+          userEntry, false);
+      if (!authState.passwordMatches(ByteString.valueOf(password)))
       {
         bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
-
         Message message = ERR_SASLPLAIN_INVALID_PASSWORD.get();
         bindOperation.setAuthFailureReason(message);
         return;
diff --git a/opends/src/server/org/opends/server/extensions/SASLContext.java b/opends/src/server/org/opends/server/extensions/SASLContext.java
index 954baad..c94cc20 100644
--- a/opends/src/server/org/opends/server/extensions/SASLContext.java
+++ b/opends/src/server/org/opends/server/extensions/SASLContext.java
@@ -23,6 +23,7 @@
  *
  *
  *      Copyright 2008-2009 Sun Microsystems, Inc.
+ *      Portions copyright 2011 ForgeRock AS.
  */
 
 package org.opends.server.extensions;
@@ -48,6 +49,7 @@
 import org.ietf.jgss.GSSException;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.messages.Message;
+import org.opends.server.api.AuthenticationPolicyState;
 import org.opends.server.api.ClientConnection;
 import org.opends.server.api.IdentityMapper;
 import org.opends.server.core.AccessControlConfigManager;
@@ -57,6 +59,7 @@
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.ldap.LDAPClientConnection;
 import org.opends.server.types.*;
+
 import static org.opends.messages.ExtensionMessages.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.util.ServerConstants.*;
@@ -101,6 +104,9 @@
     //Error message used by callbacks.
     private Message cbMsg;
 
+    //Error code used by callbacks.
+    private ResultCode cbResultCode;
+
     //The current bind operation used by the callbacks.
     private BindOperation bindOp;
 
@@ -330,12 +336,25 @@
         dispose();
         ClientConnection clientConn = bindOp.getClientConnection();
         clientConn.setSASLAuthStateInfo(null);
+
         //Check if the callback message is null and use that message if not.
-        if(cbMsg != null)
-            bindOp.setAuthFailureReason(cbMsg);
+        if (cbResultCode != null)
+        {
+          bindOp.setResultCode(cbResultCode);
+        }
         else
-            bindOp.setAuthFailureReason(msg);
-        bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+        {
+          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
+        }
+
+        if (cbMsg != null)
+        {
+          bindOp.setAuthFailureReason(cbMsg);
+        }
+        else
+        {
+          bindOp.setAuthFailureReason(msg);
+        }
     }
 
 
@@ -398,6 +417,18 @@
      * @param cbMsg The message to set the callback message to.
      */
     private void setCallbackMsg(Message cbMsg) {
+        setCallbackMsg(ResultCode.INVALID_CREDENTIALS, cbMsg);
+    }
+
+
+    /**
+     * Sets the callback message to the specified message.
+     *
+     * @param cbResultCode The result code.
+     * @param cbMsg The message.
+     */
+    private void setCallbackMsg(ResultCode cbResultCode, Message cbMsg) {
+        this.cbResultCode = cbResultCode;
         this.cbMsg = cbMsg;
     }
 
@@ -614,8 +645,19 @@
         //Try to get a clear password to use.
         List<ByteString> clearPasswords;
         try {
-          PasswordPolicyState pwPolicyState =
-                                    new PasswordPolicyState(authEntry, false);
+          AuthenticationPolicyState authState =
+            AuthenticationPolicyState.forUser(authEntry, false);
+
+          if (!authState.isPasswordPolicy())
+          {
+            Message message = ERR_SASL_ACCOUNT_NOT_LOCAL.get(
+                mechanism, String.valueOf(authEntry.getDN()));
+            setCallbackMsg(ResultCode.INAPPROPRIATE_AUTHENTICATION, message);
+            return;
+          }
+
+          PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState;
+
           clearPasswords = pwPolicyState.getClearPasswords();
           if ((clearPasswords == null) || clearPasswords.isEmpty()) {
               setCallbackMsg(
diff --git a/opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java b/opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java
index d80ce25..8273673 100644
--- a/opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java
+++ b/opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java
@@ -365,14 +365,20 @@
           try
           {
             policyDN = DN.decode(v.getValue());
-            policy = (PasswordPolicy) DirectoryServer
+            AuthenticationPolicy authPolicy = DirectoryServer
                 .getAuthenticationPolicy(policyDN);
-            if (policy == null)
+            if (authPolicy == null)
             {
               Message message = WARN_PLUGIN_PWIMPORT_NO_SUCH_POLICY.get(
                   String.valueOf(entry.getDN()), String.valueOf(policyDN));
               logError(message);
             }
+
+            if (authPolicy.isPasswordPolicy())
+            {
+              policy = (PasswordPolicy) authPolicy;
+            }
+
             break policyLoop;
           }
           catch (DirectoryException de)
diff --git a/opends/src/server/org/opends/server/types/AccountStatusNotification.java b/opends/src/server/org/opends/server/types/AccountStatusNotification.java
index e868922..df79cd0 100644
--- a/opends/src/server/org/opends/server/types/AccountStatusNotification.java
+++ b/opends/src/server/org/opends/server/types/AccountStatusNotification.java
@@ -237,7 +237,7 @@
          new HashMap<AccountStatusNotificationProperty,
                      List<String>>(4);
 
-    PasswordPolicy policy = pwPolicyState.getPolicy();
+    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
 
     ArrayList<String> propList = new ArrayList<String>(1);
     propList.add(policy.getDN().toString());
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 3f9a2aa..9a2f57b 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -38,20 +38,13 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.Lock;
 
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
-import org.opends.server.api.AttributeSyntax;
-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.PasswordValidator;
-import org.opends.server.api.SynchronizationProvider;
+import org.opends.server.api.*;
 import org.opends.server.api.plugin.PluginResult;
 import org.opends.server.controls.LDAPAssertionRequestControl;
 import org.opends.server.controls.LDAPPostReadRequestControl;
@@ -1029,49 +1022,14 @@
     // FIXME -- We need to check to see if the password policy subentry
     //          might be specified virtually rather than as a real
     //          attribute.
-    PasswordPolicy passwordPolicy = null;
-    List<Attribute> pwAttrList =
-         entry.getAttribute(OP_ATTR_PWPOLICY_POLICY_DN);
-    if ((pwAttrList != null) && (! pwAttrList.isEmpty()))
+    AuthenticationPolicy policy = AuthenticationPolicy.forUser(entry, false);
+    if (!policy.isPasswordPolicy())
     {
-      Attribute a = pwAttrList.get(0);
-      Iterator<AttributeValue> iterator = a.iterator();
-      if (iterator.hasNext())
-      {
-        DN policyDN;
-        try
-        {
-          policyDN = DN.decode(iterator.next().getValue());
-        }
-        catch (DirectoryException de)
-        {
-          if (debugEnabled())
-          {
-            TRACER.debugCaught(DebugLogLevel.ERROR, de);
-          }
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
-                                            String.valueOf(entryDN),
-                                           de.getMessageObject()));
-        }
-
-        passwordPolicy = (PasswordPolicy) DirectoryServer
-            .getAuthenticationPolicy(policyDN);
-        if (passwordPolicy == null)
-        {
-          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                       ERR_ADD_NO_SUCH_PWPOLICY.get(
-                                            String.valueOf(entryDN),
-                                         String.valueOf(policyDN)));
-        }
-      }
+      // The entry doesn't have a locally managed password, so no action is
+      // required.
+      return;
     }
-
-    if (passwordPolicy == null)
-    {
-      passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
-    }
+    PasswordPolicy passwordPolicy = (PasswordPolicy) policy;
 
     // See if a password was specified.
     AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
index 68d58d9..026621f 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -35,9 +35,7 @@
 
 import org.opends.messages.Message;
 import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
-import org.opends.server.api.Backend;
-import org.opends.server.api.ClientConnection;
-import org.opends.server.api.SASLMechanismHandler;
+import org.opends.server.api.*;
 import org.opends.server.api.plugin.PluginResult;
 import org.opends.server.controls.AuthorizationIdentityResponseControl;
 import org.opends.server.controls.PasswordExpiredControl;
@@ -138,15 +136,8 @@
   // The idle time limit that should be enforced for the user.
   private long idleTimeLimit;
 
-  /**
-   * The password policy that applies to the user.
-   */
-  protected PasswordPolicy policy;
-
-  /**
-   * The password policy state for the user.
-   */
-  protected PasswordPolicyState pwPolicyState;
+  // Authentication policy state.
+  private AuthenticationPolicyState authPolicyState;
 
   // The password policy error type for this bind operation.
   private PasswordPolicyErrorType pwPolicyErrorType;
@@ -199,7 +190,7 @@
     idleTimeLimit            = DirectoryServer.getIdleTimeLimit();
     bindDN                   = getBindDN();
     saslMechanism            = getSASLMechanism();
-    pwPolicyState            = null;
+    authPolicyState          = null;
     pwPolicyErrorType        = null;
     pwPolicyControlRequested = false;
     isGraceLogin             = false;
@@ -330,9 +321,9 @@
     // required.
     try
     {
-      if (pwPolicyState != null)
+      if (authPolicyState != null)
       {
-        pwPolicyState.updateUserEntry();
+        authPolicyState.finalizeStateAfterBind();
       }
     }
     catch (DirectoryException de)
@@ -569,124 +560,148 @@
       }
 
 
-      // Check to see if the user has a password.  If not, then fail.
+      // Check to see if the user has a password. If not, then fail.
       // FIXME -- We need to have a way to enable/disable debugging.
-      pwPolicyState = new PasswordPolicyState(userEntry, false);
-      policy = pwPolicyState.getPolicy();
-      AttributeType  pwType = policy.getPasswordAttribute();
-
-      List<Attribute> pwAttr = userEntry.getAttribute(pwType);
-      if ((pwAttr == null) || (pwAttr.isEmpty()))
+      authPolicyState = AuthenticationPolicyState.forUser(userEntry, false);
+      if (authPolicyState.isPasswordPolicy())
       {
-        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
-                                     ERR_BIND_OPERATION_NO_PASSWORD.get(
-                                          String.valueOf(bindDN)));
-      }
+        // Account is managed locally.
+        PasswordPolicyState pwPolicyState =
+          (PasswordPolicyState) authPolicyState;
+        PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
 
+        AttributeType pwType = policy.getPasswordAttribute();
 
-      // Perform a number of password policy state checks for the user.
-      checkPasswordPolicyState(userEntry, null);
-
-
-      // Invoke the pre-operation bind plugins.
-      executePostOpPlugins = true;
-      PluginResult.PreOperation preOpResult =
-          pluginConfigManager.invokePreOperationBindPlugins(this);
-      if (!preOpResult.continueProcessing())
-      {
-        setResultCode(preOpResult.getResultCode());
-        appendErrorMessage(preOpResult.getErrorMessage());
-        setMatchedDN(preOpResult.getMatchedDN());
-        setReferralURLs(preOpResult.getReferralURLs());
-        return false;
-      }
-
-
-      // Determine whether the provided password matches any of the stored
-      // passwords for the user.
-      if (pwPolicyState.passwordMatches(simplePassword))
-      {
-        setResultCode(ResultCode.SUCCESS);
-
-        if (DirectoryServer.lockdownMode() &&
-            (! ClientConnection.hasPrivilege(userEntry,
-                Privilege.BYPASS_LOCKDOWN)))
+        List<Attribute> pwAttr = userEntry.getAttribute(pwType);
+        if ((pwAttr == null) || (pwAttr.isEmpty()))
         {
           throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
-                                 ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
+              ERR_BIND_OPERATION_NO_PASSWORD.get(String.valueOf(bindDN)));
         }
-        setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
-            simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
 
+        // Perform a number of password policy state checks for the user.
+        checkPasswordPolicyState(userEntry, null);
 
-        // Set resource limits for the authenticated user.
-        setResourceLimits(userEntry);
-
-
-        // Perform any remaining processing for a successful simple
-        // authentication.
-        pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
-        pwPolicyState.clearFailureLockout();
-
-        if (isFirstWarning)
+        // Invoke pre-operation plugins.
+        if (!invokePreOpPlugins())
         {
-          pwPolicyState.setWarnedTime();
-
-          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
-          Message m = WARN_BIND_PASSWORD_EXPIRING.get(
-                           secondsToTimeString(numSeconds));
-
-          pwPolicyState.generateAccountStatusNotification(
-               AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
-               AccountStatusNotification.createProperties(pwPolicyState,
-                     false, numSeconds, null, null));
+          return false;
         }
 
-        if (isGraceLogin)
+        // Determine whether the provided password matches any of the stored
+        // passwords for the user.
+        if (pwPolicyState.passwordMatches(simplePassword))
         {
-          pwPolicyState.updateGraceLoginTimes();
-        }
+          setResultCode(ResultCode.SUCCESS);
 
-        pwPolicyState.setLastLoginTime();
+          if (DirectoryServer.lockdownMode()
+              && (!ClientConnection.hasPrivilege(userEntry,
+                  Privilege.BYPASS_LOCKDOWN)))
+          {
+            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
+          }
+          setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
+              simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
+
+          // Set resource limits for the authenticated user.
+          setResourceLimits(userEntry);
+
+          // Perform any remaining processing for a successful simple
+          // authentication.
+          pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
+          pwPolicyState.clearFailureLockout();
+
+          if (isFirstWarning)
+          {
+            pwPolicyState.setWarnedTime();
+
+            int numSeconds = pwPolicyState.getSecondsUntilExpiration();
+            Message m = WARN_BIND_PASSWORD_EXPIRING
+                .get(secondsToTimeString(numSeconds));
+
+            pwPolicyState.generateAccountStatusNotification(
+                AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
+                AccountStatusNotification.createProperties(pwPolicyState,
+                    false, numSeconds, null, null));
+          }
+
+          if (isGraceLogin)
+          {
+            pwPolicyState.updateGraceLoginTimes();
+          }
+
+          pwPolicyState.setLastLoginTime();
+        }
+        else
+        {
+          setResultCode(ResultCode.INVALID_CREDENTIALS);
+          setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
+
+          if (policy.getLockoutFailureCount() > 0)
+          {
+            pwPolicyState.updateAuthFailureTimes();
+            if (pwPolicyState.lockedDueToFailures())
+            {
+              AccountStatusNotificationType notificationType;
+              Message m;
+
+              boolean tempLocked;
+              int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
+              if (lockoutDuration > -1)
+              {
+                notificationType =
+                  AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
+                tempLocked = true;
+
+                m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED
+                    .get(secondsToTimeString(lockoutDuration));
+              }
+              else
+              {
+                notificationType =
+                  AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
+                tempLocked = false;
+
+                m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
+              }
+
+              pwPolicyState.generateAccountStatusNotification(notificationType,
+                  userEntry, m, AccountStatusNotification.createProperties(
+                      pwPolicyState, tempLocked, -1, null, null));
+            }
+          }
+        }
       }
       else
       {
-        setResultCode(ResultCode.INVALID_CREDENTIALS);
-        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
-
-        if (policy.getLockoutFailureCount() > 0)
+        // Invoke pre-operation plugins.
+        if (!invokePreOpPlugins())
         {
-          pwPolicyState.updateAuthFailureTimes();
-          if (pwPolicyState.lockedDueToFailures())
+          return false;
+        }
+
+        if (authPolicyState.passwordMatches(simplePassword))
+        {
+          setResultCode(ResultCode.SUCCESS);
+
+          if (DirectoryServer.lockdownMode()
+              && (!ClientConnection.hasPrivilege(userEntry,
+                  Privilege.BYPASS_LOCKDOWN)))
           {
-            AccountStatusNotificationType notificationType;
-            Message m;
-
-            boolean tempLocked;
-            int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
-            if (lockoutDuration > -1)
-            {
-              notificationType = AccountStatusNotificationType.
-                                      ACCOUNT_TEMPORARILY_LOCKED;
-              tempLocked = true;
-
-              m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
-                       secondsToTimeString(lockoutDuration));
-            }
-            else
-            {
-              notificationType = AccountStatusNotificationType.
-                                      ACCOUNT_PERMANENTLY_LOCKED;
-              tempLocked = false;
-
-              m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
-            }
-
-            pwPolicyState.generateAccountStatusNotification(
-                 notificationType, userEntry, m,
-                 AccountStatusNotification.createProperties(pwPolicyState,
-                       tempLocked, -1, null, null));
+            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
+                ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
           }
+          setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
+              simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
+
+          // Set resource limits for the authenticated user.
+          setResourceLimits(userEntry);
+        }
+        else
+        {
+          setResultCode(ResultCode.INVALID_CREDENTIALS);
+          setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
         }
       }
 
@@ -728,16 +743,9 @@
     }
 
 
-    // Invoke the pre-operation bind plugins.
-    executePostOpPlugins = true;
-    PluginResult.PreOperation preOpResult =
-        pluginConfigManager.invokePreOperationBindPlugins(this);
-    if (!preOpResult.continueProcessing())
+    // Invoke pre-operation plugins.
+    if (!invokePreOpPlugins())
     {
-      setResultCode(preOpResult.getResultCode());
-      appendErrorMessage(preOpResult.getErrorMessage());
-      setMatchedDN(preOpResult.getMatchedDN());
-      setReferralURLs(preOpResult.getReferralURLs());
       return false;
     }
 
@@ -776,15 +784,9 @@
     // NYI
 
 
-    // Invoke the pre-operation bind plugins.
-    PluginResult.PreOperation preOpResult =
-        pluginConfigManager.invokePreOperationBindPlugins(this);
-    if (!preOpResult.continueProcessing())
+    // Invoke pre-operation plugins.
+    if (!invokePreOpPlugins())
     {
-      setResultCode(preOpResult.getResultCode());
-      appendErrorMessage(preOpResult.getErrorMessage());
-      setMatchedDN(preOpResult.getMatchedDN());
-      setReferralURLs(preOpResult.getReferralURLs());
       return false;
     }
 
@@ -813,21 +815,20 @@
     }
 
     // Create the password policy state object.
-    if (saslAuthUserEntry == null)
+    if (saslAuthUserEntry != null)
     {
-      pwPolicyState = null;
-    }
-    else
-    {
-      // FIXME -- Need to have a way to enable debugging.
-      pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false);
-      policy = pwPolicyState.getPolicy();
       setUserEntryDN(saslAuthUserEntry.getDN());
 
-
-      // Perform password policy checks that will need to be completed
-      // regardless of whether the authentication was successful.
-      checkPasswordPolicyState(saslAuthUserEntry, saslHandler);
+      // FIXME -- Need to have a way to enable debugging.
+      authPolicyState = AuthenticationPolicyState.forUser(
+          saslAuthUserEntry, false);
+      if (authPolicyState.isPasswordPolicy())
+      {
+        // Account is managed locally: perform password policy checks that will
+        // need to be completed regardless of whether the authentication was
+        // successful.
+        checkPasswordPolicyState(saslAuthUserEntry, saslHandler);
+      }
     }
 
 
@@ -836,8 +837,11 @@
     ResultCode resultCode = getResultCode();
     if (resultCode == ResultCode.SUCCESS)
     {
-      if (pwPolicyState != null)
+      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
       {
+        PasswordPolicyState pwPolicyState =
+          (PasswordPolicyState) authPolicyState;
+
         if (saslHandler.isPasswordBased(saslMechanism) &&
             pwPolicyState.mustChangePassword())
         {
@@ -865,11 +869,10 @@
         }
 
         pwPolicyState.setLastLoginTime();
-
-
-        // Set appropriate resource limits for the user.
-        setResourceLimits(saslAuthUserEntry);
       }
+
+      // Set appropriate resource limits for the user.
+      setResourceLimits(saslAuthUserEntry);
     }
     else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
     {
@@ -878,12 +881,15 @@
     }
     else
     {
-      if (pwPolicyState != null)
+      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
       {
+        PasswordPolicyState pwPolicyState =
+          (PasswordPolicyState) authPolicyState;
+
         if (saslHandler.isPasswordBased(saslMechanism))
         {
-
-          if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
+          if (pwPolicyState.getAuthenticationPolicy()
+              .getLockoutFailureCount() > 0)
           {
             pwPolicyState.updateAuthFailureTimes();
             if (pwPolicyState.lockedDueToFailures())
@@ -924,20 +930,45 @@
 
 
 
+  private boolean invokePreOpPlugins()
+  {
+    executePostOpPlugins = true;
+    PluginResult.PreOperation preOpResult = pluginConfigManager
+        .invokePreOperationBindPlugins(this);
+    if (!preOpResult.continueProcessing())
+    {
+      setResultCode(preOpResult.getResultCode());
+      appendErrorMessage(preOpResult.getErrorMessage());
+      setMatchedDN(preOpResult.getMatchedDN());
+      setReferralURLs(preOpResult.getReferralURLs());
+      return false;
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+
+
   /**
    * Validates a number of password policy state constraints for the user.
    *
-   * @param  userEntry    The entry for the user that is authenticating.
-   * @param  saslHandler  The SASL mechanism handler if this is a SASL bind, or
-   *                      {@code null} for a simple bind.
-   *
-   * @throws  DirectoryException  If a problem occurs that should cause the bind
-   *                              to fail.
+   * @param userEntry
+   *          The entry for the user that is authenticating.
+   * @param saslHandler
+   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
+   *          for a simple bind.
+   * @throws DirectoryException
+   *           If a problem occurs that should cause the bind to fail.
    */
-  protected void checkPasswordPolicyState(Entry userEntry,
-                                          SASLMechanismHandler<?> saslHandler)
-          throws DirectoryException
+  protected void checkPasswordPolicyState(
+      Entry userEntry, SASLMechanismHandler<?> saslHandler)
+      throws DirectoryException
   {
+    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
+    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
+
     boolean isSASLBind = (saslHandler != null);
 
     // If the password policy is configured to track authentication failures or
diff --git a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 3fc9444..327323e 100644
--- a/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -44,12 +44,7 @@
 
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
-import org.opends.server.api.AttributeSyntax;
-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.*;
 import org.opends.server.api.plugin.PluginResult;
 import org.opends.server.controls.LDAPAssertionRequestControl;
 import org.opends.server.controls.LDAPPostReadRequestControl;
@@ -67,14 +62,12 @@
 import org.opends.server.core.PluginConfigManager;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.schema.AuthPasswordSyntax;
-import org.opends.server.schema.BooleanSyntax;
 import org.opends.server.schema.UserPasswordSyntax;
 import org.opends.server.types.*;
 import org.opends.server.types.operation.PostOperationModifyOperation;
 import org.opends.server.types.operation.PostResponseModifyOperation;
 import org.opends.server.types.operation.PostSynchronizationModifyOperation;
 import org.opends.server.types.operation.PreOperationModifyOperation;
-import org.opends.server.util.TimeThread;
 import org.opends.server.util.Validator;
 
 
@@ -141,7 +134,7 @@
   /**
    * Indicates whether the user's account was locked before this change.
    */
-  protected boolean wasLocked;
+  protected boolean wasLocked = false;
 
   /**
    * The client connection associated with this operation.
@@ -451,8 +444,13 @@
           selfChange = entryDN.equals(getAuthorizationDN());
 
           // FIXME -- Need a way to enable debug mode.
-          pwPolicyState = new PasswordPolicyState(currentEntry, false,
-                                                  TimeThread.getTime(), true);
+          AuthenticationPolicy policy = AuthenticationPolicy.forUser(
+              currentEntry, true);
+          if (policy.isPasswordPolicy())
+          {
+            pwPolicyState = (PasswordPolicyState) policy
+                .createAuthenticationPolicyState(currentEntry);
+          }
         }
         catch (DirectoryException de)
         {
@@ -525,12 +523,7 @@
         try
         {
           handleInitialPasswordPolicyProcessing();
-
-          wasLocked = false;
-          if (passwordChanged)
-          {
-            performAdditionalPasswordChangedProcessing();
-          }
+          performAdditionalPasswordChangedProcessing();
         }
         catch (DirectoryException de)
         {
@@ -632,20 +625,16 @@
           }
           else
           {
-              if(!processPreOperation()) {
-                  break modifyProcessing;
-              }
+            if (!processPreOperation())
+            {
+              break modifyProcessing;
+            }
 
             backend.replaceEntry(currentEntry, modifiedEntry, this);
 
-
-
             // See if we need to generate any account status notifications as a
             // result of the changes.
-            if (passwordChanged || enabledStateChanged || wasLocked)
-            {
-              handleAccountStatusNotifications();
-            }
+            handleAccountStatusNotifications();
           }
 
 
@@ -997,36 +986,12 @@
       }
 
       // If the modification is not updating the password attribute,
-      // then check if the isEnabled flag should be set and then perform any
-      // schema processing.
-      boolean isPassword =
-              t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
+      // then perform any schema processing.
+      boolean isPassword = (pwPolicyState != null)
+          && t.equals(pwPolicyState.getAuthenticationPolicy()
+              .getPasswordAttribute());
       if (!isPassword )
       {
-        // See if it's an attribute used to maintain the account
-        // enabled/disabled state.
-        AttributeType disabledAttr =
-               DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
-        if (t.equals(disabledAttr))
-        {
-          enabledStateChanged = true;
-          for (AttributeValue v : a)
-          {
-            try
-            {
-              isEnabled =
-                  (! BooleanSyntax.DECODER.decode(v));
-            }
-            catch (DirectoryException de)
-            {
-              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                      ERR_MODIFY_INVALID_DISABLED_VALUE.get(
-                              OP_ATTR_ACCOUNT_DISABLED,
-                              String.valueOf(de.getMessageObject())), de);
-            }
-          }
-        }
-
         switch (m.getModificationType())
         {
           case ADD:
@@ -1062,8 +1027,15 @@
     currentPasswordProvided = false;
     isEnabled = true;
     enabledStateChanged = false;
+
+    if (pwPolicyState == null)
+    {
+      // Account not managed locally so nothing to do.
+      return;
+    }
+
     if (currentEntry.hasAttribute(
-            pwPolicyState.getPolicy().getPasswordAttribute()))
+            pwPolicyState.getAuthenticationPolicy().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
@@ -1085,8 +1057,8 @@
       for (Modification m : modifications)
       {
         AttributeType t = m.getAttribute().getAttributeType();
-        boolean isPassword =
-                t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
+        boolean isPassword = t.equals(pwPolicyState.getAuthenticationPolicy()
+            .getPasswordAttribute());
         if (isPassword)
         {
           passwordChanged = true;
@@ -1116,8 +1088,8 @@
       // If the modification is updating the password attribute, then perform
       // any necessary password policy processing.  This processing should be
       // skipped for synchronization operations.
-      boolean isPassword =
-              t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
+      boolean isPassword = t.equals(pwPolicyState.getAuthenticationPolicy()
+          .getPasswordAttribute());
       if (isPassword)
       {
         if (!isSynchronizationOperation())
@@ -1135,8 +1107,9 @@
 
 
             // If it's a self change, then see if that's allowed.
-            if (selfChange &&
-                (! pwPolicyState.getPolicy().isAllowUserPasswordChanges()))
+            if (selfChange
+                && (!pwPolicyState.getAuthenticationPolicy()
+                    .isAllowUserPasswordChanges()))
             {
               pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
               throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1146,8 +1119,9 @@
 
             // If we require secure password changes, then makes sure it's a
             // secure communication channel.
-            if (pwPolicyState.getPolicy().isRequireSecurePasswordChanges() &&
-                (! clientConnection.isSecure()))
+            if (pwPolicyState.getAuthenticationPolicy()
+                .isRequireSecurePasswordChanges()
+                && (!clientConnection.isSecure()))
             {
               pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
               throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1242,8 +1216,8 @@
     // If there were multiple password values, then make sure that's
     // OK.
     if ((!isInternalOperation())
-        && (!pwPolicyState.getPolicy().isAllowMultiplePasswordValues())
-        && (passwordsToAdd > 1))
+        && (!pwPolicyState.getAuthenticationPolicy()
+            .isAllowMultiplePasswordValues()) && (passwordsToAdd > 1))
     {
       pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1260,7 +1234,8 @@
       if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
       {
         if ((!isInternalOperation())
-            && !pwPolicyState.getPolicy().isAllowPreEncodedPasswords())
+            && !pwPolicyState.getAuthenticationPolicy()
+                .isAllowPreEncodedPasswords())
         {
           pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
           throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1382,7 +1357,7 @@
         {
           for (AttributeValue av : attr)
           {
-            if (pwPolicyState.getPolicy().isAuthPasswordSyntax())
+            if (pwPolicyState.getAuthenticationPolicy().isAuthPasswordSyntax())
             {
               if (AuthPasswordSyntax.isEncoded(av.getValue()))
               {
@@ -1867,33 +1842,48 @@
   public void performAdditionalPasswordChangedProcessing()
          throws DirectoryException
   {
+    if (pwPolicyState == null)
+    {
+      // Account not managed locally so nothing to do.
+      return;
+    }
+
+    if (!passwordChanged)
+    {
+      // Nothing to do.
+      return;
+    }
+
     // If it was a self change, then see if the current password was provided
     // and handle accordingly.
-    if (selfChange &&
-        pwPolicyState.getPolicy().isPasswordChangeRequiresCurrentPassword() &&
-        (! currentPasswordProvided))
+    if (selfChange
+        && pwPolicyState.getAuthenticationPolicy()
+            .isPasswordChangeRequiresCurrentPassword()
+        && (!currentPasswordProvided))
     {
       pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
 
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                     ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
+          ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
     }
 
 
     // If this change would result in multiple password values, then see if
     // that's OK.
-    if ((numPasswords > 1) &&
-        (! pwPolicyState.getPolicy().isAllowMultiplePasswordValues()))
+    if ((numPasswords > 1)
+        && (!pwPolicyState.getAuthenticationPolicy()
+            .isAllowMultiplePasswordValues()))
     {
       pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
       throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                     ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
+          ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
     }
 
 
     // If any of the password values should be validated, then do so now.
-    if (selfChange ||
-        (! pwPolicyState.getPolicy().isSkipValidationForAdministrators()))
+    if (selfChange
+        || (!pwPolicyState.getAuthenticationPolicy()
+            .isSkipValidationForAdministrators()))
     {
       if (newPasswords != null)
       {
@@ -1965,7 +1955,7 @@
         {
           if (pwPolicyState.isPasswordInHistory(v.getValue()))
           {
-            if (selfChange || (! pwPolicyState.getPolicy().
+            if (selfChange || (! pwPolicyState.getAuthenticationPolicy().
                                       isSkipValidationForAdministrators()))
             {
               pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
@@ -1992,8 +1982,8 @@
     pwPolicyState.clearGraceLoginTimes();
     pwPolicyState.clearWarnedTime();
 
-    if (pwPolicyState.getPolicy().isForceChangeOnAdd() ||
-        pwPolicyState.getPolicy().isForceChangeOnReset())
+    if (pwPolicyState.getAuthenticationPolicy().isForceChangeOnAdd() ||
+        pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset())
     {
       if (selfChange)
       {
@@ -2002,17 +1992,17 @@
       else
       {
         if ((pwpErrorType == null) &&
-            pwPolicyState.getPolicy().isForceChangeOnReset())
+            pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset())
         {
           pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
         }
 
         pwPolicyState.setMustChangePassword(
-             pwPolicyState.getPolicy().isForceChangeOnReset());
+             pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset());
       }
     }
 
-    if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
+    if (pwPolicyState.getAuthenticationPolicy().getRequireChangeByTime() > 0)
     {
       pwPolicyState.setRequiredChangeTime();
     }
@@ -2079,6 +2069,18 @@
    */
   protected void handleAccountStatusNotifications()
   {
+    if (pwPolicyState == null)
+    {
+      // Account not managed locally, so nothing to do.
+      return;
+    }
+
+    if (!(passwordChanged || enabledStateChanged || wasLocked))
+    {
+      // Account managed locally, but unchanged, so nothing to do.
+      return;
+    }
+
     if (passwordChanged)
     {
       if (selfChange)
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
index 9a3419b..d9d5e40 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java
@@ -36,6 +36,7 @@
 import org.testng.annotations.Test;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.types.AttributeType;
 import org.opends.server.types.AttributeValues;
@@ -304,19 +305,15 @@
             "uid=rogasawara," + BASE));
     assertNotNull(testEntry);
 
-    PasswordPolicyState state =
-            new PasswordPolicyState(testEntry, false);
-    assertNotNull(state);
-    PasswordPolicy statePolicy = state.getPolicy();
+    AuthenticationPolicy statePolicy = AuthenticationPolicy.forUser(testEntry,
+        false);
     assertNotNull(statePolicy);
     assertEquals(policy, statePolicy);
 
     // Make sure this policy is gone and default
     // policy is in effect instead.
     TestCaseUtils.deleteEntry(policyEntry.getDN());
-    state = new PasswordPolicyState(testEntry, false);
-    assertNotNull(state);
-    statePolicy = state.getPolicy();
+    statePolicy = AuthenticationPolicy.forUser(testEntry, false);
     assertNotNull(statePolicy);
     assertEquals(defaultPolicy, statePolicy);
   }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java
index 9d94aa8..4345088 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java
@@ -41,6 +41,7 @@
 import org.opends.server.TestCaseUtils;
 import org.opends.messages.Message;
 import org.opends.server.api.AccountStatusNotificationHandler;
+import org.opends.server.api.AuthenticationPolicy;
 import org.opends.server.admin.server.AdminTestCaseUtils;
 import org.opends.server.admin.std.meta.
        ErrorLogAccountStatusNotificationHandlerCfgDefn;
@@ -50,7 +51,6 @@
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.PasswordPolicy;
-import org.opends.server.core.PasswordPolicyState;
 import org.opends.server.types.AccountStatusNotification;
 import org.opends.server.types.AccountStatusNotificationProperty;
 import org.opends.server.types.AccountStatusNotificationType;
@@ -189,7 +189,7 @@
     String dnStr = "cn=Error Log Handler,cn=Account Status Notification " +
                         "Handlers,cn=config";
     DN handlerDN = DN.decode(dnStr);
-    AccountStatusNotificationHandler handler =
+    AccountStatusNotificationHandler<?> handler =
          DirectoryServer.getAccountStatusNotificationHandler(handlerDN);
     assertNotNull(handler);
     assertTrue(handler instanceof ErrorLogAccountStatusNotificationHandler);
@@ -250,16 +250,15 @@
     String dnStr = "cn=Error Log Handler,cn=Account Status Notification " +
                         "Handlers,cn=config";
     DN handlerDN = DN.decode(dnStr);
-    AccountStatusNotificationHandler handler =
+    AccountStatusNotificationHandler<?> handler =
          DirectoryServer.getAccountStatusNotificationHandler(handlerDN);
     assertNotNull(handler);
 
     Entry userEntry =
                DirectoryServer.getEntry(DN.decode("uid=test.user,o=test"));
 
-    PasswordPolicyState pwPolicyState =
-         new PasswordPolicyState(userEntry, false);
-    PasswordPolicy policy = pwPolicyState.getPolicy();
+    PasswordPolicy policy = (PasswordPolicy) AuthenticationPolicy.forUser(
+        userEntry, false);
 
     HashMap<AccountStatusNotificationProperty,List<String>>
          notificationProperties =

--
Gitblit v1.10.0