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

Matthew Swift
25.27.2011 3883d2297c3422d8aec2b40530c2d2b0a00ee57d
Final refactoring work for OPENDJ-262: Implement pass through authentication (PTA)

Introduce AuthenticationPolicyState abstract class for managing bind context. This interface will allow PTA implementations to persist state to the user's entry as needed (e.g. cached passwords).
1 files added
22 files modified
1682 ■■■■ changed files
opends/src/messages/messages/extension.properties 7 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AuthenticationPolicy.java 244 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/AuthenticationPolicyState.java 143 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/controls/PasswordPolicyResponseControl.java 8 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java 18 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java 56 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CoreConfigManager.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicy.java 30 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/PasswordPolicyState.java 498 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SearchOperationBasis.java 23 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/CRAMMD5SASLMechanismHandler.java 20 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java 56 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordPolicyStateExtendedOperation.java 21 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PasswordPolicySubentryVirtualAttributeProvider.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java 41 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/SASLContext.java 52 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/plugins/PasswordPolicyImportPlugin.java 10 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AccountStatusNotification.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 56 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java 209 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 154 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/SubentryPasswordPolicyTestCase.java 11 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ErrorLogAccountStatusNotificationHandlerTestCase.java 11 ●●●● patch | view | raw | blame | history
opends/src/messages/messages/extension.properties
@@ -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
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.
opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
New file
@@ -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();
  }
}
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);
  }
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,18 +325,24 @@
      // FIXME -- We should provide some mechanism for enabling debug
      // processing.
      PasswordPolicyState pwpState = new PasswordPolicyState(userEntry, false);
      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_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
        throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
          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;
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.
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
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);
  }
}
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
   * 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)
  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
@@ -251,163 +216,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.
    *
    * @param  attributeType  The attribute type whose value should be retrieved.
@@ -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,18 +576,9 @@
      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));
      }
    }
  }
@@ -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));
    }
    // Fall back to using the entry creation time as the password changed time,
@@ -930,32 +720,15 @@
    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));
      }
    }
    else
    {
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      }
    }
  }
@@ -1089,18 +862,9 @@
                                            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));
      }
    }
  }
@@ -1121,16 +885,9 @@
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                          true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      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,
            Attributes.empty(type), true));
      }
      return authFailureTimes;
    }
@@ -1245,27 +995,6 @@
      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();
@@ -1273,7 +1002,6 @@
                                             true));
        }
      }
    }
    if (debugEnabled())
    {
@@ -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));
    }
    // 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));
    }
    // Now check to see if there have been sufficient failures to lock the
    // account.
@@ -1458,16 +1170,9 @@
                                  OP_ATTR_PWPOLICY_FAILURE_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
  /**
@@ -1544,17 +1249,8 @@
    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));
    }
  }
@@ -1585,16 +1281,9 @@
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      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));
    }
    if (debugEnabled())
    {
@@ -1972,16 +1652,9 @@
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -2195,31 +1868,14 @@
    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));
      }
    }
    else
    {
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      }
    }
  }
@@ -2732,18 +2388,9 @@
      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));
      }
    }
  }
@@ -2763,16 +2410,9 @@
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      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));
    }
    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));
    }
    if (debugEnabled())
    {
@@ -2955,17 +2579,10 @@
        graceLoginTimes = new ArrayList<Long>();
        if (updateEntry)
        {
          userEntry.removeAttribute(type);
        }
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
              Attributes.empty(type), true));
        }
      }
    }
    if (debugEnabled())
@@ -3036,28 +2653,11 @@
                                  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)));
      }
      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));
    }
  }
@@ -3094,18 +2694,8 @@
    }
    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));
    }
  }
@@ -3135,16 +2725,9 @@
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      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,16 +3221,6 @@
      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();
@@ -3666,7 +3233,6 @@
        Attribute a2 = builder.toAttribute();
        modifications.add(new Modification(ModificationType.ADD, a2, true));
      }
    }
    if (debugEnabled())
    {
@@ -4155,18 +3721,6 @@
    // Apply the changes, either by adding modifications or by directly updating
    // the entry.
    if (updateEntry)
    {
      LinkedList<AttributeValue> valueList = new LinkedList<AttributeValue>();
      for (Attribute a : removeAttrs)
      {
        userEntry.removeAttribute(a, valueList);
      }
      userEntry.addAttribute(newHistAttr, valueList);
    }
    else
    {
      for (Attribute a : removeAttrs)
      {
        modifications.add(new Modification(ModificationType.DELETE, a, true));
@@ -4175,7 +3729,6 @@
      modifications.add(new Modification(ModificationType.ADD, newHistAttr,
                                         true));
    }
  }
@@ -4221,16 +3774,9 @@
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_HISTORY_LC, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      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.
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;
@@ -646,13 +648,18 @@
      try
      {
        // FIXME -- Need a way to enable PWP debugging.
        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false);
        AuthenticationPolicy policy = AuthenticationPolicy
            .forUser(entry, false);
        if (policy.isPasswordPolicy())
        {
          PasswordPolicyState pwpState = (PasswordPolicyState) policy
              .createAuthenticationPolicyState(entry);
        boolean isInactive           = pwpState.isDisabled() ||
                                       pwpState.isAccountExpired();
        boolean isLocked             = pwpState.lockedDueToFailures() ||
                                       pwpState.lockedDueToMaximumResetAge() ||
                                       pwpState.lockedDueToIdleInterval();
          boolean isInactive = pwpState.isDisabled()
              || pwpState.isAccountExpired();
          boolean isLocked = pwpState.lockedDueToFailures()
              || pwpState.lockedDueToMaximumResetAge()
              || pwpState.lockedDueToIdleInterval();
        boolean isReset              = pwpState.mustChangePassword();
        boolean isExpired            = pwpState.isPasswordExpired();
@@ -666,7 +673,8 @@
            controls = new ArrayList<Control>(1);
          }
          controls.add(new AccountUsableResponseControl(isInactive, isReset,
            controls
                .add(new AccountUsableResponseControl(isInactive, isReset,
                                isExpired, remainingGraceLogins, isLocked,
                                secondsBeforeUnlock));
        }
@@ -682,6 +690,7 @@
                                secondsBeforeExpiration));
        }
      }
      }
      catch (Exception e)
      {
        if (debugEnabled())
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())
      {
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);
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)
        {
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");
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;
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,14 +336,27 @@
        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);
    }
        if (cbMsg != null)
        {
          bindOp.setAuthFailureReason(cbMsg);
        }
        else
        {
          bindOp.setAuthFailureReason(msg);
        }
    }
    /**
     * Checks the specified authentication information parameter against the
@@ -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(
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)
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());
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());
      // The entry doesn't have a locally managed password, so no action is
      // required.
      return;
        }
        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)));
        }
      }
    }
    if (passwordPolicy == null)
    {
      passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
    }
    PasswordPolicy passwordPolicy = (PasswordPolicy) policy;
    // See if a password was specified.
    AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
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)
@@ -571,45 +562,40 @@
      // 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();
      authPolicyState = AuthenticationPolicyState.forUser(userEntry, false);
      if (authPolicyState.isPasswordPolicy())
      {
        // Account is managed locally.
        PasswordPolicyState pwPolicyState =
          (PasswordPolicyState) authPolicyState;
        PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
      AttributeType  pwType = policy.getPasswordAttribute();
      List<Attribute> pwAttr = userEntry.getAttribute(pwType);
      if ((pwAttr == null) || (pwAttr.isEmpty()))
      {
        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                                     ERR_BIND_OPERATION_NO_PASSWORD.get(
                                          String.valueOf(bindDN)));
              ERR_BIND_OPERATION_NO_PASSWORD.get(String.valueOf(bindDN)));
      }
      // 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())
        // Invoke pre-operation plugins.
        if (!invokePreOpPlugins())
      {
        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,
          if (DirectoryServer.lockdownMode()
              && (!ClientConnection.hasPrivilege(userEntry,
                Privilege.BYPASS_LOCKDOWN)))
        {
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
@@ -618,11 +604,9 @@
        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);
@@ -633,8 +617,8 @@
          pwPolicyState.setWarnedTime();
          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
          Message m = WARN_BIND_PASSWORD_EXPIRING.get(
                           secondsToTimeString(numSeconds));
            Message m = WARN_BIND_PASSWORD_EXPIRING
                .get(secondsToTimeString(numSeconds));
          pwPolicyState.generateAccountStatusNotification(
               AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
@@ -666,29 +650,60 @@
            int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
            if (lockoutDuration > -1)
            {
              notificationType = AccountStatusNotificationType.
                                      ACCOUNT_TEMPORARILY_LOCKED;
                notificationType =
                  AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
              tempLocked = true;
              m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
                       secondsToTimeString(lockoutDuration));
                m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED
                    .get(secondsToTimeString(lockoutDuration));
            }
            else
            {
              notificationType = AccountStatusNotificationType.
                                      ACCOUNT_PERMANENTLY_LOCKED;
                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));
              pwPolicyState.generateAccountStatusNotification(notificationType,
                  userEntry, m, AccountStatusNotification.createProperties(
                      pwPolicyState, tempLocked, -1, null, null));
          }
        }
      }
      }
      else
      {
        // Invoke pre-operation plugins.
        if (!invokePreOpPlugins())
        {
          return false;
        }
        if (authPolicyState.passwordMatches(simplePassword))
        {
          setResultCode(ResultCode.SUCCESS);
          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);
        }
        else
        {
          setResultCode(ResultCode.INVALID_CREDENTIALS);
          setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
        }
      }
      return true;
    }
@@ -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,22 +815,21 @@
    }
    // 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.
      // 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);
    }
    }
    // Determine whether the authentication was successful and perform
@@ -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,12 +869,11 @@
        }
        pwPolicyState.setLastLoginTime();
      }
        // Set appropriate resource limits for the user.
        setResourceLimits(saslAuthUserEntry);
      }
    }
    else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
    {
      // FIXME -- Is any special processing needed here?
@@ -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)
  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
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,13 +523,8 @@
        try
        {
          handleInitialPasswordPolicyProcessing();
          wasLocked = false;
          if (passwordChanged)
          {
            performAdditionalPasswordChangedProcessing();
          }
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
@@ -632,21 +625,17 @@
          }
          else
          {
              if(!processPreOperation()) {
            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();
            }
          }
          // Handle any processing that may be needed for the pre-read and/or
@@ -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,11 +1842,24 @@
  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;
@@ -1882,8 +1870,9 @@
    // 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,
@@ -1892,8 +1881,9 @@
    // 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)
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);
  }
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 =