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

Matthew Swift
25.27.2011 3883d2297c3422d8aec2b40530c2d2b0a00ee57d
opends/src/messages/messages/extension.properties
@@ -587,7 +587,7 @@
INFO_SASLCRAMMD5_UPDATED_USER_BASE_DN_191=Attribute ds-cfg-user-base-dn in \
 configuration entry %s has been updated.  The DN %s will now be used as the \
 search base when looking up user entries based on their username
 INFO_SASL_UNSUPPORTED_CALLBACK_192=An unsupported or unexpected callback was \
INFO_SASL_UNSUPPORTED_CALLBACK_192=An unsupported or unexpected callback was \
 provided to the SASL server for use during %s authentication:  %s
MILD_ERR_SASL_NO_CREDENTIALS_193=The client connection included \
 %s state information, indicating that the client was in the process \
@@ -1401,7 +1401,7 @@
SEVERE_ERR_SASLDIGESTMD5_PROTOCOL_ERROR_570=SASL DIGEST MD5 protocol error: %s
INFO_LOG_EXTENSION_INFORMATION_571=Loaded extension from file '%s' (build %s, \
 revision %s)
 SEVERE_ERR_SASL_CREATE_SASL_SERVER_FAILED_572=Failed to create a SASL server \
SEVERE_ERR_SASL_CREATE_SASL_SERVER_FAILED_572=Failed to create a SASL server \
 for SASL mechanism %s using a server FQDN of %s
SEVERE_ERR_SASL_GSSAPI_KEYTAB_INVALID_573=GSSAPI SASL mechanism handler initalization \
failed because the keytab file %s does not exist
@@ -1427,3 +1427,10 @@
 character sets: %s
MILD_ERR_STATICMEMBERS_CANNOT_DECODE_DN_582=An error occurred while \
 attempting to decode member's DN %s of static group %s:  %s
MILD_ERR_SASL_ACCOUNT_NOT_LOCAL_583=SASL %s authentication \
 is not supported for user %s because the account is not managed locally
MILD_ERR_EXTOP_PASSMOD_ACCOUNT_NOT_LOCAL_584=Password modification is not \
 supported for user %s because the account is not managed locally
MILD_ERR_EXTOP_PWPSTATE_ACCOUNT_NOT_LOCAL_585=The password policy state \
 extended operation is not supported for user %s because the account is not \
 managed locally
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,19 +325,25 @@
      // FIXME -- We should provide some mechanism for enabling debug
      // processing.
      PasswordPolicyState pwpState = new PasswordPolicyState(userEntry, false);
      if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
          pwpState.lockedDueToFailures() ||
          pwpState.lockedDueToIdleInterval() ||
          pwpState.lockedDueToMaximumResetAge() ||
          pwpState.isPasswordExpired())
      AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
          false);
      if (policy.isPasswordPolicy())
      {
        Message message =
            ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String.valueOf(authzDN));
        throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
        PasswordPolicyState pwpState = (PasswordPolicyState) policy
            .createAuthenticationPolicyState(userEntry);
        if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
            pwpState.lockedDueToFailures() ||
            pwpState.lockedDueToIdleInterval() ||
            pwpState.lockedDueToMaximumResetAge() ||
            pwpState.isPasswordExpired())
        {
          Message message = ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String
              .valueOf(authzDN));
          throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED,
              message);
        }
      }
      // If we've made it here, then the user is acceptable.
      return userEntry;
    }
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
   * the actual current time. For all other purposes, the other constructor
   * should be used.
   *
   * @param  userEntry          The entry with the user account.
   * @param  updateEntry        Indicates whether changes should update the
   *                            provided user entry directly or whether they
   *                            should be collected as a set of modifications.
   * @param  currentTime        The time to use as the current time for all
   *                            time-related determinations.
   * @param  useDefaultOnError  Indicates whether the server should fall back to
   *                            using the default password policy if there is a
   *                            problem with the configured policy for the user.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              determine the password policy for the user or
   *                              perform any other state initialization.
   * @param policy
   *          The password policy associated with the state.
   * @param userEntry
   *          The entry with the user account.
   * @param currentTime
   *          The time to use as the current time for all time-related
   *          determinations.
   * @throws DirectoryException
   *           If a problem occurs while attempting to determine the password
   *           policy for the user or perform any other state initialization.
   */
  public PasswordPolicyState(Entry userEntry, boolean updateEntry,
                             long currentTime, boolean useDefaultOnError)
       throws DirectoryException
  PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime)
      throws DirectoryException
  {
    this.userEntry   = userEntry;
    this.updateEntry = updateEntry;
    this.currentTime = currentTime;
    userDNString     = userEntry.getDN().toString();
    passwordPolicy   = getPasswordPolicy(this.userEntry,
                                         useDefaultOnError);
    this.userDNString     = userEntry.getDN().toString();
    this.passwordPolicy   = policy;
    // Get the password changed time for the user.
    AttributeType type
@@ -250,163 +215,6 @@
  /**
   * Retrieves the password policy for the user. If the user entry contains the
   * ds-pwp-password-policy-dn attribute (whether real or virtual), that
   * password policy is returned, otherwise applicable to the user entry
   * subentry password policy is returned, if any, otherwise the default
   * password policy is returned.
   *
   * @param  userEntry          The user entry.
   * @param  useDefaultOnError  Indicates whether the server should fall back to
   *                            using the default password policy if there is a
   *                            problem with the configured policy for the user.
   *
   * @return  The password policy for the user.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              determine the password policy for the user.
   */
  public static PasswordPolicy getPasswordPolicy(Entry userEntry,
                                     boolean useDefaultOnError)
       throws DirectoryException
  {
    String userDNString = userEntry.getDN().toString();
    AttributeType type = DirectoryServer.getAttributeType(
            OP_ATTR_PWPOLICY_POLICY_DN, true);
    List<Attribute> attrList = userEntry.getAttribute(type);
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        if (a.isEmpty()) continue;
        AttributeValue v = a.iterator().next();
        DN subentryDN;
        try
        {
          subentryDN = DN.decode(v.getValue());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          if (debugEnabled())
          {
            TRACER.debugError("Could not parse password policy subentry " +
                "DN %s for user %s: %s",
                       v.getValue().toString(), userDNString,
                       stackTraceToSingleLineString(e));
          }
          Message message = ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN.get(
              v.getValue().toString(), userDNString, e.getMessage());
          if (useDefaultOnError)
          {
            ErrorLogger.logError(message);
            return DirectoryServer.getDefaultPasswordPolicy();
          }
          else
          {
            throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message,
                                         e);
          }
        }
        PasswordPolicy policy = (PasswordPolicy) DirectoryServer
            .getAuthenticationPolicy(subentryDN);
        if (policy == null)
        {
          if (debugEnabled())
          {
            TRACER.debugError("Password policy subentry %s for user %s " +
                 "is not defined in the Directory Server.",
                       String.valueOf(subentryDN), userDNString);
          }
          Message message = ERR_PWPSTATE_NO_SUCH_POLICY.get(
              userDNString, String.valueOf(subentryDN));
          if (useDefaultOnError)
          {
            ErrorLogger.logError(message);
            return DirectoryServer.getDefaultPasswordPolicy();
          }
          else
          {
            throw new DirectoryException(
                 DirectoryServer.getServerErrorResultCode(), message);
          }
        }
        if (debugEnabled())
        {
          TRACER.debugInfo("Using password policy subentry %s for user %s.",
              String.valueOf(subentryDN), userDNString);
        }
        return policy;
      }
    }
    // No attribute defined password policy: try locating and using the
    // closest to this entry password policy subentry defined, if any.
    List<SubEntry> pwpSubEntries =
            DirectoryServer.getSubentryManager().getSubentries(userEntry);
    if ((pwpSubEntries != null) && (!pwpSubEntries.isEmpty()))
    {
      for (SubEntry subentry : pwpSubEntries)
      {
        try
        {
          if (subentry.getEntry().isPasswordPolicySubentry())
          {
            PasswordPolicy policy = (PasswordPolicy) DirectoryServer
                .getAuthenticationPolicy(subentry.getDN());
            if (policy == null)
            {
              // This shouldnt happen but if it does debug log
              // this problem and fall back to default policy.
              if (debugEnabled())
              {
                TRACER.debugError(
                        "Found unknown password policy subentry "
                        + "DN %s for user %s",
                        subentry.getDN().toString(), userDNString);
              }
              break;
            }
            return policy;
          }
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugError("Could not parse password policy subentry "
                    + "DN %s for user %s: %s",
                    subentry.getDN().toString(), userDNString,
                    stackTraceToSingleLineString(e));
          }
        }
      }
    }
    // There is no policy subentry defined: use the default.
    if (debugEnabled())
    {
      TRACER.debugInfo("Using the default password policy for user %s",
          userDNString);
    }
    return DirectoryServer.getDefaultPasswordPolicy();
  }
   /**
    * Retrieves the value of the specified attribute as a string.
    *
@@ -667,11 +475,9 @@
  /**
   * Retrieves the password policy associated with this state information.
   *
   * @return  The password policy associated with this state information.
   * {@inheritDoc}
   */
  public PasswordPolicy getPolicy()
  public PasswordPolicy getAuthenticationPolicy()
  {
    return passwordPolicy;
  }
@@ -770,16 +576,7 @@
      Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_TIME,
          timeValue);
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(a.getAttributeType(), attrList);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
  }
@@ -801,15 +598,8 @@
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC,
                                       true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      Attribute a = Attributes.empty(type);
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    Attribute a = Attributes.empty(type);
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    // Fall back to using the entry creation time as the password changed time,
@@ -930,30 +720,13 @@
    if (isDisabled)
    {
      Attribute a = Attributes.create(type, String.valueOf(true));
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    else
    {
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
      modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      }
    }
  }
@@ -1089,16 +862,7 @@
                                            true);
      Attribute a = Attributes.create(type, timeStr);
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
  }
@@ -1121,15 +885,8 @@
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                          true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
          Attributes.empty(type), true));
    }
  }
@@ -1187,15 +944,8 @@
      authFailureTimes = new ArrayList<Long>();
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
      modifications.add(new Modification(ModificationType.REPLACE,
            Attributes.empty(type), true));
      }
      return authFailureTimes;
    }
@@ -1245,33 +995,11 @@
      if (valuesToRemove != null)
      {
        if (updateEntry)
        {
          if (authFailureTimes.isEmpty())
          {
            userEntry.removeAttribute(type);
          }
          else
          {
            AttributeBuilder builder = new AttributeBuilder(type);
            for (Long l : authFailureTimes)
            {
              builder.add(
                 AttributeValues.create(type, GeneralizedTimeSyntax.format(l)));
            }
            ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
            keepList.add(builder.toAttribute());
            userEntry.putAttribute(type, keepList);
          }
        }
        else
        {
          AttributeBuilder builder = new AttributeBuilder(type);
          builder.addAll(valuesToRemove);
          Attribute a = builder.toAttribute();
          modifications.add(new Modification(ModificationType.DELETE, a,
                                             true));
        }
        AttributeBuilder builder = new AttributeBuilder(type);
        builder.addAll(valuesToRemove);
        Attribute a = builder.toAttribute();
        modifications.add(new Modification(ModificationType.DELETE, a,
            true));
      }
    }
@@ -1344,14 +1072,7 @@
    Attribute addAttr = Attributes.create(type, AttributeValues.create(type,
        GeneralizedTimeSyntax.format(highestFailureTime)));
    if (updateEntry)
    {
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
    modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    // Now check to see if there have been sufficient failures to lock the
    // account.
@@ -1403,16 +1124,7 @@
    }
    Attribute a = builder.toAttribute();
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    // Now check to see if there have been sufficient failures to lock the
    // account.
@@ -1458,15 +1170,8 @@
                                  OP_ATTR_PWPOLICY_FAILURE_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -1544,16 +1249,7 @@
    Attribute a = Attributes.create(type, AttributeValues.create(type,
        GeneralizedTimeSyntax.format(failureLockedTime)));
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
  }
@@ -1585,15 +1281,8 @@
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -1936,16 +1625,7 @@
    Attribute a = Attributes.create(type, timestamp);
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    if (debugEnabled())
    {
@@ -1972,15 +1652,8 @@
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -2195,29 +1868,12 @@
    if (mustChangePassword)
    {
      Attribute a = Attributes.create(type, String.valueOf(true));
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    else
    {
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE,
      modifications.add(new Modification(ModificationType.REPLACE,
                                           Attributes.empty(type), true));
      }
    }
  }
@@ -2732,16 +2388,7 @@
      String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime);
      Attribute a = Attributes.create(type, timeValue);
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
      {
        modifications.add(new Modification(ModificationType.REPLACE, a, true));
      }
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
  }
@@ -2763,15 +2410,8 @@
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -2861,16 +2501,7 @@
    Attribute a = Attributes.create(type, GeneralizedTimeSyntax
        .createGeneralizedTimeValue(currentTime));
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    if (debugEnabled())
    {
@@ -2898,15 +2529,8 @@
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      Attribute a = Attributes.empty(type);
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    Attribute a = Attributes.empty(type);
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
    if (debugEnabled())
    {
@@ -2955,15 +2579,8 @@
        graceLoginTimes = new ArrayList<Long>();
        if (updateEntry)
        {
          userEntry.removeAttribute(type);
        }
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
        modifications.add(new Modification(ModificationType.REPLACE,
              Attributes.empty(type), true));
        }
      }
    }
@@ -3036,27 +2653,10 @@
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    if (updateEntry)
    {
      AttributeBuilder builder = new AttributeBuilder(type);
      for (Long l : graceTimes)
      {
        builder.add(AttributeValues.create(type, GeneralizedTimeSyntax
            .format(l)));
      }
    Attribute addAttr = Attributes.create(type, AttributeValues.create(
        type, GeneralizedTimeSyntax.format(highestGraceTime)));
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(builder.toAttribute());
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      Attribute addAttr = Attributes.create(type, AttributeValues.create(
          type, GeneralizedTimeSyntax.format(highestGraceTime)));
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
    modifications.add(new Modification(ModificationType.ADD, addAttr, true));
  }
@@ -3094,17 +2694,7 @@
    }
    Attribute a = builder.toAttribute();
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    modifications.add(new Modification(ModificationType.REPLACE, a, true));
  }
@@ -3135,15 +2725,8 @@
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -3240,13 +2823,7 @@
  /**
   * Indicates whether the provided password value matches any of the stored
   * passwords in the user entry.
   *
   * @param  password  The user-provided password to verify.
   *
   * @return  <CODE>true</CODE> if the provided password matches any of the
   *          stored password values, or <CODE>false</CODE> if not.
   * {@inheritDoc}
   */
  public boolean passwordMatches(ByteString password)
  {
@@ -3644,28 +3221,17 @@
      return;
    }
    if (updateEntry)
    {
      AttributeBuilder builder = new AttributeBuilder(type);
      builder.addAll(updatedValues);
      ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
      newList.add(builder.toAttribute());
      userEntry.putAttribute(type, newList);
    }
    else
    {
      AttributeBuilder builder = new AttributeBuilder(type);
      builder.addAll(removedValues);
      Attribute a = builder.toAttribute();
      modifications.add(new Modification(ModificationType.DELETE, a, true));
    AttributeBuilder builder = new AttributeBuilder(type);
    builder.addAll(removedValues);
    Attribute a = builder.toAttribute();
    modifications.add(new Modification(ModificationType.DELETE, a, true));
      if (! addedValues.isEmpty())
      {
        builder = new AttributeBuilder(type);
        builder.addAll(addedValues);
        Attribute a2 = builder.toAttribute();
        modifications.add(new Modification(ModificationType.ADD, a2, true));
      }
    if (! addedValues.isEmpty())
    {
      builder = new AttributeBuilder(type);
      builder.addAll(addedValues);
      Attribute a2 = builder.toAttribute();
      modifications.add(new Modification(ModificationType.ADD, a2, true));
    }
    if (debugEnabled())
@@ -4155,26 +3721,13 @@
    // Apply the changes, either by adding modifications or by directly updating
    // the entry.
    if (updateEntry)
    for (Attribute a : removeAttrs)
    {
      LinkedList<AttributeValue> valueList = new LinkedList<AttributeValue>();
      for (Attribute a : removeAttrs)
      {
        userEntry.removeAttribute(a, valueList);
      }
      userEntry.addAttribute(newHistAttr, valueList);
      modifications.add(new Modification(ModificationType.DELETE, a, true));
    }
    else
    {
      for (Attribute a : removeAttrs)
      {
        modifications.add(new Modification(ModificationType.DELETE, a, true));
      }
      modifications.add(new Modification(ModificationType.ADD, newHistAttr,
                                         true));
    }
    modifications.add(new Modification(ModificationType.ADD, newHistAttr,
        true));
  }
@@ -4221,15 +3774,8 @@
    AttributeType type = DirectoryServer.getAttributeType(
                             OP_ATTR_PWPOLICY_HISTORY_LC, true);
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
    modifications.add(new Modification(ModificationType.REPLACE,
                                         Attributes.empty(type), true));
    }
  }
@@ -4322,13 +3868,9 @@
  /**
   * Performs an internal modification to update the user's entry, if necessary.
   * This will do nothing if no modifications are required.
   *
   * @throws  DirectoryException  If a problem occurs while processing the
   *                              internal modification.
   * {@inheritDoc}
   */
  public void updateUserEntry()
  public void finalizeStateAfterBind()
         throws DirectoryException
  {
    // If there are no modifications, then there's nothing to do.
opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -36,6 +36,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opends.server.api.AuthenticationPolicy;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.AccountUsableResponseControl;
@@ -639,47 +641,54 @@
      }
    }
    // Determine whether to include the account usable control.  If so, then
    // Determine whether to include the account usable control. If so, then
    // create it now.
    if (isIncludeUsableControl())
    {
      try
      {
        // FIXME -- Need a way to enable PWP debugging.
        PasswordPolicyState pwpState = new PasswordPolicyState(entry, false);
        boolean isInactive           = pwpState.isDisabled() ||
                                       pwpState.isAccountExpired();
        boolean isLocked             = pwpState.lockedDueToFailures() ||
                                       pwpState.lockedDueToMaximumResetAge() ||
                                       pwpState.lockedDueToIdleInterval();
        boolean isReset              = pwpState.mustChangePassword();
        boolean isExpired            = pwpState.isPasswordExpired();
        if (isInactive || isLocked || isReset || isExpired)
        AuthenticationPolicy policy = AuthenticationPolicy
            .forUser(entry, false);
        if (policy.isPasswordPolicy())
        {
          int secondsBeforeUnlock  = pwpState.getSecondsUntilUnlock();
          int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
          PasswordPolicyState pwpState = (PasswordPolicyState) policy
              .createAuthenticationPolicyState(entry);
          if (controls == null)
          boolean isInactive = pwpState.isDisabled()
              || pwpState.isAccountExpired();
          boolean isLocked = pwpState.lockedDueToFailures()
              || pwpState.lockedDueToMaximumResetAge()
              || pwpState.lockedDueToIdleInterval();
          boolean isReset = pwpState.mustChangePassword();
          boolean isExpired = pwpState.isPasswordExpired();
          if (isInactive || isLocked || isReset || isExpired)
          {
            controls = new ArrayList<Control>(1);
          }
            int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock();
            int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
          controls.add(new AccountUsableResponseControl(isInactive, isReset,
                                isExpired, remainingGraceLogins, isLocked,
                                secondsBeforeUnlock));
        }
        else
        {
          if (controls == null)
            if (controls == null)
            {
              controls = new ArrayList<Control>(1);
            }
            controls
                .add(new AccountUsableResponseControl(isInactive, isReset,
                    isExpired, remainingGraceLogins, isLocked,
                    secondsBeforeUnlock));
          }
          else
          {
            controls = new ArrayList<Control>(1);
          }
            if (controls == null)
            {
              controls = new ArrayList<Control>(1);
            }
          int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
          controls.add(new AccountUsableResponseControl(
                                secondsBeforeExpiration));
            int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
            controls.add(new AccountUsableResponseControl(
                secondsBeforeExpiration));
          }
        }
      }
      catch (Exception e)
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,12 +336,25 @@
        dispose();
        ClientConnection clientConn = bindOp.getClientConnection();
        clientConn.setSASLAuthStateInfo(null);
        //Check if the callback message is null and use that message if not.
        if(cbMsg != null)
            bindOp.setAuthFailureReason(cbMsg);
        if (cbResultCode != null)
        {
          bindOp.setResultCode(cbResultCode);
        }
        else
            bindOp.setAuthFailureReason(msg);
        bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        {
          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        }
        if (cbMsg != null)
        {
          bindOp.setAuthFailureReason(cbMsg);
        }
        else
        {
          bindOp.setAuthFailureReason(msg);
        }
    }
@@ -398,6 +417,18 @@
     * @param cbMsg The message to set the callback message to.
     */
    private void setCallbackMsg(Message cbMsg) {
        setCallbackMsg(ResultCode.INVALID_CREDENTIALS, cbMsg);
    }
    /**
     * Sets the callback message to the specified message.
     *
     * @param cbResultCode The result code.
     * @param cbMsg The message.
     */
    private void setCallbackMsg(ResultCode cbResultCode, Message cbMsg) {
        this.cbResultCode = cbResultCode;
        this.cbMsg = cbMsg;
    }
@@ -614,8 +645,19 @@
        //Try to get a clear password to use.
        List<ByteString> clearPasswords;
        try {
          PasswordPolicyState pwPolicyState =
                                    new PasswordPolicyState(authEntry, false);
          AuthenticationPolicyState authState =
            AuthenticationPolicyState.forUser(authEntry, false);
          if (!authState.isPasswordPolicy())
          {
            Message message = ERR_SASL_ACCOUNT_NOT_LOCAL.get(
                mechanism, String.valueOf(authEntry.getDN()));
            setCallbackMsg(ResultCode.INAPPROPRIATE_AUTHENTICATION, message);
            return;
          }
          PasswordPolicyState pwPolicyState = (PasswordPolicyState) authState;
          clearPasswords = pwPolicyState.getClearPasswords();
          if ((clearPasswords == null) || clearPasswords.isEmpty()) {
              setCallbackMsg(
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());
        }
        catch (DirectoryException de)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, de);
          }
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                       ERR_ADD_INVALID_PWPOLICY_DN_SYNTAX.get(
                                            String.valueOf(entryDN),
                                           de.getMessageObject()));
        }
        passwordPolicy = (PasswordPolicy) DirectoryServer
            .getAuthenticationPolicy(policyDN);
        if (passwordPolicy == null)
        {
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       ERR_ADD_NO_SUCH_PWPOLICY.get(
                                            String.valueOf(entryDN),
                                         String.valueOf(policyDN)));
        }
      }
      // The entry doesn't have a locally managed password, so no action is
      // required.
      return;
    }
    if (passwordPolicy == null)
    {
      passwordPolicy = DirectoryServer.getDefaultPasswordPolicy();
    }
    PasswordPolicy passwordPolicy = (PasswordPolicy) policy;
    // See if a password was specified.
    AttributeType passwordAttribute = passwordPolicy.getPasswordAttribute();
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -35,9 +35,7 @@
import org.opends.messages.Message;
import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn;
import org.opends.server.api.Backend;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SASLMechanismHandler;
import org.opends.server.api.*;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.AuthorizationIdentityResponseControl;
import org.opends.server.controls.PasswordExpiredControl;
@@ -138,15 +136,8 @@
  // The idle time limit that should be enforced for the user.
  private long idleTimeLimit;
  /**
   * The password policy that applies to the user.
   */
  protected PasswordPolicy policy;
  /**
   * The password policy state for the user.
   */
  protected PasswordPolicyState pwPolicyState;
  // Authentication policy state.
  private AuthenticationPolicyState authPolicyState;
  // The password policy error type for this bind operation.
  private PasswordPolicyErrorType pwPolicyErrorType;
@@ -199,7 +190,7 @@
    idleTimeLimit            = DirectoryServer.getIdleTimeLimit();
    bindDN                   = getBindDN();
    saslMechanism            = getSASLMechanism();
    pwPolicyState            = null;
    authPolicyState          = null;
    pwPolicyErrorType        = null;
    pwPolicyControlRequested = false;
    isGraceLogin             = false;
@@ -330,9 +321,9 @@
    // required.
    try
    {
      if (pwPolicyState != null)
      if (authPolicyState != null)
      {
        pwPolicyState.updateUserEntry();
        authPolicyState.finalizeStateAfterBind();
      }
    }
    catch (DirectoryException de)
@@ -569,124 +560,148 @@
      }
      // Check to see if the user has a password.  If not, then fail.
      // Check to see if the user has a password. If not, then fail.
      // FIXME -- We need to have a way to enable/disable debugging.
      pwPolicyState = new PasswordPolicyState(userEntry, false);
      policy = pwPolicyState.getPolicy();
      AttributeType  pwType = policy.getPasswordAttribute();
      List<Attribute> pwAttr = userEntry.getAttribute(pwType);
      if ((pwAttr == null) || (pwAttr.isEmpty()))
      authPolicyState = AuthenticationPolicyState.forUser(userEntry, false);
      if (authPolicyState.isPasswordPolicy())
      {
        throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                                     ERR_BIND_OPERATION_NO_PASSWORD.get(
                                          String.valueOf(bindDN)));
      }
        // Account is managed locally.
        PasswordPolicyState pwPolicyState =
          (PasswordPolicyState) authPolicyState;
        PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
        AttributeType pwType = policy.getPasswordAttribute();
      // Perform a number of password policy state checks for the user.
      checkPasswordPolicyState(userEntry, null);
      // Invoke the pre-operation bind plugins.
      executePostOpPlugins = true;
      PluginResult.PreOperation preOpResult =
          pluginConfigManager.invokePreOperationBindPlugins(this);
      if (!preOpResult.continueProcessing())
      {
        setResultCode(preOpResult.getResultCode());
        appendErrorMessage(preOpResult.getErrorMessage());
        setMatchedDN(preOpResult.getMatchedDN());
        setReferralURLs(preOpResult.getReferralURLs());
        return false;
      }
      // Determine whether the provided password matches any of the stored
      // passwords for the user.
      if (pwPolicyState.passwordMatches(simplePassword))
      {
        setResultCode(ResultCode.SUCCESS);
        if (DirectoryServer.lockdownMode() &&
            (! ClientConnection.hasPrivilege(userEntry,
                Privilege.BYPASS_LOCKDOWN)))
        List<Attribute> pwAttr = userEntry.getAttribute(pwType);
        if ((pwAttr == null) || (pwAttr.isEmpty()))
        {
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                                 ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
              ERR_BIND_OPERATION_NO_PASSWORD.get(String.valueOf(bindDN)));
        }
        setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
            simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
        // Perform a number of password policy state checks for the user.
        checkPasswordPolicyState(userEntry, null);
        // Set resource limits for the authenticated user.
        setResourceLimits(userEntry);
        // Perform any remaining processing for a successful simple
        // authentication.
        pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
        pwPolicyState.clearFailureLockout();
        if (isFirstWarning)
        // Invoke pre-operation plugins.
        if (!invokePreOpPlugins())
        {
          pwPolicyState.setWarnedTime();
          int numSeconds = pwPolicyState.getSecondsUntilExpiration();
          Message m = WARN_BIND_PASSWORD_EXPIRING.get(
                           secondsToTimeString(numSeconds));
          pwPolicyState.generateAccountStatusNotification(
               AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
               AccountStatusNotification.createProperties(pwPolicyState,
                     false, numSeconds, null, null));
          return false;
        }
        if (isGraceLogin)
        // Determine whether the provided password matches any of the stored
        // passwords for the user.
        if (pwPolicyState.passwordMatches(simplePassword))
        {
          pwPolicyState.updateGraceLoginTimes();
        }
          setResultCode(ResultCode.SUCCESS);
        pwPolicyState.setLastLoginTime();
          if (DirectoryServer.lockdownMode()
              && (!ClientConnection.hasPrivilege(userEntry,
                  Privilege.BYPASS_LOCKDOWN)))
          {
            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
          }
          setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
              simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
          // Set resource limits for the authenticated user.
          setResourceLimits(userEntry);
          // Perform any remaining processing for a successful simple
          // authentication.
          pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
          pwPolicyState.clearFailureLockout();
          if (isFirstWarning)
          {
            pwPolicyState.setWarnedTime();
            int numSeconds = pwPolicyState.getSecondsUntilExpiration();
            Message m = WARN_BIND_PASSWORD_EXPIRING
                .get(secondsToTimeString(numSeconds));
            pwPolicyState.generateAccountStatusNotification(
                AccountStatusNotificationType.PASSWORD_EXPIRING, userEntry, m,
                AccountStatusNotification.createProperties(pwPolicyState,
                    false, numSeconds, null, null));
          }
          if (isGraceLogin)
          {
            pwPolicyState.updateGraceLoginTimes();
          }
          pwPolicyState.setLastLoginTime();
        }
        else
        {
          setResultCode(ResultCode.INVALID_CREDENTIALS);
          setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
          if (policy.getLockoutFailureCount() > 0)
          {
            pwPolicyState.updateAuthFailureTimes();
            if (pwPolicyState.lockedDueToFailures())
            {
              AccountStatusNotificationType notificationType;
              Message m;
              boolean tempLocked;
              int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
              if (lockoutDuration > -1)
              {
                notificationType =
                  AccountStatusNotificationType.ACCOUNT_TEMPORARILY_LOCKED;
                tempLocked = true;
                m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED
                    .get(secondsToTimeString(lockoutDuration));
              }
              else
              {
                notificationType =
                  AccountStatusNotificationType.ACCOUNT_PERMANENTLY_LOCKED;
                tempLocked = false;
                m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
              }
              pwPolicyState.generateAccountStatusNotification(notificationType,
                  userEntry, m, AccountStatusNotification.createProperties(
                      pwPolicyState, tempLocked, -1, null, null));
            }
          }
        }
      }
      else
      {
        setResultCode(ResultCode.INVALID_CREDENTIALS);
        setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
        if (policy.getLockoutFailureCount() > 0)
        // Invoke pre-operation plugins.
        if (!invokePreOpPlugins())
        {
          pwPolicyState.updateAuthFailureTimes();
          if (pwPolicyState.lockedDueToFailures())
          return false;
        }
        if (authPolicyState.passwordMatches(simplePassword))
        {
          setResultCode(ResultCode.SUCCESS);
          if (DirectoryServer.lockdownMode()
              && (!ClientConnection.hasPrivilege(userEntry,
                  Privilege.BYPASS_LOCKDOWN)))
          {
            AccountStatusNotificationType notificationType;
            Message m;
            boolean tempLocked;
            int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
            if (lockoutDuration > -1)
            {
              notificationType = AccountStatusNotificationType.
                                      ACCOUNT_TEMPORARILY_LOCKED;
              tempLocked = true;
              m = ERR_BIND_ACCOUNT_TEMPORARILY_LOCKED.get(
                       secondsToTimeString(lockoutDuration));
            }
            else
            {
              notificationType = AccountStatusNotificationType.
                                      ACCOUNT_PERMANENTLY_LOCKED;
              tempLocked = false;
              m = ERR_BIND_ACCOUNT_PERMANENTLY_LOCKED.get();
            }
            pwPolicyState.generateAccountStatusNotification(
                 notificationType, userEntry, m,
                 AccountStatusNotification.createProperties(pwPolicyState,
                       tempLocked, -1, null, null));
            throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
                ERR_BIND_REJECTED_LOCKDOWN_MODE.get());
          }
          setAuthenticationInfo(new AuthenticationInfo(userEntry, getBindDN(),
              simplePassword, DirectoryServer.isRootDN(userEntry.getDN())));
          // Set resource limits for the authenticated user.
          setResourceLimits(userEntry);
        }
        else
        {
          setResultCode(ResultCode.INVALID_CREDENTIALS);
          setAuthFailureReason(ERR_BIND_OPERATION_WRONG_PASSWORD.get());
        }
      }
@@ -728,16 +743,9 @@
    }
    // Invoke the pre-operation bind plugins.
    executePostOpPlugins = true;
    PluginResult.PreOperation preOpResult =
        pluginConfigManager.invokePreOperationBindPlugins(this);
    if (!preOpResult.continueProcessing())
    // Invoke pre-operation plugins.
    if (!invokePreOpPlugins())
    {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return false;
    }
@@ -776,15 +784,9 @@
    // NYI
    // Invoke the pre-operation bind plugins.
    PluginResult.PreOperation preOpResult =
        pluginConfigManager.invokePreOperationBindPlugins(this);
    if (!preOpResult.continueProcessing())
    // Invoke pre-operation plugins.
    if (!invokePreOpPlugins())
    {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return false;
    }
@@ -813,21 +815,20 @@
    }
    // Create the password policy state object.
    if (saslAuthUserEntry == null)
    if (saslAuthUserEntry != null)
    {
      pwPolicyState = null;
    }
    else
    {
      // FIXME -- Need to have a way to enable debugging.
      pwPolicyState = new PasswordPolicyState(saslAuthUserEntry, false);
      policy = pwPolicyState.getPolicy();
      setUserEntryDN(saslAuthUserEntry.getDN());
      // Perform password policy checks that will need to be completed
      // regardless of whether the authentication was successful.
      checkPasswordPolicyState(saslAuthUserEntry, saslHandler);
      // FIXME -- Need to have a way to enable debugging.
      authPolicyState = AuthenticationPolicyState.forUser(
          saslAuthUserEntry, false);
      if (authPolicyState.isPasswordPolicy())
      {
        // Account is managed locally: perform password policy checks that will
        // need to be completed regardless of whether the authentication was
        // successful.
        checkPasswordPolicyState(saslAuthUserEntry, saslHandler);
      }
    }
@@ -836,8 +837,11 @@
    ResultCode resultCode = getResultCode();
    if (resultCode == ResultCode.SUCCESS)
    {
      if (pwPolicyState != null)
      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
      {
        PasswordPolicyState pwPolicyState =
          (PasswordPolicyState) authPolicyState;
        if (saslHandler.isPasswordBased(saslMechanism) &&
            pwPolicyState.mustChangePassword())
        {
@@ -865,11 +869,10 @@
        }
        pwPolicyState.setLastLoginTime();
        // Set appropriate resource limits for the user.
        setResourceLimits(saslAuthUserEntry);
      }
      // Set appropriate resource limits for the user.
      setResourceLimits(saslAuthUserEntry);
    }
    else if (resultCode == ResultCode.SASL_BIND_IN_PROGRESS)
    {
@@ -878,12 +881,15 @@
    }
    else
    {
      if (pwPolicyState != null)
      if (authPolicyState != null && authPolicyState.isPasswordPolicy())
      {
        PasswordPolicyState pwPolicyState =
          (PasswordPolicyState) authPolicyState;
        if (saslHandler.isPasswordBased(saslMechanism))
        {
          if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
          if (pwPolicyState.getAuthenticationPolicy()
              .getLockoutFailureCount() > 0)
          {
            pwPolicyState.updateAuthFailureTimes();
            if (pwPolicyState.lockedDueToFailures())
@@ -924,20 +930,45 @@
  private boolean invokePreOpPlugins()
  {
    executePostOpPlugins = true;
    PluginResult.PreOperation preOpResult = pluginConfigManager
        .invokePreOperationBindPlugins(this);
    if (!preOpResult.continueProcessing())
    {
      setResultCode(preOpResult.getResultCode());
      appendErrorMessage(preOpResult.getErrorMessage());
      setMatchedDN(preOpResult.getMatchedDN());
      setReferralURLs(preOpResult.getReferralURLs());
      return false;
    }
    else
    {
      return true;
    }
  }
  /**
   * Validates a number of password policy state constraints for the user.
   *
   * @param  userEntry    The entry for the user that is authenticating.
   * @param  saslHandler  The SASL mechanism handler if this is a SASL bind, or
   *                      {@code null} for a simple bind.
   *
   * @throws  DirectoryException  If a problem occurs that should cause the bind
   *                              to fail.
   * @param userEntry
   *          The entry for the user that is authenticating.
   * @param saslHandler
   *          The SASL mechanism handler if this is a SASL bind, or {@code null}
   *          for a simple bind.
   * @throws DirectoryException
   *           If a problem occurs that should cause the bind to fail.
   */
  protected void checkPasswordPolicyState(Entry userEntry,
                                          SASLMechanismHandler<?> saslHandler)
          throws DirectoryException
  protected void checkPasswordPolicyState(
      Entry userEntry, SASLMechanismHandler<?> saslHandler)
      throws DirectoryException
  {
    PasswordPolicyState pwPolicyState = (PasswordPolicyState) authPolicyState;
    PasswordPolicy policy = pwPolicyState.getAuthenticationPolicy();
    boolean isSASLBind = (saslHandler != null);
    // If the password policy is configured to track authentication failures or
opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -44,12 +44,7 @@
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import org.opends.server.api.AttributeSyntax;
import org.opends.server.api.Backend;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.PasswordStorageScheme;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.api.*;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.LDAPAssertionRequestControl;
import org.opends.server.controls.LDAPPostReadRequestControl;
@@ -67,14 +62,12 @@
import org.opends.server.core.PluginConfigManager;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.BooleanSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.*;
import org.opends.server.types.operation.PostOperationModifyOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PostSynchronizationModifyOperation;
import org.opends.server.types.operation.PreOperationModifyOperation;
import org.opends.server.util.TimeThread;
import org.opends.server.util.Validator;
@@ -141,7 +134,7 @@
  /**
   * Indicates whether the user's account was locked before this change.
   */
  protected boolean wasLocked;
  protected boolean wasLocked = false;
  /**
   * The client connection associated with this operation.
@@ -451,8 +444,13 @@
          selfChange = entryDN.equals(getAuthorizationDN());
          // FIXME -- Need a way to enable debug mode.
          pwPolicyState = new PasswordPolicyState(currentEntry, false,
                                                  TimeThread.getTime(), true);
          AuthenticationPolicy policy = AuthenticationPolicy.forUser(
              currentEntry, true);
          if (policy.isPasswordPolicy())
          {
            pwPolicyState = (PasswordPolicyState) policy
                .createAuthenticationPolicyState(currentEntry);
          }
        }
        catch (DirectoryException de)
        {
@@ -525,12 +523,7 @@
        try
        {
          handleInitialPasswordPolicyProcessing();
          wasLocked = false;
          if (passwordChanged)
          {
            performAdditionalPasswordChangedProcessing();
          }
          performAdditionalPasswordChangedProcessing();
        }
        catch (DirectoryException de)
        {
@@ -632,20 +625,16 @@
          }
          else
          {
              if(!processPreOperation()) {
                  break modifyProcessing;
              }
            if (!processPreOperation())
            {
              break modifyProcessing;
            }
            backend.replaceEntry(currentEntry, modifiedEntry, this);
            // See if we need to generate any account status notifications as a
            // result of the changes.
            if (passwordChanged || enabledStateChanged || wasLocked)
            {
              handleAccountStatusNotifications();
            }
            handleAccountStatusNotifications();
          }
@@ -997,36 +986,12 @@
      }
      // If the modification is not updating the password attribute,
      // then check if the isEnabled flag should be set and then perform any
      // schema processing.
      boolean isPassword =
              t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
      // then perform any schema processing.
      boolean isPassword = (pwPolicyState != null)
          && t.equals(pwPolicyState.getAuthenticationPolicy()
              .getPasswordAttribute());
      if (!isPassword )
      {
        // See if it's an attribute used to maintain the account
        // enabled/disabled state.
        AttributeType disabledAttr =
               DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
        if (t.equals(disabledAttr))
        {
          enabledStateChanged = true;
          for (AttributeValue v : a)
          {
            try
            {
              isEnabled =
                  (! BooleanSyntax.DECODER.decode(v));
            }
            catch (DirectoryException de)
            {
              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                      ERR_MODIFY_INVALID_DISABLED_VALUE.get(
                              OP_ATTR_ACCOUNT_DISABLED,
                              String.valueOf(de.getMessageObject())), de);
            }
          }
        }
        switch (m.getModificationType())
        {
          case ADD:
@@ -1062,8 +1027,15 @@
    currentPasswordProvided = false;
    isEnabled = true;
    enabledStateChanged = false;
    if (pwPolicyState == null)
    {
      // Account not managed locally so nothing to do.
      return;
    }
    if (currentEntry.hasAttribute(
            pwPolicyState.getPolicy().getPasswordAttribute()))
            pwPolicyState.getAuthenticationPolicy().getPasswordAttribute()))
    {
      // It may actually have more than one, but we can't tell the difference if
      // the values are encoded, and its enough for our purposes just to know
@@ -1085,8 +1057,8 @@
      for (Modification m : modifications)
      {
        AttributeType t = m.getAttribute().getAttributeType();
        boolean isPassword =
                t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
        boolean isPassword = t.equals(pwPolicyState.getAuthenticationPolicy()
            .getPasswordAttribute());
        if (isPassword)
        {
          passwordChanged = true;
@@ -1116,8 +1088,8 @@
      // If the modification is updating the password attribute, then perform
      // any necessary password policy processing.  This processing should be
      // skipped for synchronization operations.
      boolean isPassword =
              t.equals(pwPolicyState.getPolicy().getPasswordAttribute());
      boolean isPassword = t.equals(pwPolicyState.getAuthenticationPolicy()
          .getPasswordAttribute());
      if (isPassword)
      {
        if (!isSynchronizationOperation())
@@ -1135,8 +1107,9 @@
            // If it's a self change, then see if that's allowed.
            if (selfChange &&
                (! pwPolicyState.getPolicy().isAllowUserPasswordChanges()))
            if (selfChange
                && (!pwPolicyState.getAuthenticationPolicy()
                    .isAllowUserPasswordChanges()))
            {
              pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1146,8 +1119,9 @@
            // If we require secure password changes, then makes sure it's a
            // secure communication channel.
            if (pwPolicyState.getPolicy().isRequireSecurePasswordChanges() &&
                (! clientConnection.isSecure()))
            if (pwPolicyState.getAuthenticationPolicy()
                .isRequireSecurePasswordChanges()
                && (!clientConnection.isSecure()))
            {
              pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1242,8 +1216,8 @@
    // If there were multiple password values, then make sure that's
    // OK.
    if ((!isInternalOperation())
        && (!pwPolicyState.getPolicy().isAllowMultiplePasswordValues())
        && (passwordsToAdd > 1))
        && (!pwPolicyState.getAuthenticationPolicy()
            .isAllowMultiplePasswordValues()) && (passwordsToAdd > 1))
    {
      pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1260,7 +1234,8 @@
      if (pwPolicyState.passwordIsPreEncoded(v.getValue()))
      {
        if ((!isInternalOperation())
            && !pwPolicyState.getPolicy().isAllowPreEncodedPasswords())
            && !pwPolicyState.getAuthenticationPolicy()
                .isAllowPreEncodedPasswords())
        {
          pwpErrorType = PasswordPolicyErrorType.INSUFFICIENT_PASSWORD_QUALITY;
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
@@ -1382,7 +1357,7 @@
        {
          for (AttributeValue av : attr)
          {
            if (pwPolicyState.getPolicy().isAuthPasswordSyntax())
            if (pwPolicyState.getAuthenticationPolicy().isAuthPasswordSyntax())
            {
              if (AuthPasswordSyntax.isEncoded(av.getValue()))
              {
@@ -1867,33 +1842,48 @@
  public void performAdditionalPasswordChangedProcessing()
         throws DirectoryException
  {
    if (pwPolicyState == null)
    {
      // Account not managed locally so nothing to do.
      return;
    }
    if (!passwordChanged)
    {
      // Nothing to do.
      return;
    }
    // If it was a self change, then see if the current password was provided
    // and handle accordingly.
    if (selfChange &&
        pwPolicyState.getPolicy().isPasswordChangeRequiresCurrentPassword() &&
        (! currentPasswordProvided))
    if (selfChange
        && pwPolicyState.getAuthenticationPolicy()
            .isPasswordChangeRequiresCurrentPassword()
        && (!currentPasswordProvided))
    {
      pwpErrorType = PasswordPolicyErrorType.MUST_SUPPLY_OLD_PASSWORD;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                     ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
          ERR_MODIFY_PW_CHANGE_REQUIRES_CURRENT_PW.get());
    }
    // If this change would result in multiple password values, then see if
    // that's OK.
    if ((numPasswords > 1) &&
        (! pwPolicyState.getPolicy().isAllowMultiplePasswordValues()))
    if ((numPasswords > 1)
        && (!pwPolicyState.getAuthenticationPolicy()
            .isAllowMultiplePasswordValues()))
    {
      pwpErrorType = PasswordPolicyErrorType.PASSWORD_MOD_NOT_ALLOWED;
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                     ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
          ERR_MODIFY_MULTIPLE_PASSWORDS_NOT_ALLOWED.get());
    }
    // If any of the password values should be validated, then do so now.
    if (selfChange ||
        (! pwPolicyState.getPolicy().isSkipValidationForAdministrators()))
    if (selfChange
        || (!pwPolicyState.getAuthenticationPolicy()
            .isSkipValidationForAdministrators()))
    {
      if (newPasswords != null)
      {
@@ -1965,7 +1955,7 @@
        {
          if (pwPolicyState.isPasswordInHistory(v.getValue()))
          {
            if (selfChange || (! pwPolicyState.getPolicy().
            if (selfChange || (! pwPolicyState.getAuthenticationPolicy().
                                      isSkipValidationForAdministrators()))
            {
              pwpErrorType = PasswordPolicyErrorType.PASSWORD_IN_HISTORY;
@@ -1992,8 +1982,8 @@
    pwPolicyState.clearGraceLoginTimes();
    pwPolicyState.clearWarnedTime();
    if (pwPolicyState.getPolicy().isForceChangeOnAdd() ||
        pwPolicyState.getPolicy().isForceChangeOnReset())
    if (pwPolicyState.getAuthenticationPolicy().isForceChangeOnAdd() ||
        pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset())
    {
      if (selfChange)
      {
@@ -2002,17 +1992,17 @@
      else
      {
        if ((pwpErrorType == null) &&
            pwPolicyState.getPolicy().isForceChangeOnReset())
            pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset())
        {
          pwpErrorType = PasswordPolicyErrorType.CHANGE_AFTER_RESET;
        }
        pwPolicyState.setMustChangePassword(
             pwPolicyState.getPolicy().isForceChangeOnReset());
             pwPolicyState.getAuthenticationPolicy().isForceChangeOnReset());
      }
    }
    if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
    if (pwPolicyState.getAuthenticationPolicy().getRequireChangeByTime() > 0)
    {
      pwPolicyState.setRequiredChangeTime();
    }
@@ -2079,6 +2069,18 @@
   */
  protected void handleAccountStatusNotifications()
  {
    if (pwPolicyState == null)
    {
      // Account not managed locally, so nothing to do.
      return;
    }
    if (!(passwordChanged || enabledStateChanged || wasLocked))
    {
      // Account managed locally, but unchanged, so nothing to do.
      return;
    }
    if (passwordChanged)
    {
      if (selfChange)
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 =