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

david_page
12.38.2007 5d0af207adc99bba2b4ed7d34670a2e3e4ae0d0f
The following update to core.PasswordPolicyState.java, along with some small changes to consumers of its API are the result of a review in preparation for password policy state management extended operation (issue 579).

1. Issue 1301 Force Pwd Change Option Allows User to Change Pwds But Not Bind With New Pwd

I had made a change to PasswordPolicyState.setMustChangePassword in a previous commit that did not account for the possibility that the PasswordPolicyState field could be ConditionResult.UNDEFINED. Now the routine calls mustChangePassword, which ensures the state field is set.

2. Issue 1295 PwP: PasswordPolicyState.setRequiredChangeTime uses incorrect time value

Per https://opends.dev.java.net/public/docs/architecture/OpenDS-PWPolicy-Architecture.pdf , the field is to be set to the value of ds-cfg-require-change-by-time. It was being set to the PasswordPolicyState field passwordChangedTime value.

3. Issue 1344 PwP: PasswordPolicyState field secondsUntilUnlock set inside debug condition. Moved outside.

4. Issue 1346 PwP: When lockout-due-to-failures expiration detected, pwdFailureTime timestamps must be cleared. Now cleared along with pwdAccountLockedTime.

5. Initialize PasswordPolicyState fields at declaration, when a default initial value is available. Otherwise, the field is qualified with final, to help ensure proper initialization. (Similar to recent change to PasswordPolicy.)

6. Fixed several errors introduced when the new debug{Error,Warning,Info} methods were introduced and the string concatenation message style was replaced with the printf format string message style.

7. Converted getPasswordPolicyInternal to a static method: while the password policy state computation depends on the policy object, it is not a part of the state, per se. The method could be made public and used for AddOperation.

8. Changed PasswordPolicyState.getBoolean to return ConditionResult to be consistent with other entry field getters (i.e., indicate when the attribute is absent from the entry).

9. Used canonical attribute access style for getPasswordValues (for loop) to accommodate absent attribute and empty attribute values (based on attribute getters used throughout the existing code).

10. For the policy state getters, reorganized to follow a canonical structure:
a. Return if cached value is found (note that lazy initialization is used for state fields).
b. Try to fetch attribute value from entry.
i. Handle exception case.
ii. Handle absence case.
c. Perform any computation on entry value needed to arrive at state value; store state value.
d. Return result.

11. Revised PasswordPolicyState.lockedDueToFailures feature. The feature is complicated by the use (due to the IETF draft) of two state attributes. This change attempted to simplify managing the feature by wrapping the attribute management into a simpler API.
a. Narrowed public API to
i. updateAuthFailureTimes: adds pwdFailureTime timestamp and [new] checks for lockout;
ii. lockedDueToFailure: checks for pwdAccountLockedTime, another lockout corner case, and handles lockout expiration including [1295] clearing pwdFailureTime timestamps;
iii. clearFailureLockout: clears pwdAccountLockedTime and [new] pwdFailureTime.
b. Factored pwdAccountLockedTime getter/setter/clear from public API. These are called from the public API as appropriate.
c. Also, it now accommodates the pwdAccountLockedTime:19700101000000Z as a valid locked time (but subject to lockout expiration and not as "locked until reset" as described in IETF draft-behera).

12. Note that PasswordPolicyState.mustChangePassword is unusual in that it does not evaluate the entry unless the policy indicates the feature is enabled (i.e., checks for allow-user-change and force-change-on-{add,reset}). I did not change this behavior, but marked it with FIXME.

13. PasswordPolicyState.clearWarnedTime: Added getWarnedTime() and warnedTime = -1 to make it consistent with other clear routines.

14. PasswordPolicyState.getClearPasswords, passwordMatches, and handleDeprecatedStorageSchemes duplicated code for authPassword and userPassword syntax. Combining the two cases was complicated by the fact that these two routines have different return types:

StringBuilder[] AuthPasswordSyntax.decodeAuthPassword
String[] UserPasswordSyntax.decodeUserPassword

I did not change either return type, but believe the return types should be consistent.


TESTS

There are no unit tests. The functional tests (security) provide some coverage (e.g., like finding 1301), and these 157 tests now pass.

4 files modified
2328 ■■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java 30 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java 3 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java 2294 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java 1 ●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/core/BindOperation.java
@@ -1542,7 +1542,7 @@
              pwPolicyState.handleDeprecatedStorageSchemes(simplePassword);
              pwPolicyState.clearAuthFailureTimes();
              pwPolicyState.clearFailureLockout();
              if (isFirstWarning)
              {
@@ -1574,21 +1574,15 @@
              setResultCode(ResultCode.INVALID_CREDENTIALS);
              setAuthFailureReason(msgID, message);
              int maxAllowedFailures
                   = pwPolicyState.getPolicy().getLockoutFailureCount();
              if (maxAllowedFailures > 0)
              if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
              {
                pwPolicyState.updateAuthFailureTimes();
                if (pwPolicyState.getAuthFailureTimes().size() >=
                    maxAllowedFailures)
                if (pwPolicyState.lockedDueToFailures())
                {
                  pwPolicyState.lockDueToFailures();
                  AccountStatusNotificationType notificationType;
                  int lockoutDuration
                       = pwPolicyState.getPolicy().getLockoutDuration();
                  if (lockoutDuration > 0)
                  int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                  if (lockoutDuration > -1)
                  {
                    notificationType = AccountStatusNotificationType.
                                            ACCOUNT_TEMPORARILY_LOCKED;
@@ -2073,23 +2067,17 @@
              if (saslHandler.isPasswordBased(saslMechanism))
              {
                int maxAllowedFailures
                     = pwPolicyState.getPolicy().getLockoutFailureCount();
                if (maxAllowedFailures > 0)
                if (pwPolicyState.getPolicy().getLockoutFailureCount() > 0)
                {
                  pwPolicyState.updateAuthFailureTimes();
                  if (pwPolicyState.getAuthFailureTimes().size() >=
                      maxAllowedFailures)
                  if (pwPolicyState.lockedDueToFailures())
                  {
                    pwPolicyState.lockDueToFailures();
                    AccountStatusNotificationType notificationType;
                    int msgID;
                    String message;
                    int lockoutDuration
                         = pwPolicyState.getPolicy().getLockoutDuration();
                    if (lockoutDuration > 0)
                    int lockoutDuration = pwPolicyState.getSecondsUntilUnlock();
                    if (lockoutDuration > -1)
                    {
                      notificationType = AccountStatusNotificationType.
                                              ACCOUNT_TEMPORARILY_LOCKED;
opendj-sdk/opends/src/server/org/opends/server/core/ModifyOperation.java
@@ -1286,7 +1286,6 @@
            // Update the password policy state attributes in the user's entry.
            // If the modification fails, then these changes won't be applied.
            pwPolicyState.setPasswordChangedTime();
            pwPolicyState.clearAuthFailureTimes();
            pwPolicyState.clearFailureLockout();
            pwPolicyState.clearGraceLoginTimes();
            pwPolicyState.clearWarnedTime();
@@ -1297,7 +1296,7 @@
              pwPolicyState.setMustChangePassword(! selfChange);
            }
            if (pwPolicyState.getRequiredChangeTime() > 0)
            if (pwPolicyState.getPolicy().getRequireChangeByTime() > 0)
            {
              pwPolicyState.setRequiredChangeTime();
            }
opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -85,106 +85,83 @@
{
  // Indicates whether to debug password policy processing performed wth this
  // state object.
  private boolean debug;
  // 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 boolean updateEntry;
  private final boolean updateEntry;
  // Indicates whether an expiration warning message should be sent if the
  // authentication is successful.
  private boolean sendExpirationWarning;
  // Indicates whether to debug password policy processing performed wth this
  // state object.
  private final boolean debug;
  // Indicates whether a grace login will be used if the authentication is
  // successful.
  private boolean useGraceLogin;
  // The string representation of the user's DN.
  private final String userDNString;
  // The password policy with which the account is associated.
  private final PasswordPolicy passwordPolicy;
  // The current time for use in all password policy calculations.
  private final long currentTime;
  // The time that the user's password was last changed.
  private long passwordChangedTime = Long.MIN_VALUE;
  // Indicates whether the user's account is expired.
  private ConditionResult isAccountExpired;
  private ConditionResult isAccountExpired = ConditionResult.UNDEFINED;
  // Indicates whether the user's account is disabled.
  private ConditionResult isDisabled;
  private ConditionResult isDisabled = ConditionResult.UNDEFINED;
  // Indicates whether the user's password is expired.
  private ConditionResult isPasswordExpired;
  private ConditionResult isPasswordExpired = ConditionResult.UNDEFINED;
  // Indicates whether the warning to send to the client would be the first
  // warning for the user.
  private ConditionResult isFirstWarning;
  private ConditionResult isFirstWarning = ConditionResult.UNDEFINED;
  // Indicates whether the user's account is locked by the idle lockout.
  private ConditionResult isIdleLocked;
  // Indicates whether the user's account is locked by administrative reset.
  private ConditionResult isResetLocked;
  private ConditionResult isIdleLocked = ConditionResult.UNDEFINED;
  // Indicates whether the user may use a grace login if the password is expired
  // and there are one or more grace logins remaining.
  private ConditionResult mayUseGraceLogin;
  private ConditionResult mayUseGraceLogin = ConditionResult.UNDEFINED;
  // Indicates whether the user's password must be changed.
  private ConditionResult mustChangePassword;
  private ConditionResult mustChangePassword = ConditionResult.UNDEFINED;
  // Indicates whether the user should be warned of an upcoming expiration.
  private ConditionResult shouldWarn;
  // The user entry with which this state information is associated.
  private Entry userEntry;
  private ConditionResult shouldWarn = ConditionResult.UNDEFINED;
  // The number of seconds until the user's account is automatically unlocked.
  private int secondsUntilUnlock;
  // The number of seconds until the user's password expires.
  private int secondsUntilExpiration;
  // The set of modifications that should be applied to the user's entry.
  private LinkedList<Modification> modifications;
  private int secondsUntilUnlock = Integer.MIN_VALUE;
  // The set of authentication failure times for this user.
  private List<Long> authFailureTimes;
  private List<Long> authFailureTimes = null;
  // The set of grace login times for this user.
  private List<Long> graceLoginTimes;
  // The time that the user's account was created.
  private long createTime;
  // The current time for use in all password policy calculations.
  private long currentTime;
  private List<Long> graceLoginTimes = null;
  // The time that the user's password should expire (or did expire).
  private long expirationTime;
  private long expirationTime = Long.MIN_VALUE;
  // The time that the user's entry was locked due to too many authentication
  // failures.
  private long failureLockedTime;
  // The time that the user's entry was locked due to the idle lockout.
  private long idleLockedTime;
  private long failureLockedTime = Long.MIN_VALUE;
  // The time that the user last authenticated to the Directory Server.
  private long lastLoginTime;
  // The time that the user's password was last changed.
  private long passwordChangedTime;
  private long lastLoginTime = Long.MIN_VALUE;
  // The last required change time with which the user complied.
  private long requiredChangeTime;
  private long requiredChangeTime = Long.MIN_VALUE;
  // The time that the user was first warned about an upcoming expiration.
  private long warnedTime;
  private long warnedTime = Long.MIN_VALUE;
  // The password policy with which the account is associated.
  private PasswordPolicy passwordPolicy;
  // The string representation of the current time.
  private String currentGeneralizedTime;
  // The string representation of the user's DN.
  private String userDNString;
  // The set of modifications that should be applied to the user's entry.
  private LinkedList<Modification> modifications
       = new LinkedList<Modification>();
@@ -211,45 +188,12 @@
    this.debug       = debug;
    userDNString           = userEntry.getDN().toString();
    passwordPolicy         = getPasswordPolicyInternal();
    currentGeneralizedTime = TimeThread.getGeneralizedTime();
    passwordPolicy   = getPasswordPolicyInternal(this.userEntry, this.debug);
    currentTime            = TimeThread.getTime();
    modifications          = new LinkedList<Modification>();
    isDisabled             = ConditionResult.UNDEFINED;
    isAccountExpired       = ConditionResult.UNDEFINED;
    isPasswordExpired      = ConditionResult.UNDEFINED;
    isFirstWarning         = ConditionResult.UNDEFINED;
    isIdleLocked           = ConditionResult.UNDEFINED;
    isResetLocked          = ConditionResult.UNDEFINED;
    mayUseGraceLogin       = ConditionResult.UNDEFINED;
    mustChangePassword     = ConditionResult.UNDEFINED;
    shouldWarn             = ConditionResult.UNDEFINED;
    expirationTime         = Long.MIN_VALUE;
    failureLockedTime      = Long.MIN_VALUE;
    idleLockedTime         = Long.MIN_VALUE;
    lastLoginTime          = Long.MIN_VALUE;
    requiredChangeTime     = Long.MIN_VALUE;
    warnedTime             = Long.MIN_VALUE;
    authFailureTimes       = null;
    sendExpirationWarning  = false;
    useGraceLogin          = false;
    secondsUntilExpiration = Integer.MIN_VALUE;
    secondsUntilUnlock     = Integer.MIN_VALUE;
    // Get the time that the user's account was created.
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(OP_ATTR_CREATE_TIMESTAMP);
    }
    createTime = getGeneralizedTime(type);
    // Get the password changed time for the user.
    type = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
    AttributeType type
         = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
@@ -259,7 +203,15 @@
    passwordChangedTime = getGeneralizedTime(type);
    if (passwordChangedTime <= 0)
    {
      passwordChangedTime = createTime;
      // Get the time that the user's account was created.
      AttributeType createTimeType
           = DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC);
      if (createTimeType == null)
      {
        createTimeType
            = DirectoryServer.getDefaultAttributeType(OP_ATTR_CREATE_TIMESTAMP);
      }
      passwordChangedTime = getGeneralizedTime(createTimeType);
      if (passwordChangedTime <= 0)
      {
@@ -267,9 +219,8 @@
        if (debug)
        {
          debugWarning(
              "Could not determine password changed time " +
                           "for user %s", userDNString);
          debugWarning("Could not determine password changed time for user %s.",
                       userDNString);
        }
      }
    }
@@ -278,42 +229,36 @@
  /**
   * Retrieves the password policy for the user.
   * 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 the default password policy is
   * returned.
   *
   * @param  userEntry    The user entry.
   * @param  debug        Indicates whether to enable debugging for the
   *                      operations performed.
   *
   * @return  The password policy for the user.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              determine the password policy for the user.
   */
  private PasswordPolicy getPasswordPolicyInternal()
  private static PasswordPolicy getPasswordPolicyInternal(Entry userEntry,
                                                          boolean debug)
          throws DirectoryException
  {
    // See if the user entry contains the ds-pwp-password-policy-dn attribute to
    // select a custom objectclass (whether real or virtual).
    String userDNString = userEntry.getDN().toString();
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_POLICY_DN, true);
    List<Attribute> attrList = userEntry.getAttribute(type);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList != null)
    {
      // There is no policy subentry defined, so we'll use the default.
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Using the default password policy for user %s",
                    userDNString);
        }
      }
      return DirectoryServer.getDefaultPasswordPolicy();
    }
    for (Attribute a : attrList)
    {
      for (AttributeValue v : a.getValues())
      {
        if(a.getValues().isEmpty()) continue;
        AttributeValue v = a.getValues().iterator().next();
        DN subentryDN;
        try
        {
@@ -328,9 +273,8 @@
          if (debug)
          {
            debugError(
                "Could not parse password policy subentry " +
                    "DN %s for user %s: %s",
            debugError("Could not parse password policy subentry DN %s " +
                 "for user %s: %s",
                v.getStringValue(), userDNString,
                stackTraceToSingleLineString(e));
          }
@@ -347,8 +291,7 @@
        {
          if (debug)
          {
            debugError(
                "Password policy subentry %s for user %s " +
            debugError("Password policy subentry %s for user %s " +
                           "is not defined in the Directory Server.",
                       String.valueOf(subentryDN), userDNString);
          }
@@ -360,30 +303,27 @@
                         DirectoryServer.getServerErrorResultCode(), message,
                         msgID);
        }
        else
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Using password policy subentry %s for user " +
                  "%s.", String.valueOf(subentryDN), userDNString);
              debugInfo("Using password policy subentry %s for user %s.",
                        String.valueOf(subentryDN), userDNString);
            }
          }
          return policy;
        }
      }
    }
    // This shouldn't happen, but if it does then use the default.
    // There is no policy subentry defined: use the default.
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Falling back to the default password policy for " +
            "user %s", userDNString);
        debugInfo("Using the default password policy for user %s",
                  userDNString);
      }
    }
@@ -402,43 +342,43 @@
   */
  private String getValue(AttributeType attributeType)
  {
    String stringValue = null;
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        if (a.getValues().isEmpty()) continue;
        stringValue = a.getValues().iterator().next().getStringValue();
        break ;
      }
    }
      if (debug)
      {
      if (stringValue == null)
      {
        if (debugEnabled())
        {
          debugInfo("Returning null because attribute %s does not " +
              "exist in user entry %s", attributeType.getNameOrOID(),
                                        userDNString);
              "exist in user entry %s",
                    attributeType.getNameOrOID(), userDNString);
        }
      }
      return null;
    }
    for (Attribute a : attrList)
    {
      for (AttributeValue v : a.getValues())
      {
        String stringValue = v.getStringValue();
        if (debug)
      else
        {
          if (debugEnabled())
          {
            debugInfo("Returning value %s for user %s", stringValue,
                      userDNString);
          debugInfo("Returning value %s for user %s",
                    stringValue, userDNString);
        }
          }
        }
        return stringValue;
      }
    }
    return null;
  }
@@ -457,30 +397,19 @@
  private long getGeneralizedTime(AttributeType attributeType)
          throws DirectoryException
  {
    long timeValue = -1 ;
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList != null)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning -1 because attribute %s does not " +
              "exist in user entry %s", attributeType.getNameOrOID(),
                                        userDNString);
        }
      }
      return -1;
    }
    for (Attribute a : attrList)
    {
      for (AttributeValue v  : a.getValues())
      {
        if (a.getValues().isEmpty()) continue;
        AttributeValue v = a.getValues().iterator().next();
        try
        {
          return GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
          timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
                                            v.getNormalizedValue());
        }
        catch (Exception e)
@@ -492,11 +421,10 @@
          if (debug)
          {
            debugWarning(
                "Unable to decode value %s for attribute " +
                    "%s in user entry %s: %s",
                v.getStringValue(),
                attributeType.getNameOrOID(), userDNString);
            debugWarning("Unable to decode value %s for attribute %s " +
                 "in user entry %s: %s",
                         v.getStringValue(), attributeType.getNameOrOID(),
                         userDNString, stackTraceToSingleLineString(e));
          }
          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME;
@@ -506,21 +434,25 @@
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                       message, msgID, e);
        }
        break ;
      }
    }
    if (debug)
    {
      if (timeValue == -1)
      {
      if (debugEnabled())
      {
        debugInfo("Returning -1 for attribute %s in user entry %s " +
            "because all options have been exhausted.",
          debugInfo("Returning -1 because attribute %s does not " +
              "exist in user entry %s",
                  attributeType.getNameOrOID(), userDNString);
      }
    }
      // FIXME: else to be consistent...
    }
    return -1;
    return timeValue;
  }
@@ -544,22 +476,8 @@
    ArrayList<Long> timeValues = new ArrayList<Long>();
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList != null)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning an empty list because attribute %s " +
              "does not exist in user entry %s",
                    attributeType.getNameOrOID(), userDNString);
        }
      }
      return timeValues;
    }
    for (Attribute a : attrList)
    {
      for (AttributeValue v  : a.getValues())
@@ -578,12 +496,10 @@
          if (debug)
          {
            debugWarning(
                "Unable to decode value %s for attribute " +
                    "%s in user entry %s: %s",
                v.getStringValue(),
                attributeType.getNameOrOID(),
                userDNString, e);
              debugWarning("Unable to decode value %s for attribute %s" +
                   "in user entry %s: %s",
                           v.getStringValue(), attributeType.getNameOrOID(),
                           userDNString, stackTraceToSingleLineString(e));
          }
          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME;
@@ -595,7 +511,20 @@
        }
      }
    }
    }
    if (debug)
    {
      if (timeValues.isEmpty())
      {
        if (debugEnabled())
        {
          debugInfo("Returning an empty list because attribute %s " +
              "does not exist in user entry %s",
                    attributeType.getNameOrOID(), userDNString);
        }
      }
    }
    return timeValues;
  }
@@ -607,40 +536,26 @@
   *
   * @param  attributeType  The attribute type whose value should be parsed as a
   *                        Boolean.
   * @param  defaultValue   The default value that should be used if the
   *                        specified attribute does not exist.
   *
   * @return  The requested Boolean value, or the default value if the specified
   *          attribute does not exist with a Boolean value.
   * @return  The attribute's value represented as a ConditionResult value, or
   *          ConditionResult.UNDEFINED if the specified attribute does not
   *          exist in the entry.
   *
   * @throws  DirectoryException  If the value cannot be decoded as a Boolean.
   */
  private boolean getBoolean(AttributeType attributeType, boolean defaultValue)
  private ConditionResult getBoolean(AttributeType attributeType)
          throws DirectoryException
  {
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if ((attrList == null) || attrList.isEmpty())
    if (attrList != null)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning default of %b because attribute " +
              "%s does not exist in user entry %s",
                    defaultValue, attributeType.getNameOrOID(),
                    attributeType.getNameOrOID());
        }
      }
      return defaultValue;
    }
    for (Attribute a : attrList)
    {
      for (AttributeValue v  : a.getValues())
      {
        String valueString = toLowerCase(v.getStringValue());
        if (a.getValues().isEmpty()) continue;
        String valueString
             = toLowerCase(a.getValues().iterator().next().getStringValue());
        if (valueString.equals("true") || valueString.equals("yes") ||
            valueString.equals("on") || valueString.equals("1"))
        {
@@ -648,61 +563,58 @@
          {
            if (debugEnabled())
            {
              debugInfo("Attribute %s resolves to true for user " +
                  "entry %s", attributeType.getNameOrOID(), userDNString);
              debugInfo("Attribute %s resolves to true for user entry %s",
                        attributeType.getNameOrOID(), userDNString);
            }
          }
          return true;
          return ConditionResult.TRUE;
        }
        else if (valueString.equals("false") || valueString.equals("no") ||
        if (valueString.equals("false") || valueString.equals("no") ||
                 valueString.equals("off") || valueString.equals("0"))
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Attribute %s resolves to false for user " +
                  "entry %s", attributeType.getNameOrOID(), userDNString);
              debugInfo("Attribute %s resolves to false for user entry %s",
                        attributeType.getNameOrOID(), userDNString);
            }
          }
          return false;
          return ConditionResult.FALSE;
        }
        else
        {
          if (debug)
          {
            debugError(
                "Unable to resolve value %s for attribute " +
                           "%s in user entry %us as a Boolean.",
            debugError("Unable to resolve value %s for attribute %s " +
                 "in user entry %s as a Boolean.",
                       valueString, attributeType.getNameOrOID(),
                       userDNString);
          }
          int msgID = MSGID_PWPSTATE_CANNOT_DECODE_BOOLEAN;
          String message = getMessage(msgID, v.getStringValue(),
        String message = getMessage(msgID, valueString,
                                      attributeType.getNameOrOID(),
                                      userDNString);
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                       message, msgID);
        }
      }
    }
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning default of %b for attribute %s in " +
            "user entry %s because all options have been " +
            "exhausted.", defaultValue, attributeType.getNameOrOID(),
                          userDNString);
        debugInfo("Returning %s because attribute %s does not exist " +
             "in user entry %s",
                  ConditionResult.UNDEFINED.toString(),
                  attributeType.getNameOrOID(), userDNString);
      }
    }
    return defaultValue;
    return ConditionResult.UNDEFINED;
  }
@@ -720,21 +632,6 @@
  /**
   * Retrieves the set of modifications that correspond to changes made in
   * password policy processing that may need to be applied to the user entry.
   *
   * @return  The set of modifications that correspond to changes made in
   *          password policy processing that may need to be applied to the user
   *          entry.
   */
  public LinkedList<Modification> getModifications()
  {
    return modifications;
  }
  /**
   * Retrieves the set of values for the password attribute from the user entry.
   *
   * @return  The set of values for the password attribute from the user entry.
@@ -743,10 +640,15 @@
  {
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList != null)
    {
    for (Attribute a : attrList)
    {
        if (a.getValues().isEmpty()) continue;
      return a.getValues();
    }
    }
    return new LinkedHashSet<AttributeValue>(0);
  }
@@ -754,47 +656,6 @@
  /**
   * Indicates whether the associated password policy requires that
   * authentication be performed in a secure manner.
   *
   * @return  <CODE>true</CODE> if the associated password policy requires that
   *          authentication be performed in a secure manner, or
   *          <CODE>false</CODE> if not.
   */
  public boolean requireSecureAuthentication()
  {
    return passwordPolicy.requireSecureAuthentication();
  }
  /**
   * Retrieves time that this password policy state object was created.
   *
   * @return  The time that this password policy state object was created.
   */
  public long getCurrentTime()
  {
    return currentTime;
  }
  /**
   * Retrieves the generalized time representation of the time that this
   * password policy state object was created.
   *
   * @return  The generalized time representation of the time that this
   *          password policy state object was created.
   */
  public String getCurrentGeneralizedTime()
  {
    return currentGeneralizedTime;
  }
  /**
   * Sets a new value for the password changed time equal to the current time.
   */
  public void setPasswordChangedTime()
@@ -808,6 +669,8 @@
      }
    }
    // passwordChangedTime is computed in the constructor from values in the
    // entry.
    if (passwordChangedTime != currentTime)
    {
      passwordChangedTime = currentTime;
@@ -850,39 +713,25 @@
   */
  public boolean isDisabled()
  {
    if ((isDisabled == null) || (isDisabled == ConditionResult.UNDEFINED))
    if (isDisabled != ConditionResult.UNDEFINED)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of %b for user %s",
                    (isDisabled == ConditionResult.TRUE), userDNString);
        }
      }
      return isDisabled == ConditionResult.TRUE;
    }
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
      try
      {
        if (getBoolean(type, false))
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("User %s is administratively disabled.", userDNString);
            }
          }
          isDisabled = ConditionResult.TRUE;
          return true;
        }
        else
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("User %s is not administratively disabled.",
                        userDNString);
            }
          }
          isDisabled = ConditionResult.FALSE;
          return false;
        }
      isDisabled = getBoolean(type);
      }
      catch (Exception e)
      {
@@ -891,46 +740,44 @@
          debugCaught(DebugLogLevel.ERROR, e);
        }
      isDisabled = ConditionResult.TRUE;
        if (debug)
        {
          debugWarning(
              "User %s is considered administratively disabled " +
          debugWarning("User %s is considered administratively disabled " +
                  "because an error occurred while attempting to make " +
                  "the determination: %s.",
              userDNString, stackTraceToSingleLineString(e));
        }
        isDisabled = ConditionResult.TRUE;
        return true;
      }
    }
    if (isDisabled == ConditionResult.FALSE)
    if (isDisabled == ConditionResult.UNDEFINED)
    {
      isDisabled = ConditionResult.FALSE;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of false for user %s",
                    userDNString);
          debugInfo("User %s is not administratively disabled since the" +
                          " attribute \"%s\" is not present in the entry.",
                     userDNString, OP_ATTR_ACCOUNT_DISABLED);
        }
      }
      return false;
    }
    else
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of true for user %s",
                    userDNString);
        debugInfo("User %s %s administratively disabled.",
                  userDNString,
                  ((isDisabled == ConditionResult.TRUE) ? " is" : " is not"));
        }
      }
      return true;
    }
    return isDisabled == ConditionResult.TRUE;
  }
@@ -954,27 +801,27 @@
    }
    if (isDisabled == isDisabled())
    {
      return; // requested state matches current state
    }
    this.isDisabled = ConditionResult.inverseOf(this.isDisabled);
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true);
    LinkedHashSet<AttributeValue> values;
    if (isDisabled)
    {
      if (this.isDisabled == ConditionResult.TRUE)
      {
        return;
      }
      this.isDisabled = ConditionResult.TRUE;
      values = new LinkedHashSet<AttributeValue>(1);
      values.add(new AttributeValue(type, String.valueOf(isDisabled)));
      LinkedHashSet<AttributeValue> values
           = new LinkedHashSet<AttributeValue>(1);
      values.add(new AttributeValue(type, String.valueOf(true)));
      Attribute a = new Attribute(type, OP_ATTR_ACCOUNT_DISABLED, values);
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
@@ -984,15 +831,7 @@
    }
    else
    {
      if (this.isDisabled == ConditionResult.FALSE)
      {
        return;
      }
      this.isDisabled = ConditionResult.FALSE;
      values = new LinkedHashSet<AttributeValue>(1);
      values.add(new AttributeValue(type, String.valueOf(isDisabled)));
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
@@ -1015,19 +854,79 @@
   */
  public boolean isAccountExpired()
  {
    if ((isAccountExpired == null) ||
        (isAccountExpired == ConditionResult.UNDEFINED))
    if (isAccountExpired != ConditionResult.UNDEFINED)
    {
      if(debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of %b for user %s",
                    (isAccountExpired == ConditionResult.TRUE), userDNString);
        }
      }
      return isAccountExpired == ConditionResult.TRUE;
    }
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME,
                                            true);
    long expirationTime;
      try
      {
        long expirationTime = getGeneralizedTime(type);
        if (expirationTime < 0)
      expirationTime = getGeneralizedTime(type);
     }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      isAccountExpired = ConditionResult.TRUE;
      if (debug)
      {
          debugWarning("User %s is considered to have an expired account " +
               "because an error occurred while attempting to make " +
               "the determination: %s.",
              userDNString, stackTraceToSingleLineString(e));
      }
      return true;
    }
    if (expirationTime > currentTime)
    {
      // The user does have an expiration time, but it hasn't arrived yet.
      isAccountExpired = ConditionResult.FALSE;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("The account for user %s is not expired because the " +
              "expiration time has not yet arrived.", userDNString);
        }
      }
    }
    else if (expirationTime >= 0)
    {
      // The user does have an expiration time, and it is in the past.
      isAccountExpired = ConditionResult.TRUE;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("The account for user %s is expired because the " +
              "expiration time in that account has passed.", userDNString);
        }
      }
    }
    else
        {
          // The user doesn't have an expiration time in their entry, so it
          // can't be expired.
      isAccountExpired = ConditionResult.FALSE;
          if (debug)
          {
            if (debugEnabled())
@@ -1037,102 +936,40 @@
              userDNString);
            }
          }
          isAccountExpired = ConditionResult.FALSE;
          return false;
        }
        else if (expirationTime > currentTime)
        {
          // The user does have an expiration time, but it hasn't arrived yet.
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("The account for user %s is not expired because the " +
                  "expiration time has not yet arrived.", userDNString);
            }
          }
          isAccountExpired = ConditionResult.FALSE;
          return false;
        }
        else
        {
          // The user does have an expiration time, and it is in the past.
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("The account for user %s is expired because the " +
                  "expiration time in that account has passed.", userDNString);
            }
          }
          isAccountExpired = ConditionResult.TRUE;
          return true;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        if (debug)
        {
          debugWarning(
              "User %s is considered to have an expired account " +
                  "because an error occurred while attempting to make " +
                  "the determination: %s.",
              userDNString, stackTraceToSingleLineString(e));
        }
        isAccountExpired = ConditionResult.TRUE;
        return true;
      }
    }
    if (isAccountExpired == ConditionResult.FALSE)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of false for user %s",
                    userDNString);
        }
      }
      return false;
    }
    else
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of true for user %s",
                    userDNString);
        }
      }
      return true;
    }
    return isAccountExpired == ConditionResult.TRUE;
  }
  /**
   * Retrieves the set of times of failed authentication attempts for the user.
   * If authentication failure time expiration is enabled, and there are expired
   * times in the entry, these times are removed from the instance field and an
   * update is provided to delete those values from the entry.
   *
   * @return  The set of times of failed authentication attempts for the user.
   * @return  The set of times of failed authentication attempts for the user,
   *          which will be an empty list in the case of no valid (unexpired)
   *          times in the entry.
   */
  public List<Long> getAuthFailureTimes()
  private List<Long> getAuthFailureTimes()
  {
    if (authFailureTimes == null)
    if (authFailureTimes != null)
    {
      if(debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored auth failure time list of %d " +
              "elements for user %s" +
              authFailureTimes.size(), userDNString);
        }
      }
      return authFailureTimes;
    }
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
      if (type == null)
@@ -1144,85 +981,6 @@
      try
      {
        authFailureTimes = getGeneralizedTimes(type);
        // Remove any expired failures from the list.
        if (passwordPolicy.getLockoutFailureExpirationInterval() > 0)
        {
          LinkedHashSet<AttributeValue> values = null;
          long expirationTime = currentTime -
               (passwordPolicy.getLockoutFailureExpirationInterval()*1000L);
          Iterator<Long> iterator = authFailureTimes.iterator();
          while (iterator.hasNext())
          {
            long l = iterator.next();
            if (l < expirationTime)
            {
              if (debug)
              {
                if (debugEnabled())
                {
                  debugInfo("Removing expired auth failure time %d for user " +
                      "%s", l, userDNString);
                }
              }
              iterator.remove();
              if (values == null)
              {
                values = new LinkedHashSet<AttributeValue>();
              }
              values.add(new AttributeValue(type,
                                            GeneralizedTimeSyntax.format(l)));
            }
          }
          if (values != null)
          {
            Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
                                        values);
            ArrayList<Attribute> removeList = new ArrayList<Attribute>(1);
            removeList.add(a);
            if (authFailureTimes.isEmpty())
            {
              if (updateEntry)
              {
                userEntry.removeAttribute(type);
              }
            }
            else
            {
              LinkedHashSet<AttributeValue> keepValues =
                   new LinkedHashSet<AttributeValue>(authFailureTimes.size());
              for (Long l : authFailureTimes)
              {
                keepValues.add(new AttributeValue(type,
                                        GeneralizedTimeSyntax.format(l)));
              }
              ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
              keepList.add(new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
                                         keepValues));
              if (updateEntry)
              {
                userEntry.putAttribute(type, keepList);
              }
            }
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.DELETE, a,
                                                 true));
            }
          }
        }
      }
      catch (Exception e)
      {
@@ -1233,11 +991,9 @@
        if (debug)
        {
          debugWarning(
              "Error while processing auth failure times " +
        debugWarning("Error while processing auth failure times " +
                  "for user %s: %s",
              userDNString,
              stackTraceToSingleLineString(e));
                     userDNString, stackTraceToSingleLineString(e));
        }
        authFailureTimes = new ArrayList<Long>();
@@ -1251,16 +1007,97 @@
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type), true));
        }
      return authFailureTimes;
    }
    if (authFailureTimes.isEmpty())
    {
      if (debug)
      {
       if (debugEnabled())
        {
          debugInfo("Returning an empty auth failure time list for user %s" +
                    " because the attribute is absent from the entry.",
                    userDNString);
      }
    }
      return authFailureTimes;
    }
    // Remove any expired failures from the list.
    if (passwordPolicy.getLockoutFailureExpirationInterval() > 0)
    {
      LinkedHashSet<AttributeValue> valuesToRemove = null;
      long expirationTime = currentTime -
           (passwordPolicy.getLockoutFailureExpirationInterval() * 1000L);
      Iterator<Long> iterator = authFailureTimes.iterator();
      while (iterator.hasNext())
      {
        long l = iterator.next();
        if (l < expirationTime)
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Removing expired auth failure time %d for user %s",
                        l, userDNString);
            }
          }
          iterator.remove();
          if (valuesToRemove == null)
          {
            valuesToRemove = new LinkedHashSet<AttributeValue>();
          }
          valuesToRemove.add(new AttributeValue(type,
                                              GeneralizedTimeSyntax.format(l)));
        }
      }
      if (valuesToRemove != null)
      {
        if (updateEntry)
        {
          if (authFailureTimes.isEmpty())
          {
            userEntry.removeAttribute(type);
          }
          else
          {
            LinkedHashSet<AttributeValue> keepValues =
                 new LinkedHashSet<AttributeValue>(authFailureTimes.size());
            for (Long l : authFailureTimes)
            {
              keepValues.add(
                   new AttributeValue(type, GeneralizedTimeSyntax.format(l)));
            }
            ArrayList<Attribute> keepList = new ArrayList<Attribute>(1);
            keepList.add(new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
                                       keepValues));
            userEntry.putAttribute(type, keepList);
          }
        }
        else
        {
          Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME,
                                      valuesToRemove);
          modifications.add(new Modification(ModificationType.DELETE, a,
                                             true));
        }
      }
    }
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning auth failure time list of %d " +
            "elements for user %s" +
        debugInfo("Returning auth failure time list of %d elements for user %s",
            authFailureTimes.size(), userDNString);
      }
    }
@@ -1272,10 +1109,13 @@
  /**
   * Updates the set of authentication failure times to include the current
   * time.
   * time. If the number of failures reaches the policy configuration limit,
   * lock the account.
   */
  public void updateAuthFailureTimes()
  {
    assert passwordPolicy.getLockoutFailureCount() > 0;
    if (debug)
    {
      if (debugEnabled())
@@ -1287,6 +1127,7 @@
    List<Long> failureTimes = getAuthFailureTimes();
    // Note: failureTimes == this.authFailureTimes
    long highestFailureTime = -1;
    for (Long l : failureTimes)
    {
@@ -1337,15 +1178,30 @@
    {
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
    // Now check to see if there have been sufficient failures to lock the
    // account.
    if (passwordPolicy.getLockoutFailureCount() <= failureTimes.size())
    {
      setFailureLockedTime(highestFailureTime);
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Locking user account %s due to too many failures.",
                    userDNString);
        }
      }
    }
  }
  /**
   * Updates the user entry to remove any record of previous authentication
   * failures.
   * failure times.
   */
  public void clearAuthFailureTimes()
  private void clearAuthFailureTimes()
  {
    if (debug)
    {
@@ -1361,7 +1217,8 @@
    {
      return;
    }
    failureTimes.clear();
    failureTimes.clear(); // Note: failureTimes == this.authFailureTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC);
@@ -1383,17 +1240,159 @@
  }
  /**
   * Retrieves the time of an authentication failure lockout for the user.
   *
   * @return  The time of an authentication failure lockout for the user, or -1
   *          if no such time is present in the entry.
   */
  private long getFailureLockedTime()
  {
    if (failureLockedTime != Long.MIN_VALUE)
    {
      return failureLockedTime;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
           OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    try
    {
      failureLockedTime = getGeneralizedTime(type);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      failureLockedTime = currentTime;
      if (debug)
      {
        debugWarning("Returning current time for user %s because an error " +
             "occurred: %s",
                     userDNString, stackTraceToSingleLineString(e));
      }
      return failureLockedTime;
    }
    // An expired locked time is handled in lockedDueToFailures.
    return failureLockedTime;
  }
  /**
    Sets the failure lockout attribute in the entry to the requested time.
    @param time  The time to which to set the entry's failure lockout attribute.
   */
  private void setFailureLockedTime(final long time)
  {
    if (time == getFailureLockedTime())
    {
      return;
    }
    failureLockedTime = time;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(type,
                        GeneralizedTimeSyntax.format(failureLockedTime)));
    Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_LOCKED_TIME, values);
    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));
    }
  }
  /**
   * Updates the user entry to remove any record of previous authentication
   * failure lockout.
   */
  private void clearFailureLockedTime()
  {
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Clearing failure lockout time for user %s.", userDNString);
      }
    }
    if (-1L == getFailureLockedTime())
    {
      return;
    }
    failureLockedTime = -1L;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type), true));
    }
  }
  /**
   * Indicates whether the associated user should be considered locked out as a
   * result of too many authentication failures.
   * result of too many authentication failures. In the case of an expired
   * lock-out, this routine produces the update to clear the lock-out attribute
   * and the authentication failure timestamps.
   * In case the failure lockout time is absent from the entry, but sufficient
   * authentication failure timestamps are present in the entry, this routine
   * produces the update to set the lock-out attribute.
   *
   * @return  <CODE>true</CODE> if the user is currently locked out due to too
   *          many authentication failures, or <CODE>false</CODE> if not.
   */
  public boolean lockedDueToFailures()
  {
    int maxFailures = passwordPolicy.getLockoutFailureCount();
    // FIXME: Introduce a state field to cache the computed value of this
    // method. Note that only a cached "locked" status can be returned due to
    // the possibility of intervening updates to this.failureLockedTime by
    // updateAuthFailureTimes.
    // Check if the feature is enabled in the policy.
    final int maxFailures = passwordPolicy.getLockoutFailureCount();
    if (maxFailures <= 0)
    {
      if (debug)
@@ -1408,86 +1407,61 @@
      return false;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    // Get the locked time from the user's entry. If it is present and not
    // expired, the account is locked. If it is absent, the failure timestamps
    // must be checked, since failure timestamps sufficient to lock the
    // account could be produced across the synchronization topology within the
    // synchronization latency. Also, note that IETF
    // draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as
    // the value to be set under a "locked until reset" regime; however, this
    // implementation accepts the value as a locked entry, but observes the
    // lockout expiration policy for all values including this one.
    // FIXME: This "getter" is unusual in that it might produce an update to the
    // entry in two cases. Does it make sense to factor the methods so that,
    // e.g., an expired lockout is reported, and clearing the lockout is left to
    // the caller?
    if (getFailureLockedTime() < 0L)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    // Get the locked time from the user's entry.  If it's not there, then the
    // account is not locked.
    if (failureLockedTime == Long.MIN_VALUE)
      // There was no locked time present in the entry; however, sufficient
      // failure times might have accumulated to trigger a lockout.
      if (getAuthFailureTimes().size() < maxFailures)
    {
      try
      {
        failureLockedTime = getGeneralizedTime(type);
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        if (debug)
        {
          debugWarning(
              "Returning true for user %s because an error occurred: %s",
              userDNString, stackTraceToSingleLineString(e));
        }
        return true;
      }
    }
    if (failureLockedTime <= 0)
    {
      // There is no failure locked time, but that doesn't mean that the
      // account isn't locked anyway due to the maximum number of failures
      // (which may happen in certain cases due to synchronization latency).
      List<Long> failureTimes = getAuthFailureTimes();
      if ((failureTimes != null) && (failureTimes.size() >= maxFailures))
      {
        // The account isn't locked but should be, so do so now.
        lockDueToFailures();
        if (debug)
        {
          if (debugEnabled())
          {
            debugInfo("Setting the lock for user " + userDNString +
                " because there were enough preexisting failures even " +
                "though there was no account locked time.");
          }
        }
        return true;
      }
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning false for user  because there is no locked " +
              "time.", userDNString);
            debugInfo("Returning false for user %s because there is no " +
                 "locked time.", userDNString);
        }
      }
      return false;
    }
    // There is a failure locked time, but it may be expired.  See if that's the
    // case.
      // The account isn't locked but should be, so do so now.
      setFailureLockedTime(currentTime);// FIXME: set to max(failureTimes)?
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Locking user %s because there were enough existing " +
               "failures even though there was no account locked time.",
                    userDNString);
        }
      }
      // Fall through...
    }
    // There is a failure locked time, but it may be expired.
    if (passwordPolicy.getLockoutDuration() > 0)
    {
      long unlockTime = failureLockedTime +
      final long unlockTime = getFailureLockedTime() +
          (1000L * passwordPolicy.getLockoutDuration());
      if (unlockTime > currentTime)
      {
        secondsUntilUnlock = (int) (unlockTime - currentTime);
        if (debug)
        {
          if (debugEnabled())
@@ -1496,23 +1470,13 @@
                "time and the lockout duration has not been reached.",
                      userDNString);
          }
          secondsUntilUnlock = (int) (unlockTime - currentTime);
        }
        return true;
      }
      else
      {
        if (updateEntry)
        {
          userEntry.removeAttribute(type);
        }
        else
        {
          modifications.add(new Modification(ModificationType.REPLACE,
                                             new Attribute(type), true));
        }
      // The lockout in the entry has expired...
      clearFailureLockout();
        if (debug)
        {
@@ -1523,11 +1487,10 @@
          }
        }
      assert -1L == getFailureLockedTime();
        return false;
      }
    }
    else
    {
      if (debug)
      {
        if (debugEnabled())
@@ -1538,9 +1501,9 @@
        }
      }
    assert -1L <= getFailureLockedTime();
      return true;
    }
  }
@@ -1555,58 +1518,12 @@
   */
  public int getSecondsUntilUnlock()
  {
    if (secondsUntilUnlock < 0)
    {
      return -1;
    }
    else
    {
      return secondsUntilUnlock;
    }
  }
    // secondsUntilUnlock is only set when failureLockedTime is present and
    // PasswordPolicy.getLockoutDuration is enabled; hence it is not
    // unreasonable to find secondsUntilUnlock uninitialized.
    assert failureLockedTime != Long.MIN_VALUE;
  /**
   * Updates the user account to indicate that it has been locked due to too
   * many authentication failures.
   */
  public void lockDueToFailures()
  {
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Locking user account %s due to too many failures.",
                  userDNString);
      }
    }
    failureLockedTime = currentTime;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
    values.add(new AttributeValue(type,
                        GeneralizedTimeSyntax.format(failureLockedTime)));
    Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_LOCKED_TIME, values);
    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(a);
    if (updateEntry)
    {
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE, a, true));
    }
    return (secondsUntilUnlock < 0) ? -1 : secondsUntilUnlock;
  }
@@ -1617,36 +1534,8 @@
   */
  public void clearFailureLockout()
  {
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Clearing lockout failures for user %s", userDNString);
      }
    }
    if (! lockedDueToFailures())
    {
      return;
    }
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC);
    if (type == null)
    {
      type = DirectoryServer.getDefaultAttributeType(
                                  OP_ATTR_PWPOLICY_LOCKED_TIME);
    }
    if (updateEntry)
    {
      userEntry.removeAttribute(type);
    }
    else
    {
      modifications.add(new Modification(ModificationType.REPLACE,
                                         new Attribute(type), true));
    }
    clearAuthFailureTimes();
    clearFailureLockedTime();
  }
@@ -1660,61 +1549,63 @@
   */
  public long getLastLoginTime()
  {
    if (lastLoginTime == Long.MIN_VALUE)
    if (lastLoginTime != Long.MIN_VALUE)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored last login time of %d for user %s.",
                    lastLoginTime, userDNString);
        }
      }
      return lastLoginTime;
    }
    // The policy configuration must be checked since the entry cannot be
    // evaluated without both an attribute name and timestamp format.
      AttributeType type   = passwordPolicy.getLastLoginTimeAttribute();
      String        format = passwordPolicy.getLastLoginTimeFormat();
      if ((type == null) || (format == null))
      {
      lastLoginTime = -1;
        if (debug)
        {
          if (debugEnabled())
          {
            debugInfo("Returning -1 for user %s because no last " +
                "login time will be maintained.", userDNString);
          debugInfo("Returning -1 for user %s because no last login time " +
               "will be maintained.", userDNString);
          }
        }
        lastLoginTime = -1;
        return lastLoginTime;
      }
    lastLoginTime = -1;
      List<Attribute> attrList = userEntry.getAttribute(type);
      if ((attrList == null) || attrList.isEmpty())
      {
        if (debug)
        {
          if (debugEnabled())
          {
            debugInfo("Returning -1 for user %s because no last " +
                "login time value exists.", userDNString);
          }
        }
        lastLoginTime = -1;
        return lastLoginTime;
      }
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        for (AttributeValue v : a.getValues())
        {
          String valueString = v.getStringValue();
          SimpleDateFormat dateFormat;
        if (a.getValues().isEmpty()) continue;
        String valueString = a.getValues().iterator().next().getStringValue();
          try
          {
            dateFormat    = new SimpleDateFormat(format);
          SimpleDateFormat dateFormat = new SimpleDateFormat(format);
            lastLoginTime = dateFormat.parse(valueString).getTime();
            if (debug)
            {
              if (debugEnabled())
              {
                debugInfo("Returning last login time of %s for user " +
                    "%s decoded using current last login " +
                    "time format.", lastLoginTime, userDNString);
              debugInfo("Returning last login time of %d for user %s" +
                   "decoded using current last login time format.",
                        lastLoginTime, userDNString);
              }
            }
@@ -1733,16 +1624,15 @@
            {
              try
              {
                dateFormat = new SimpleDateFormat(f);
              SimpleDateFormat dateFormat = new SimpleDateFormat(f);
                lastLoginTime = dateFormat.parse(valueString).getTime();
                if (debug)
                {
                  if (debugEnabled())
                  {
                    debugInfo("Returning last login time of %s for " +
                        "user %s decoded using previous " +
                        "last login time format of %s",
                  debugInfo("Returning last login time of %d for user %s" +
                       "decoded using previous last login time format of %s.",
                              lastLoginTime, userDNString, f);
                  }
                }
@@ -1753,46 +1643,31 @@
              {
                if (debugEnabled())
                {
                  debugCaught(DebugLogLevel.ERROR, e2);
                debugCaught(DebugLogLevel.ERROR, e);
                }
              }
            }
          assert lastLoginTime == -1;
            if (debug)
            {
              debugWarning(
                  "Returning -1 for user %s because the " +
                      "last login time value %s could not " +
                      "be parsed using any known format.",
              debugWarning("Returning -1 for user %s because the last login " +
                   "time value %s could not be parsed using any known format.",
                  userDNString, valueString);
            }
            lastLoginTime = -1;
            return lastLoginTime;
          }
        }
      }
      // We shouldn't get here.
      if (debug)
      {
        debugWarning(
            "Returning -1 for user %s because even though " +
                         "there appears to be a last login time " +
                         "value we couldn't decipher it.",
                     userDNString);
      }
      return -1;
    }
    assert lastLoginTime == -1;
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning previously calculated last login time " +
            "of %s for user %s", lastLoginTime, userDNString);
        debugInfo("Returning %d for user %s because no last " +
            "login time value exists.", lastLoginTime, userDNString);
      }
    }
@@ -1829,8 +1704,7 @@
      if (debug)
      {
        debugWarning(
            "Unable to set last login time for user %s because an " +
        debugWarning("Unable to set last login time for user %s because an " +
                "error occurred: %s",
            userDNString, stackTraceToSingleLineString(e));
      }
@@ -1859,11 +1733,11 @@
    values.add(new AttributeValue(type, timestamp));
    Attribute a = new Attribute(type, type.getNameOrOID(), values);
    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(a);
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
@@ -1892,10 +1766,26 @@
   */
  public boolean lockedDueToIdleInterval()
  {
    if ((isIdleLocked == null) || (isIdleLocked == ConditionResult.UNDEFINED))
    if (isIdleLocked != ConditionResult.UNDEFINED)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of %b for user %s",
                    (isIdleLocked == ConditionResult.TRUE), userDNString);
        }
      }
      return isIdleLocked == ConditionResult.TRUE;
    }
    // Return immediately if this feature is disabled, since the feature is not
    // responsible for any state attribute in the entry.
      if (passwordPolicy.getIdleLockoutInterval() <= 0)
      {
      isIdleLocked = ConditionResult.FALSE;
        if (debug)
        {
          if (debugEnabled())
@@ -1905,124 +1795,59 @@
          }
        }
        isIdleLocked = ConditionResult.FALSE;
        return false;
      }
      long lockTime = currentTime -
          (passwordPolicy.getIdleLockoutInterval() * 1000L);
                         (1000L * passwordPolicy.getIdleLockoutInterval());
    if(lockTime < 0) lockTime = 0;
      long lastLoginTime = getLastLoginTime();
      if (lastLoginTime > 0)
    if (lastLoginTime > lockTime || passwordChangedTime > lockTime)
      {
      isIdleLocked = ConditionResult.FALSE;
      if (debug)
      {
        if (debugEnabled())
        {
          StringBuilder reason = new StringBuilder();
        if (lastLoginTime > lockTime)
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Returning false for user %s because the last login " +
                  "time is in an acceptable window.", userDNString);
            }
          }
          isIdleLocked = ConditionResult.FALSE;
          return false;
            reason.append("the last login time is in an acceptable window");
        }
        else
        {
          if (passwordChangedTime > lockTime)
            if(lastLoginTime < 0)
          {
            if (debug)
            {
              if (debugEnabled())
              {
                debugInfo("Returning false for user  because the password " +
                    "changed time is in an acceptable window.", userDNString);
              reason.append("there is no last login time, but ");
            }
            reason.append(
                 "the password changed time is in an acceptable window");
          }
          debugInfo("Returning false for user %s because %s.",
                    userDNString, reason.toString());
              }
            }
            isIdleLocked = ConditionResult.FALSE;
            return false;
          }
          else
          {
            if (debug)
            {
              if (debugEnabled())
              {
                debugInfo("Returning true for user because neither last " +
                    "login time nor password changed time are in an " +
                    "acceptable window.", userDNString);
              }
            }
            isIdleLocked = ConditionResult.TRUE;
            return true;
          }
        }
      }
      else
      {
        if (passwordChangedTime < lockTime)
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Returning true for user %s because there is no last " +
                  "login time and the password changed time is not in " +
                  "an acceptable window.", userDNString);
            }
          }
          isIdleLocked = ConditionResult.TRUE;
          return true;
        }
        else
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Returning false for user %s because there is no " +
                  "last login time but the password changed time is in an " +
                  "acceptable window.", userDNString);
            }
          }
          isIdleLocked = ConditionResult.FALSE;
          return false;
          String reason = (lastLoginTime < 0)
             ? "there is no last login time and the password " +
                  "changed time is not in an acceptable window"
             : "neither last login time nor password " +
                  "changed time are in an acceptable window";
          debugInfo("Returning true for user %s because %s.",
                    userDNString, reason);
        }
      }
    }
    if (isIdleLocked == ConditionResult.TRUE)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of true for user %s",
                    userDNString);
        }
      }
      return true;
    }
    else
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of false for user %s",
                    userDNString);
        }
      }
      return false;
    }
    return isIdleLocked == ConditionResult.TRUE;
  }
@@ -2036,22 +1861,46 @@
   */
  public boolean mustChangePassword()
  {
    // If the password policy doesn't use force change on add or force change on
    // reset, or if it forbits the user from changing their password, then this
    // must return false.
    if (! passwordPolicy.allowUserPasswordChanges())
    if(mustChangePassword != ConditionResult.UNDEFINED)
    {
      return false;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of %b for user %s.",
                    (mustChangePassword == ConditionResult.TRUE), userDNString);
    }
    else if (! (passwordPolicy.forceChangeOnAdd() ||
                passwordPolicy.forceChangeOnReset()))
      }
      return mustChangePassword == ConditionResult.TRUE;
    }
    // If the password policy doesn't use force change on add or force change on
    // reset, or if it forbids the user from changing his password, then return
    // false.
    // FIXME: the only getter responsible for a state attribute (pwdReset) that
    // considers the policy before checking the entry for the presence of the
    // attribute.
    if (! (passwordPolicy.allowUserPasswordChanges()
           && (passwordPolicy.forceChangeOnAdd()
               || passwordPolicy.forceChangeOnReset())))
    {
      mustChangePassword = ConditionResult.FALSE;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning false for user %s because neither force " +
               "change on add nor force change on reset is enabled, " +
               "or users are not allowed to self-modify passwords.",
                    userDNString);
        }
      }
      return false;
    }
    if ((mustChangePassword == null) ||
        (mustChangePassword == ConditionResult.UNDEFINED))
    {
      AttributeType type =
           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC);
      if (type == null)
@@ -2062,33 +1911,7 @@
      try
      {
        boolean resetRequired = getBoolean(type, false);
        if (resetRequired)
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Returning true for user %", userDNString);
            }
          }
          mustChangePassword = ConditionResult.TRUE;
          return true;
        }
        else
        {
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Returning false for user %s", userDNString);
            }
          }
          mustChangePassword = ConditionResult.FALSE;
          return false;
        }
      mustChangePassword = getBoolean(type);
      }
      catch (Exception e)
      {
@@ -2097,48 +1920,42 @@
          debugCaught(DebugLogLevel.ERROR, e);
        }
      mustChangePassword = ConditionResult.TRUE;
        if (debug)
        {
          if (debugEnabled())
          {
            debugInfo("Returning true for user %s because an unexpected " +
                "error occurred: %s",
        debugWarning("Returning true for user %s because an error occurred: %s",
                      userDNString, stackTraceToSingleLineString(e));
          }
        }
        mustChangePassword = ConditionResult.TRUE;
        return true;
      }
    }
    if (mustChangePassword == ConditionResult.TRUE)
    if(mustChangePassword == ConditionResult.UNDEFINED)
    {
      mustChangePassword = ConditionResult.FALSE;
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of true for user %s",
                    userDNString);
        }
      }
      return true;
    }
    else
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored result of false for user %s",
                    userDNString);
          debugInfo("Returning %b for user since the attribute \"%s\"" +
               " is not present in the entry.",
                    false, userDNString, OP_ATTR_PWPOLICY_RESET_REQUIRED);
        }
      }
      return false;
    }
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning %b for user %s.",
                  (mustChangePassword == ConditionResult.TRUE), userDNString);
      }
    }
    return mustChangePassword == ConditionResult.TRUE;
  }
@@ -2161,8 +1978,8 @@
      }
    }
    if (mustChangePassword ==
            (this.mustChangePassword == ConditionResult.TRUE)){
    if (mustChangePassword == mustChangePassword())
    {
      return;  // requested state matches current state
    }
@@ -2198,6 +2015,7 @@
    }
    else
    {
      // erase
      if (updateEntry)
      {
        userEntry.removeAttribute(type);
@@ -2222,6 +2040,8 @@
   */
  public boolean lockedDueToMaximumResetAge()
  {
    // This feature is reponsible for neither a state field nor an entry state
    // attribute.
    if (passwordPolicy.getMaximumPasswordResetAge() <= 0)
    {
      if (debug)
@@ -2448,17 +2268,15 @@
      }
    }
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning password expiration time of %s for user " +
            "%s", expirationTime, userDNString);
        debugInfo("Returning password expiration time of %d for user %s.",
                  expirationTime, userDNString);
      }
    }
    secondsUntilExpiration = (int) (expirationTime - currentTime);
    return expirationTime;
  }
@@ -2478,14 +2296,7 @@
      getPasswordExpirationTime();
    }
    if (isPasswordExpired == ConditionResult.TRUE)
    {
      return true;
    }
    else
    {
      return false;
    }
    return isPasswordExpired == ConditionResult.TRUE;
  }
@@ -2500,6 +2311,8 @@
   */
  public boolean isWithinMinimumAge()
  {
    // This feature is reponsible for neither a state field nor entry state
    // attribute.
    int minAge = passwordPolicy.getMinimumPasswordAge();
    if (minAge <= 0)
    {
@@ -2546,7 +2359,10 @@
      // The user is within the minimum age.
      if (debug)
      {
        debugWarning("Returning true.");
        if (debugEnabled())
        {
          debugInfo("Returning true.");
        }
      }
      return true;
@@ -2575,14 +2391,7 @@
      getPasswordExpirationTime();
    }
    if (mayUseGraceLogin == ConditionResult.TRUE)
    {
      return true;
    }
    else
    {
      return false;
    }
    return mayUseGraceLogin == ConditionResult.TRUE;
  }
@@ -2602,14 +2411,7 @@
      getPasswordExpirationTime();
    }
    if (shouldWarn == ConditionResult.TRUE)
    {
      return true;
    }
    else
    {
      return false;
    }
    return shouldWarn == ConditionResult.TRUE;
  }
@@ -2629,14 +2431,7 @@
      getPasswordExpirationTime();
    }
    if (isFirstWarning == ConditionResult.TRUE)
    {
      return true;
    }
    else
    {
      return false;
    }
    return isFirstWarning == ConditionResult.TRUE;
  }
@@ -2677,11 +2472,23 @@
   */
  public long getRequiredChangeTime()
  {
    if (requiredChangeTime == Long.MIN_VALUE)
    if (requiredChangeTime != Long.MIN_VALUE)
    {
      if (debug)
      {
        if (debugEnabled())
        {
          debugInfo("Returning stored required change time of %d for user %s",
                    requiredChangeTime, userDNString);
        }
      }
      return requiredChangeTime;
    }
      AttributeType type = DirectoryServer.getAttributeType(
                                OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
                                true);
                              OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
      try
      {
        requiredChangeTime = getGeneralizedTime(type);
@@ -2693,25 +2500,22 @@
          debugCaught(DebugLogLevel.ERROR, e);
        }
      requiredChangeTime = -1;
        if (debug)
        {
          debugWarning(
              "An error occurred while attempting to " +
                  "determine the required change time for " +
                  "user %s: %s",
              userDNString, stackTraceToSingleLineString(e));
        debugWarning("Returning %d for user %s because an error occurred: %s",
                     requiredChangeTime, userDNString,
                     stackTraceToSingleLineString(e));
        }
        requiredChangeTime = -1;
      return requiredChangeTime;
      }
    }
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Returning required change time of %s for user %s",
        debugInfo("Returning required change time of %d for user %s",
                  requiredChangeTime, userDNString);
      }
    }
@@ -2735,29 +2539,26 @@
      }
    }
    long reqChangeTime = getRequiredChangeTime();
    if (reqChangeTime != passwordPolicy.getRequireChangeByTime())
    long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime();
    if (getRequiredChangeTime() != requiredChangeByTimePolicy)
    {
      reqChangeTime = passwordPolicy.getRequireChangeByTime();
      requiredChangeTime = reqChangeTime;
      AttributeType type = DirectoryServer.getAttributeType(
                                OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
                                true);
                               OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true);
      LinkedHashSet<AttributeValue> values =
           new LinkedHashSet<AttributeValue>(1);
      String timeValue = GeneralizedTimeSyntax.format(passwordChangedTime);
      String timeValue =
           GeneralizedTimeSyntax.format(requiredChangeByTimePolicy);
      values.add(new AttributeValue(type, timeValue));
      Attribute a = new Attribute(type,
                                  OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME,
                                  values);
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      if (updateEntry)
      {
        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
        attrList.add(a);
        userEntry.putAttribute(type, attrList);
      }
      else
@@ -2795,9 +2596,7 @@
        if (debug)
        {
          debugWarning(
              "Unable to decode the warned time for user " +
                  "%s: %s",
          debugWarning("Unable to decode the warned time for user %s: %s",
              userDNString, stackTraceToSingleLineString(e));
        }
@@ -2848,11 +2647,11 @@
    values.add(GeneralizedTimeSyntax.createGeneralizedTimeValue(currentTime));
    Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_WARNED_TIME, values);
    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(a);
    if (updateEntry)
    {
      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
      attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
@@ -2876,6 +2675,20 @@
   */
  public void clearWarnedTime()
  {
    if (debug)
    {
      if (debugEnabled())
      {
        debugInfo("Clearing warned time for user %s", userDNString);
      }
    }
    if (getWarnedTime() < 0)
    {
      return;
    }
    warnedTime = -1;
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
    if (updateEntry)
@@ -2931,8 +2744,7 @@
        if (debug)
        {
          debugWarning(
              "Error while processing grace login times " +
          debugWarning("Error while processing grace login times " +
                  "for user %s: %s",
              userDNString, stackTraceToSingleLineString(e));
        }
@@ -3015,7 +2827,7 @@
    {
      highestGraceTime = currentTime;
    }
    graceTimes.add(highestGraceTime);
    graceTimes.add(highestGraceTime); // graceTimes == this.graceLoginTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
@@ -3025,6 +2837,8 @@
                                  OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME);
    }
    if (updateEntry)
    {
    LinkedHashSet<AttributeValue> values =
           new LinkedHashSet<AttributeValue>(graceTimes.size());
    for (Long l : graceTimes)
@@ -3037,6 +2851,10 @@
    ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
    attrList.add(a);
      userEntry.putAttribute(type, attrList);
    }
    else
    {
    LinkedHashSet<AttributeValue> addValues =
         new LinkedHashSet<AttributeValue>(1);
    addValues.add(new AttributeValue(type,
@@ -3044,12 +2862,6 @@
    Attribute addAttr = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME,
                                      addValues);
    if (updateEntry)
    {
      userEntry.putAttribute(type, attrList);
    }
    else
    {
      modifications.add(new Modification(ModificationType.ADD, addAttr, true));
    }
  }
@@ -3074,7 +2886,7 @@
    {
      return;
    }
    graceTimes.clear();
    graceTimes.clear(); // graceTimes == this.graceLoginTimes
    AttributeType type =
         DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC);
@@ -3109,90 +2921,61 @@
    List<Attribute> attrList =
         userEntry.getAttribute(passwordPolicy.getPasswordAttribute());
    if (attrList != null)
    if (attrList == null)
    {
      if (passwordPolicy.usesAuthPasswordSyntax())
      {
      return clearPasswords;
    }
        for (Attribute a : attrList)
        {
      boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
          for (AttributeValue v : a.getValues())
          {
            try
            {
              StringBuilder[] pwComponents =
          StringBuilder[] pwComponents;
          if (usesAuthPasswordSyntax)
          {
            pwComponents =
                   AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
              PasswordStorageScheme scheme =
                   DirectoryServer.getAuthPasswordStorageScheme(
                                        pwComponents[0].toString());
              if (scheme == null)
              {
                if (debug)
                {
                  debugWarning("User entry %s contains an " +
                                 "authPassword with scheme %s " +
                                 "that is not defined in the " +
                                 "server.", userDNString, pwComponents[0]);
                }
                continue;
              }
              else if (scheme.isReversible())
              {
                ByteString clearValue =
                     scheme.getAuthPasswordPlaintextValue(
                          pwComponents[1].toString(),
                          pwComponents[2].toString());
                clearPasswords.add(clearValue);
              }
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              if (debug)
              {
                debugWarning(
                    "Cannot get clear authPassword " +
                        "value for user %s: %s",
                    userDNString, e);
              }
            }
          }
        }
      }
      else
      {
        for (Attribute a : attrList)
        {
          for (AttributeValue v : a.getValues())
          {
            try
            {
              String[] pwComponents =
            String[] userPwComponents =
                   UserPasswordSyntax.decodeUserPassword(v.getStringValue());
              PasswordStorageScheme scheme =
                   DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
            pwComponents = new StringBuilder[userPwComponents.length];
            for (int i = 0; i < userPwComponents.length; ++i)
            {
              pwComponents[i] = new StringBuilder(userPwComponents[i]);
            }
          }
          String schemeName = pwComponents[0].toString();
          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                    : DirectoryServer.getPasswordStorageScheme(schemeName);
              if (scheme == null)
              {
                if (debug)
                {
                  debugWarning(
                      "User entry %s contains a password " +
                                 "with scheme %s that is not " +
                                 "defined in the server.",
                             userDNString, pwComponents[0]);
              debugWarning("User entry %s contains a password with scheme %s " +
                   "that is not defined in the server.",
                           userDNString, schemeName);
                }
                continue;
              }
              else if (scheme.isReversible())
          if (scheme.isReversible())
              {
                ByteString clearValue =
                     scheme.getPlaintextValue(
                          new ASN1OctetString(pwComponents[1]));
            ByteString clearValue = (usesAuthPasswordSyntax)
                         ? scheme.getAuthPasswordPlaintextValue(
                               pwComponents[1].toString(),
                               pwComponents[2].toString())
                         : scheme.getPlaintextValue(
                               new ASN1OctetString(pwComponents[1].toString()));
                clearPasswords.add(clearValue);
              }
            }
@@ -3205,11 +2988,8 @@
              if (debug)
              {
                debugWarning(
                    "Cannot get clear password value for " +
                        "user %s: %s", userDNString, e);
              }
            }
            debugWarning("Cannot get clear password value foruser %s: %s",
                         userDNString, e);
          }
        }
      }
@@ -3249,94 +3029,54 @@
      return false;
    }
    if (passwordPolicy.usesAuthPasswordSyntax())
    {
      for (Attribute a : attrList)
      {
      boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
        for (AttributeValue v : a.getValues())
        {
          try
          {
            StringBuilder[] pwComponents =
          StringBuilder[] pwComponents;
          if (usesAuthPasswordSyntax)
          {
            pwComponents =
                 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
            PasswordStorageScheme scheme =
                 DirectoryServer.getAuthPasswordStorageScheme(
                                      pwComponents[0].toString());
            if (scheme == null)
            {
              if (debug)
              {
                debugWarning(
                    "User entry %s contains a password with scheme %s " +
                                 "that is not defined in the server.",
                             userDNString, pwComponents[0]);
              }
              continue;
            }
            if (scheme.authPasswordMatches(password, pwComponents[1].toString(),
                                           pwComponents[2].toString()))
            {
              if (debug)
              {
                if (debugEnabled())
                {
                  debugInfo("Returning true for user %s because the provided " +
                      "password matches a value encoded with scheme %s",
                            userDNString, pwComponents[0]);
                }
              }
              return true;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, e);
            }
            if (debug)
            {
              debugError(
                  "An error occurred while attempting to process a " +
                      "password value for user %s: %s",
                  userDNString, stackTraceToSingleLineString(e));
            }
          }
        }
      }
    }
    else
    {
      for (Attribute a : attrList)
      {
        for (AttributeValue v : a.getValues())
        {
          try
          {
            String[] pwComponents =
            String[] userPwComponents =
                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
            PasswordStorageScheme scheme =
                 DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
            pwComponents = new StringBuilder[userPwComponents.length];
            for (int i = 0; i < userPwComponents.length; ++i)
            {
              pwComponents[i] = new StringBuilder(userPwComponents[i]);
            }
          }
          String schemeName = pwComponents[0].toString();
          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
                     ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                     : DirectoryServer.getPasswordStorageScheme(schemeName);
            if (scheme == null)
            {
              if (debug)
              {
                debugWarning(
                    "User entry %s contains a password with scheme %s " +
              debugWarning("User entry %s contains a password with scheme %s " +
                                 "that is not defined in the server.",
                             userDNString, pwComponents[0]);
                           userDNString, schemeName);
              }
              continue;
            }
            if (scheme.passwordMatches(password,
                                       new ASN1OctetString(pwComponents[1])))
          boolean passwordMatches = (usesAuthPasswordSyntax)
                     ? scheme.authPasswordMatches(password,
                                                  pwComponents[1].toString(),
                                                  pwComponents[2].toString())
                     : scheme.passwordMatches(password,
                               new ASN1OctetString(pwComponents[1].toString()));
          if (passwordMatches)
            {
              if (debug)
              {
@@ -3344,7 +3084,7 @@
                {
                  debugInfo("Returning true for user %s because the provided " +
                      "password matches a value encoded with scheme %s",
                            userDNString, pwComponents[0]);
                          userDNString, schemeName);
                }
              }
@@ -3360,15 +3100,13 @@
            if (debug)
            {
              debugError(
                  "An error occurred while attempting to process a " +
            debugWarning("An error occurred while attempting to process a " +
                      "password value for user %s: %s",
                  userDNString, stackTraceToSingleLineString(e));
            }
          }
        }
      }
    }
    // If we've gotten here, then we couldn't find a match.
    if (debug)
@@ -3553,8 +3291,8 @@
    LinkedHashSet<AttributeValue> updatedValues =
         new LinkedHashSet<AttributeValue>();
    if (passwordPolicy.usesAuthPasswordSyntax())
    {
    boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax();
      for (Attribute a : attrList)
      {
        Iterator<AttributeValue> iterator = a.getValues().iterator();
@@ -3564,27 +3302,46 @@
          try
          {
            StringBuilder[] pwComponents =
          StringBuilder[] pwComponents;
          if (usesAuthPasswordSyntax)
          {
            pwComponents =
                 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue());
          }
          else
          {
            String[] userPwComponents =
                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
            pwComponents = new StringBuilder[userPwComponents.length];
            for (int i = 0; i < userPwComponents.length; ++i)
            {
              pwComponents[i] = new StringBuilder(userPwComponents[i]);
            }
          }
            String schemeName = pwComponents[0].toString();
            PasswordStorageScheme scheme =
                 DirectoryServer.getAuthPasswordStorageScheme(schemeName);
          PasswordStorageScheme scheme = (usesAuthPasswordSyntax)
                    ? DirectoryServer.getAuthPasswordStorageScheme(schemeName)
                    : DirectoryServer.getPasswordStorageScheme(schemeName);
            if (scheme == null)
            {
              if (debug)
              {
                debugWarning(
                    "Skipping password value for user %s because the " +
                                 "associated storage scheme %s is not " +
                                 "configured for use.",
              debugWarning("Skipping password value for user %s because the " +
                   "associated storage scheme %s is not configured for use.",
                    userDNString, schemeName);
              }
              continue;
            }
            if (scheme.authPasswordMatches(password, pwComponents[1].toString(),
                                           pwComponents[2].toString()))
          boolean passwordMatches = (usesAuthPasswordSyntax)
                     ? scheme.authPasswordMatches(password,
                                                  pwComponents[1].toString(),
                                                  pwComponents[2].toString())
                     : scheme.passwordMatches(password,
                               new ASN1OctetString(pwComponents[1].toString()));
          if (passwordMatches)
            {
              if (passwordPolicy.isDefaultStorageScheme(schemeName))
              {
@@ -3598,7 +3355,7 @@
                  if (debugEnabled())
                  {
                    debugInfo("Marking password with scheme %s for removal " +
                        "from user entry %s", pwComponents[0], userDNString);
                      "from user entry %s.", schemeName, userDNString);
                  }
                }
@@ -3620,8 +3377,7 @@
            if (debug)
            {
              debugWarning(
                  "Skipping password value for user %s because an " +
            debugWarning("Skipping password value for user %s because an " +
                      "error occurred while attempting to decode it " +
                      "based on the user password syntax: %s",
                  userDNString, stackTraceToSingleLineString(e));
@@ -3640,9 +3396,10 @@
                "encoded using deprecated schemes.", userDNString);
          }
        }
      return;
      }
      else
      {
        LinkedHashSet<AttributeValue> addedValues = new
             LinkedHashSet<AttributeValue>();
        for (PasswordStorageScheme s :
@@ -3653,7 +3410,9 @@
          {
            try
            {
              ByteString encodedPassword = s.encodeAuthPassword(password);
          ByteString encodedPassword = (usesAuthPasswordSyntax)
                                       ? s.encodeAuthPassword(password)
                                       : s.encodePasswordWithScheme(password);
              AttributeValue v = new AttributeValue(type, encodedPassword);
              addedValues.add(v);
              updatedValues.add(v);
@@ -3667,8 +3426,7 @@
              if (debug)
              {
                debugWarning(
                    "Unable to encode password for user %s using " +
            debugWarning("Unable to encode password for user %s using " +
                        "default scheme %s: %s",
                    userDNString, s.getStorageSchemeName(),
                    stackTraceToSingleLineString(e));
@@ -3681,41 +3439,32 @@
        {
          if (debug)
          {
            debugWarning(
                "Not updating user entry %s because removing " +
                             "deprecated schemes would leave the user " +
                             "without a password.", userDNString);
        debugWarning("Not updating user entry %s because removing " +
             "deprecated schemes would leave the user without a password.",
                     userDNString);
          }
          return;
        }
    if (updateEntry)
    {
      ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
      newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
      userEntry.putAttribute(type, newList);
    }
        else
        {
          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
          if (! updateEntry)
          {
            modifications.add(new Modification(ModificationType.DELETE, a,
                                               true));
          }
      modifications.add(new Modification(ModificationType.DELETE, a, true));
          if (! addedValues.isEmpty())
          {
            Attribute a2 = new Attribute(type, type.getNameOrOID(),
                                         addedValues);
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.ADD, a2,
                                                 true));
        Attribute a2 = new Attribute(type, type.getNameOrOID(), addedValues);
        modifications.add(new Modification(ModificationType.ADD, a2, true));
            }
          }
          ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
          newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
          if (updateEntry)
          {
            userEntry.putAttribute(type, newList);
          }
          if (debug)
          {
            if (debugEnabled())
@@ -3726,184 +3475,6 @@
            }
          }
        }
      }
    }
    else
    {
      for (Attribute a : attrList)
      {
        Iterator<AttributeValue> iterator = a.getValues().iterator();
        while (iterator.hasNext())
        {
          AttributeValue v = iterator.next();
          try
          {
            String[] pwComponents =
                 UserPasswordSyntax.decodeUserPassword(v.getStringValue());
            PasswordStorageScheme scheme =
                 DirectoryServer.getPasswordStorageScheme(pwComponents[0]);
            if (scheme == null)
            {
              if (debug)
              {
                debugWarning(
                    "Skipping password value for user %s because the " +
                                 "associated storage scheme %s is not " +
                                 "configured for use.",
                             userDNString, pwComponents[0]);
              }
              continue;
            }
            if (scheme.passwordMatches(password,
                                       new ASN1OctetString(pwComponents[1])))
            {
              if (passwordPolicy.isDefaultStorageScheme(pwComponents[0]))
              {
                existingDefaultSchemes.add(pwComponents[0]);
                updatedValues.add(v);
              }
              else if (passwordPolicy.isDeprecatedStorageScheme(
                                           pwComponents[0]))
              {
                if (debug)
                {
                  if (debugEnabled())
                  {
                    debugInfo("Marking password with scheme %s for removal " +
                        "from user entry %s", pwComponents[0], userDNString);
                  }
                }
                iterator.remove();
                removedValues.add(v);
              }
              else
              {
                updatedValues.add(v);
              }
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, e);
            }
            if (debug)
            {
              debugWarning(
                  "Skipping password value for user %s because an error " +
                      "occurred while attempting to decode it based on " +
                      "the user password syntax: %s",
                  userDNString, stackTraceToSingleLineString(e));
            }
          }
        }
      }
      if (removedValues.isEmpty())
      {
        if (debug)
        {
          if (debugEnabled())
          {
            debugInfo("User entry %s does not have any password values " +
                "encoded using deprecated schemes.", userDNString);
          }
        }
      }
      else
      {
        LinkedHashSet<AttributeValue> addedValues = new
             LinkedHashSet<AttributeValue>();
        for (PasswordStorageScheme s :
             passwordPolicy.getDefaultStorageSchemes())
        {
          if (! existingDefaultSchemes.contains(
                     toLowerCase(s.getStorageSchemeName())))
          {
            try
            {
              ByteString encodedPassword = s.encodePasswordWithScheme(password);
              AttributeValue v = new AttributeValue(type, encodedPassword);
              addedValues.add(v);
              updatedValues.add(v);
            }
            catch (Exception e)
            {
              if (debugEnabled())
              {
                debugCaught(DebugLogLevel.ERROR, e);
              }
              if (debug)
              {
                debugWarning(
                    "Unable to encode password for user %s using " +
                        "default scheme %s: %s",
                    userDNString, s.getStorageSchemeName(),
                    stackTraceToSingleLineString(e));
              }
            }
          }
        }
        if (updatedValues.isEmpty())
        {
          if (debug)
          {
            debugWarning(
                "Not updating user entry %s because removing " +
                             "deprecated schemes would leave the user " +
                             "without a password.", userDNString);
          }
          return;
        }
        else
        {
          Attribute a = new Attribute(type, type.getNameOrOID(), removedValues);
          if (! updateEntry)
          {
            modifications.add(new Modification(ModificationType.DELETE, a,
                                               true));
          }
          if (! addedValues.isEmpty())
          {
            Attribute a2 = new Attribute(type, type.getNameOrOID(),
                                         addedValues);
            if (! updateEntry)
            {
              modifications.add(new Modification(ModificationType.ADD, a2,
                                                 true));
            }
          }
          ArrayList<Attribute> newList = new ArrayList<Attribute>(1);
          newList.add(new Attribute(type, type.getNameOrOID(), updatedValues));
          if (updateEntry)
          {
            userEntry.putAttribute(type, newList);
          }
          if (debug)
          {
            if (debugEnabled())
            {
              debugInfo("Updating user entry %sto replace password values " +
                  "encoded with deprecated schemes with values encoded " +
                  "with the default schemes.", userDNString);
            }
          }
        }
      }
    }
  }
@@ -3924,10 +3495,9 @@
    {
      if (debug)
      {
        debugWarning(
            "Unable to generate a new password for user %s " +
                         "because no password generator has been " +
                         "defined in the associated password policy.",
        debugWarning("Unable to generate a new password for user %s because " +
             "no password generator has been defined in the associated " +
             "password policy.",
                     userDNString);
      }
@@ -3993,6 +3563,21 @@
  /**
   * Retrieves the set of modifications that correspond to changes made in
   * password policy processing that may need to be applied to the user entry.
   *
   * @return  The set of modifications that correspond to changes made in
   *          password policy processing that may need to be applied to the user
   *          entry.
   */
  public LinkedList<Modification> getModifications()
  {
    return modifications;
  }
  /**
   * Performs an internal modification to update the user's entry, if necessary.
   * This will do nothing if no modifications are required.
   *
@@ -4033,4 +3618,3 @@
    }
  }
}
opendj-sdk/opends/src/server/org/opends/server/extensions/PasswordModifyExtendedOperation.java
@@ -1101,7 +1101,6 @@
      // Clear any record of grace logins, auth failures, and expiration
      // warnings.
      pwPolicyState.clearAuthFailureTimes();
      pwPolicyState.clearFailureLockout();
      pwPolicyState.clearGraceLoginTimes();
      pwPolicyState.clearWarnedTime();