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

Matthew Swift
20.29.2011 af1b4bead731b2dc8f25e4db507afab0428054d0
opendj-sdk/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
@@ -29,9 +29,20 @@
import org.opends.server.types.ByteString;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.OP_ATTR_ACCOUNT_DISABLED;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.types.*;
@@ -43,6 +54,13 @@
public abstract class AuthenticationPolicyState
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Returns the authentication policy state for the user provided user. This
   * method is equivalent to the following:
   *
@@ -68,10 +86,10 @@
   *           policy for the user.
   * @see AuthenticationPolicy#forUser(Entry, boolean)
   */
  public final static AuthenticationPolicyState forUser(Entry userEntry,
      boolean useDefaultOnError) throws DirectoryException
  public final static AuthenticationPolicyState forUser(final Entry userEntry,
      final boolean useDefaultOnError) throws DirectoryException
  {
    AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
    final AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
        useDefaultOnError);
    return policy.createAuthenticationPolicyState(userEntry);
  }
@@ -79,37 +97,184 @@
  /**
   * Creates a new abstract authentication policy context.
   * A utility method which may be used by implementations in order to obtain
   * the value of the specified attribute from the provided entry as a boolean.
   *
   * @param entry
   *          The entry whose attribute is to be parsed as a boolean.
   * @param attributeType
   *          The attribute type whose value should be parsed as a boolean.
   * @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.
   */
  protected AuthenticationPolicyState()
  protected static final ConditionResult getBoolean(final Entry entry,
      final AttributeType attributeType) throws DirectoryException
  {
    // No implementation required.
    final List<Attribute> attrList = entry.getAttribute(attributeType);
    if (attrList != null)
    {
      for (final Attribute a : attrList)
      {
        if (a.isEmpty())
        {
          continue;
        }
        final String valueString = toLowerCase(a.iterator().next().getValue()
            .toString());
        if (valueString.equals("true") || valueString.equals("yes")
            || valueString.equals("on") || valueString.equals("1"))
        {
          if (debugEnabled())
          {
            TRACER.debugInfo("Attribute %s resolves to true for user entry "
                + "%s", attributeType.getNameOrOID(), entry.getDN().toString());
          }
          return ConditionResult.TRUE;
        }
        if (valueString.equals("false") || valueString.equals("no")
            || valueString.equals("off") || valueString.equals("0"))
        {
          if (debugEnabled())
          {
            TRACER.debugInfo("Attribute %s resolves to false for user "
                + "entry %s", attributeType.getNameOrOID(), entry.getDN()
                .toString());
          }
          return ConditionResult.FALSE;
        }
        if (debugEnabled())
        {
          TRACER.debugError("Unable to resolve value %s for attribute %s "
              + "in user entry %s as a Boolean.", valueString,
              attributeType.getNameOrOID(), entry.getDN().toString());
        }
        final Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN
            .get(valueString, attributeType.getNameOrOID(), entry.getDN()
                .toString());
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
            message);
      }
    }
    if (debugEnabled())
    {
      TRACER.debugInfo("Returning %s because attribute %s does not exist "
          + "in user entry %s", ConditionResult.UNDEFINED.toString(),
          attributeType.getNameOrOID(), entry.getDN().toString());
    }
    return ConditionResult.UNDEFINED;
  }
  /**
   * Returns {@code true} if the provided password value matches any of the
   * user's passwords.
   * A utility method which may be used by implementations in order to obtain
   * the value of the specified attribute from the provided entry as a time in
   * generalized time format.
   *
   * @param password
   *          The user-provided password to verify.
   * @return {@code true} if the provided password value matches any of the
   *         user's passwords.
   * @param entry
   *          The entry whose attribute is to be parsed as a boolean.
   * @param attributeType
   *          The attribute type whose value should be parsed as a generalized
   *          time value.
   * @return The requested time, or -1 if it could not be determined.
   * @throws DirectoryException
   *           If verification unexpectedly failed.
   *           If a problem occurs while attempting to decode the value as a
   *           generalized time.
   */
  public abstract boolean passwordMatches(ByteString password)
      throws DirectoryException;
  protected static final long getGeneralizedTime(final Entry entry,
      final AttributeType attributeType) throws DirectoryException
  {
    long timeValue = -1;
    final List<Attribute> attrList = entry.getAttribute(attributeType);
    if (attrList != null)
    {
      for (final Attribute a : attrList)
      {
        if (a.isEmpty())
        {
          continue;
        }
        final AttributeValue v = a.iterator().next();
        try
        {
          timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(v
              .getNormalizedValue());
        }
        catch (final Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
            TRACER.debugWarning("Unable to decode value %s for attribute %s "
                + "in user entry %s: %s", v.getValue().toString(),
                attributeType.getNameOrOID(), entry.getDN().toString(),
                stackTraceToSingleLineString(e));
          }
          final Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME
              .get(v.getValue().toString(), attributeType.getNameOrOID(), entry
                  .getDN().toString(), String.valueOf(e));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
              message, e);
        }
        break;
      }
    }
    if (timeValue == -1)
    {
      if (debugEnabled())
      {
        TRACER.debugInfo("Returning -1 because attribute %s does not "
            + "exist in user entry %s", attributeType.getNameOrOID(), entry
            .getDN().toString());
      }
    }
    // FIXME: else to be consistent...
    return timeValue;
  }
  /**
   * Returns the authentication policy associated with this state.
   *
   * @return The authentication policy associated with this state.
   * A boolean indicating whether or not the account associated with this
   * authentication state has been administratively disabled.
   */
  public abstract AuthenticationPolicy getAuthenticationPolicy();
  protected ConditionResult isDisabled = ConditionResult.UNDEFINED;
  /**
   * The user entry associated with this authentication policy state.
   */
  protected final Entry userEntry;
  /**
   * Creates a new abstract authentication policy context.
   *
   * @param userEntry
   *          The user's entry.
   */
  protected AuthenticationPolicyState(final Entry userEntry)
  {
    this.userEntry = userEntry;
  }
@@ -129,6 +294,76 @@
  /**
   * Returns the authentication policy associated with this state.
   *
   * @return The authentication policy associated with this state.
   */
  public abstract AuthenticationPolicy getAuthenticationPolicy();
  /**
   * Returns {@code true} if this authentication policy state is associated with
   * a user whose account has been administratively disabled.
   * <p>
   * The default implementation is use the value of the "ds-pwp-account-disable"
   * attribute in the user's entry.
   *
   * @return {@code true} if this authentication policy state is associated with
   *         a user whose account has been administratively disabled.
   */
  public boolean isDisabled()
  {
    final AttributeType type = DirectoryServer.getAttributeType(
        OP_ATTR_ACCOUNT_DISABLED, true);
    try
    {
      isDisabled = getBoolean(userEntry, type);
    }
    catch (final Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      isDisabled = ConditionResult.TRUE;
      if (debugEnabled())
      {
        TRACER.debugWarning("User %s is considered administratively "
            + "disabled because an error occurred while "
            + "attempting to make the determination: %s.", userEntry.getDN()
            .toString(), stackTraceToSingleLineString(e));
      }
      return true;
    }
    if (isDisabled == ConditionResult.UNDEFINED)
    {
      isDisabled = ConditionResult.FALSE;
      if (debugEnabled())
      {
        TRACER.debugInfo("User %s is not administratively disabled since "
            + "the attribute \"%s\" is not present in the entry.", userEntry
            .getDN().toString(), OP_ATTR_ACCOUNT_DISABLED);
      }
      return false;
    }
    if (debugEnabled())
    {
      TRACER.debugInfo("User %s %s administratively disabled.", userEntry
          .getDN().toString(), ((isDisabled == ConditionResult.TRUE) ? " is"
          : " is not"));
    }
    return isDisabled == ConditionResult.TRUE;
  }
  /**
   * Returns {@code true} if this authentication policy state is associated with
   * a password policy and the method {@link #getAuthenticationPolicy} will
   * return a {@code PasswordPolicy}.
@@ -140,4 +375,20 @@
  {
    return getAuthenticationPolicy().isPasswordPolicy();
  }
  /**
   * Returns {@code true} if the provided password value matches any of the
   * user's passwords.
   *
   * @param password
   *          The user-provided password to verify.
   * @return {@code true} if the provided password value matches any of the
   *         user's passwords.
   * @throws DirectoryException
   *           If verification unexpectedly failed.
   */
  public abstract boolean passwordMatches(ByteString password)
      throws DirectoryException;
}
opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
@@ -32,7 +32,7 @@
import java.util.concurrent.locks.Lock;
import java.io.IOException;
import org.opends.server.api.AuthenticationPolicy;
import org.opends.server.api.AuthenticationPolicyState;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.protocols.asn1.*;
@@ -325,13 +325,20 @@
      // FIXME -- We should provide some mechanism for enabling debug
      // processing.
      AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
          false);
      if (policy.isPasswordPolicy())
      AuthenticationPolicyState state = AuthenticationPolicyState.forUser(
          userEntry, false);
      if (state.isDisabled())
      {
        PasswordPolicyState pwpState = (PasswordPolicyState) policy
            .createAuthenticationPolicyState(userEntry);
        if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
        Message message = ERR_PROXYAUTH1_UNUSABLE_ACCOUNT.get(String
            .valueOf(userEntry.getDN()));
        throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
      }
      if (state.isPasswordPolicy())
      {
        PasswordPolicyState pwpState = (PasswordPolicyState) state;
        if (pwpState.isAccountExpired() ||
            pwpState.lockedDueToFailures() ||
            pwpState.lockedDueToIdleInterval() ||
            pwpState.lockedDueToMaximumResetAge() ||
opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
@@ -33,7 +33,7 @@
import java.util.concurrent.locks.Lock;
import java.io.IOException;
import org.opends.server.api.AuthenticationPolicy;
import org.opends.server.api.AuthenticationPolicyState;
import org.opends.server.api.IdentityMapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PasswordPolicyState;
@@ -333,13 +333,20 @@
  private void checkAccountIsUsable(Entry userEntry)
      throws DirectoryException
  {
    AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
        false);
    if (policy.isPasswordPolicy())
    AuthenticationPolicyState state = AuthenticationPolicyState.forUser(
        userEntry, false);
    if (state.isDisabled())
    {
      PasswordPolicyState pwpState = (PasswordPolicyState) policy
          .createAuthenticationPolicyState(userEntry);
      if (pwpState.isDisabled() || pwpState.isAccountExpired() ||
      Message message = ERR_PROXYAUTH2_UNUSABLE_ACCOUNT.get(String
          .valueOf(userEntry.getDN()));
      throw new DirectoryException(ResultCode.AUTHORIZATION_DENIED, message);
    }
    if (state.isPasswordPolicy())
    {
      PasswordPolicyState pwpState = (PasswordPolicyState) state;
      if (pwpState.isAccountExpired() ||
          pwpState.lockedDueToFailures() ||
          pwpState.lockedDueToIdleInterval() ||
          pwpState.lockedDueToMaximumResetAge() ||
opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
@@ -77,9 +77,6 @@
  // The user entry with which this state information is associated.
  private final Entry userEntry;
  // The string representation of the user's DN.
  private final String userDNString;
@@ -169,7 +166,7 @@
   */
  PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime)
  {
    this.userEntry   = userEntry;
    super(userEntry);
    this.currentTime = currentTime;
    this.userDNString     = userEntry.getDN().toString();
    this.passwordPolicy   = policy;
@@ -225,74 +222,6 @@
  /**
   * Retrieves the value of the specified attribute from the user's entry as a
   * time in generalized time format.
   *
   * @param  attributeType  The attribute type whose value should be parsed as a
   *                        generalized time value.
   *
   * @return  The requested time, or -1 if it could not be determined.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              decode the value as a generalized time.
   */
  private long getGeneralizedTime(AttributeType attributeType)
          throws DirectoryException
  {
    long timeValue = -1 ;
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        if (a.isEmpty()) continue;
        AttributeValue v = a.iterator().next();
        try
        {
          timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(
                          v.getNormalizedValue());
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
            TRACER.debugWarning("Unable to decode value %s for attribute %s " +
                "in user entry %s: %s",
                v.getValue().toString(), attributeType.getNameOrOID(),
                userDNString, stackTraceToSingleLineString(e));
          }
          Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.
              get(v.getValue().toString(), attributeType.getNameOrOID(),
                  userDNString, String.valueOf(e));
          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                       message, e);
        }
        break ;
      }
    }
    if (timeValue == -1)
    {
      if (debugEnabled())
      {
        TRACER.debugInfo("Returning -1 because attribute %s does not " +
            "exist in user entry %s",
            attributeType.getNameOrOID(), userDNString);
      }
    }
    // FIXME: else to be consistent...
    return timeValue;
  }
  /**
   * Retrieves the set of values of the specified attribute from the user's
   * entry in generalized time format.
   *
@@ -359,84 +288,6 @@
  /**
   * Retrieves the value of the specified attribute from the user's entry as a
   * Boolean.
   *
   * @param  attributeType  The attribute type whose value should be parsed as a
   *                        Boolean.
   *
   * @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 ConditionResult getBoolean(AttributeType attributeType)
          throws DirectoryException
  {
    List<Attribute> attrList = userEntry.getAttribute(attributeType);
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        if (a.isEmpty()) continue;
        String valueString
             = toLowerCase(a.iterator().next().getValue().toString());
        if (valueString.equals("true") || valueString.equals("yes") ||
            valueString.equals("on") || valueString.equals("1"))
        {
          if (debugEnabled())
          {
            TRACER.debugInfo("Attribute %s resolves to true for user entry " +
                "%s", attributeType.getNameOrOID(), userDNString);
          }
          return ConditionResult.TRUE;
        }
        if (valueString.equals("false") || valueString.equals("no") ||
                 valueString.equals("off") || valueString.equals("0"))
        {
          if (debugEnabled())
          {
            TRACER.debugInfo("Attribute %s resolves to false for user " +
                "entry %s", attributeType.getNameOrOID(), userDNString);
          }
          return ConditionResult.FALSE;
        }
        if(debugEnabled())
        {
          TRACER.debugError("Unable to resolve value %s for attribute %s " +
              "in user entry %s as a Boolean.",
              valueString, attributeType.getNameOrOID(),
              userDNString);
        }
        Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN.get(
            valueString, attributeType.getNameOrOID(), userDNString);
        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
            message);
      }
    }
    if (debugEnabled())
    {
      TRACER.debugInfo("Returning %s because attribute %s does not exist " +
          "in user entry %s",
          ConditionResult.UNDEFINED.toString(),
          attributeType.getNameOrOID(), userDNString);
    }
    return ConditionResult.UNDEFINED;
  }
  /**
   * {@inheritDoc}
   */
  public PasswordPolicy getAuthenticationPolicy()
@@ -461,7 +312,7 @@
      try
      {
        passwordChangedTime = getGeneralizedTime(type);
        passwordChangedTime = getGeneralizedTime(userEntry, type);
      }
      catch (DirectoryException e)
      {
@@ -481,7 +332,7 @@
            OP_ATTR_CREATE_TIMESTAMP_LC, true);
        try
        {
          passwordChangedTime = getGeneralizedTime(createTimeType);
          passwordChangedTime = getGeneralizedTime(userEntry, createTimeType);
        }
        catch (DirectoryException e)
        {
@@ -626,7 +477,7 @@
         DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
    try
    {
      passwordChangedTime = getGeneralizedTime(createTimeType);
      passwordChangedTime = getGeneralizedTime(userEntry, createTimeType);
      if (passwordChangedTime < 0)
      {
        passwordChangedTime = 0;
@@ -640,81 +491,13 @@
  /**
   * Indicates whether the user account has been administratively disabled.
   *
   * @return  <CODE>true</CODE> if the user account has been administratively
   *          disabled, or <CODE>false</CODE> otherwise.
   */
  public boolean isDisabled()
  {
    if (isDisabled != ConditionResult.UNDEFINED)
    {
      if (debugEnabled())
      {
        TRACER.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
    {
      isDisabled = getBoolean(type);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      isDisabled = ConditionResult.TRUE;
      if (debugEnabled())
      {
          TRACER.debugWarning("User %s is considered administratively " +
              "disabled because an error occurred while attempting to make " +
              "the determination: %s.",
                       userDNString, stackTraceToSingleLineString(e));
      }
      return true;
    }
    if (isDisabled == ConditionResult.UNDEFINED)
    {
      isDisabled = ConditionResult.FALSE;
      if (debugEnabled())
      {
        TRACER.debugInfo("User %s is not administratively disabled since " +
            "the attribute \"%s\" is not present in the entry.",
            userDNString, OP_ATTR_ACCOUNT_DISABLED);
      }
      return false;
    }
    if (debugEnabled())
    {
      TRACER.debugInfo("User %s %s administratively disabled.",
          userDNString,
          ((isDisabled == ConditionResult.TRUE) ? " is" : " is not"));
    }
    return isDisabled == ConditionResult.TRUE;
  }
  /**
   * Updates the user entry to indicate whether user account has been
   * administratively disabled.
   *
   * @param  isDisabled  Indicates whether the user account has been
   *                     administratively disabled.
   * @param isDisabled
   *          Indicates whether the user account has been administratively
   *          disabled.
   */
  public void setDisabled(boolean isDisabled)
  {
@@ -775,7 +558,7 @@
    try
    {
      accountExpirationTime = getGeneralizedTime(type);
      accountExpirationTime = getGeneralizedTime(userEntry, type);
     }
    catch (Exception e)
    {
@@ -1216,7 +999,7 @@
    try
    {
      failureLockedTime = getGeneralizedTime(type);
      failureLockedTime = getGeneralizedTime(userEntry, type);
    }
    catch (Exception e)
    {
@@ -1811,7 +1594,7 @@
    try
    {
      mustChangePassword = getBoolean(type);
      mustChangePassword = getBoolean(userEntry, type);
    }
    catch (Exception e)
    {
@@ -2335,7 +2118,7 @@
    try
    {
      requiredChangeTime = getGeneralizedTime(type);
      requiredChangeTime = getGeneralizedTime(userEntry, type);
    }
    catch (Exception e)
    {
@@ -2449,7 +2232,7 @@
           DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true);
      try
      {
        warnedTime = getGeneralizedTime(type);
        warnedTime = getGeneralizedTime(userEntry, type);
      }
      catch (Exception e)
      {
opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
@@ -37,7 +37,7 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opends.server.api.AuthenticationPolicy;
import org.opends.server.api.AuthenticationPolicyState;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult;
import org.opends.server.controls.AccountUsableResponseControl;
@@ -645,15 +645,19 @@
    // create it now.
    if (isIncludeUsableControl())
    {
      if (controls == null)
      {
        controls = new ArrayList<Control>(1);
      }
      try
      {
        // FIXME -- Need a way to enable PWP debugging.
        AuthenticationPolicy policy = AuthenticationPolicy
            .forUser(entry, false);
        if (policy.isPasswordPolicy())
        AuthenticationPolicyState state = AuthenticationPolicyState.forUser(
            entry, false);
        if (state.isPasswordPolicy())
        {
          PasswordPolicyState pwpState = (PasswordPolicyState) policy
              .createAuthenticationPolicyState(entry);
          PasswordPolicyState pwpState = (PasswordPolicyState) state;
          boolean isInactive = pwpState.isDisabled()
              || pwpState.isAccountExpired();
@@ -667,12 +671,6 @@
          {
            int secondsBeforeUnlock = pwpState.getSecondsUntilUnlock();
            int remainingGraceLogins = pwpState.getGraceLoginsRemaining();
            if (controls == null)
            {
              controls = new ArrayList<Control>(1);
            }
            controls
                .add(new AccountUsableResponseControl(isInactive, isReset,
                    isExpired, remainingGraceLogins, isLocked,
@@ -680,16 +678,24 @@
          }
          else
          {
            if (controls == null)
            {
              controls = new ArrayList<Control>(1);
            }
            int secondsBeforeExpiration = pwpState.getSecondsUntilExpiration();
            controls.add(new AccountUsableResponseControl(
                secondsBeforeExpiration));
          }
        }
        else
        {
          // Another type of authentication policy (e.g. PTA).
          if (state.isDisabled())
          {
            controls.add(new AccountUsableResponseControl(false, false, false,
                -1, true, -1));
          }
          else
          {
            controls.add(new AccountUsableResponseControl(-1));
          }
        }
      }
      catch (Exception e)
      {
opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -73,7 +73,6 @@
  // TODO: handle password policy response controls? AD?
  // TODO: custom aliveness pings
  // TODO: manage account lockout
  // TODO: cache password
  /**
@@ -1555,14 +1554,13 @@
    private final class StateImpl extends AuthenticationPolicyState
    {
      private final Entry userEntry;
      private ByteString cachedPassword = null;
      private StateImpl(final Entry userEntry)
      {
        this.userEntry = userEntry;
        super(userEntry);
      }
opendj-sdk/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
@@ -29,6 +29,7 @@
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
@@ -504,6 +505,17 @@
      // the user's entry when the bind completes.
      AuthenticationPolicyState authState = AuthenticationPolicyState.forUser(
          userEntry, false);
      if (authState.isDisabled())
      {
        // Check to see if the user is administratively disabled or locked.
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
        Message message = ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(String
            .valueOf(userEntry.getDN()));
        bindOperation.setAuthFailureReason(message);
        return;
      }
      if (!authState.passwordMatches(ByteString.valueOf(password)))
      {
        bindOperation.setResultCode(ResultCode.INVALID_CREDENTIALS);
opendj-sdk/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
@@ -675,6 +675,14 @@
      }
      else
      {
        // Check to see if the user is administratively disabled or locked.
        if (authPolicyState.isDisabled())
        {
          throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
              ERR_BIND_OPERATION_ACCOUNT_DISABLED.get(String.valueOf(userEntry
                  .getDN())));
        }
        // Invoke pre-operation plugins.
        if (!invokePreOpPlugins())
        {
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java
@@ -30,6 +30,7 @@
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import org.opends.server.TestCaseUtils;
@@ -38,6 +39,7 @@
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.types.*;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -54,6 +56,8 @@
   */
  private final class MockPolicy extends AuthenticationPolicy
  {
    private final boolean isDisabled;
    private boolean isPolicyFinalized = false;
    private boolean isStateFinalized = false;
@@ -93,9 +97,9 @@
     *
     * @return The password which was tested.
     */
    public String getMatchedPassword()
    public ByteString getMatchedPassword()
    {
      return matchedPassword.toString();
      return matchedPassword;
    }
@@ -105,10 +109,13 @@
     *
     * @param matches
     *          The result to always return from {@code passwordMatches}.
     * @param isDisabled
     *          The result to return from {@code isDisabled}.
     */
    public MockPolicy(boolean matches)
    public MockPolicy(boolean matches, boolean isDisabled)
    {
      this.matches = matches;
      this.isDisabled = isDisabled;
    }
@@ -129,7 +136,7 @@
    public AuthenticationPolicyState createAuthenticationPolicyState(
        Entry userEntry, long time) throws DirectoryException
    {
      return new AuthenticationPolicyState()
      return new AuthenticationPolicyState(userEntry)
      {
        /**
@@ -147,6 +154,16 @@
        /**
         * {@inheritDoc}
         */
        public boolean isDisabled()
        {
          return MockPolicy.this.isDisabled;
        }
        /**
         * {@inheritDoc}
         */
        public void finalizeStateAfterBind() throws DirectoryException
        {
          isStateFinalized = true;
@@ -202,29 +219,22 @@
  /**
   * Test simple authentication where password validation succeeds.
   * Returns test data for the simple/sasl tests.
   *
   * @throws Exception
   *           If an unexpected exception occurred.
   * @return Test data for the simple/sasl tests.
   */
  @Test
  public void testSimpleBindAllowed() throws Exception
  @DataProvider
  public Object[][] testBindData()
  {
    testSimpleBind(true);
  }
  /**
   * Test simple authentication where password validation fails.
   *
   * @throws Exception
   *           If an unexpected exception occurred.
   */
  @Test
  public void testSimpleBindRefused() throws Exception
  {
    testSimpleBind(false);
    // @formatter:off
    return new Object[][] {
        /* password matches, account is disabled */
        { false, false },
        { false,  true },
        {  true, false },
        {  true,  true },
    };
    // @formatter:on
  }
@@ -232,34 +242,18 @@
  /**
   * Test simple authentication where password validation succeeds.
   *
   * @param matches
   *          The result to always return from {@code passwordMatches}.
   * @param isDisabled
   *          The result to return from {@code isDisabled}.
   * @throws Exception
   *           If an unexpected exception occurred.
   */
  @Test
  public void testSASLPLAINBindAllowed() throws Exception
  @Test(dataProvider = "testBindData")
  public void testSimpleBind(boolean matches, boolean isDisabled)
      throws Exception
  {
    testSASLPLAINBind(true);
  }
  /**
   * Test simple authentication where password validation fails.
   *
   * @throws Exception
   *           If an unexpected exception occurred.
   */
  @Test
  public void testSASLPLAINBindRefused() throws Exception
  {
    testSASLPLAINBind(false);
  }
  private void testSimpleBind(boolean allow) throws Exception
  {
    MockPolicy policy = new MockPolicy(allow);
    MockPolicy policy = new MockPolicy(matches, isDisabled);
    DirectoryServer.registerAuthenticationPolicy(policyDN, policy);
    try
    {
@@ -287,13 +281,24 @@
      BindOperation bind = conn.processSimpleBind(userDNString, "password");
      // Check authentication result.
      assertEquals(bind.getResultCode(), allow ? ResultCode.SUCCESS
          : ResultCode.INVALID_CREDENTIALS);
      assertEquals(bind.getResultCode(),
          matches & !isDisabled ? ResultCode.SUCCESS
              : ResultCode.INVALID_CREDENTIALS);
      // Verify interaction with the policy/state.
      assertTrue(policy.isStateFinalized());
      assertFalse(policy.isPolicyFinalized());
      assertEquals(policy.getMatchedPassword(), "password");
      if (!isDisabled)
      {
        assertEquals(policy.getMatchedPassword().toString(), "password");
      }
      else
      {
        // If the account is disabled then the password should not have been
        // checked. This is important because we want to avoid potentially
        // expensive password fetches (e.g. PTA).
        assertNull(policy.getMatchedPassword());
      }
    }
    finally
    {
@@ -304,9 +309,21 @@
  private void testSASLPLAINBind(boolean allow) throws Exception
  /**
   * Test simple authentication where password validation succeeds.
   *
   * @param matches
   *          The result to always return from {@code passwordMatches}.
   * @param isDisabled
   *          The result to return from {@code isDisabled}.
   * @throws Exception
   *           If an unexpected exception occurred.
   */
  @Test(dataProvider = "testBindData")
  public void testSASLPLAINBind(boolean matches, boolean isDisabled)
      throws Exception
  {
    MockPolicy policy = new MockPolicy(allow);
    MockPolicy policy = new MockPolicy(matches, isDisabled);
    DirectoryServer.registerAuthenticationPolicy(policyDN, policy);
    try
    {
@@ -342,13 +359,24 @@
          credentials.toByteString());
      // Check authentication result.
      assertEquals(bind.getResultCode(), allow ? ResultCode.SUCCESS
          : ResultCode.INVALID_CREDENTIALS);
      assertEquals(bind.getResultCode(),
          matches & !isDisabled ? ResultCode.SUCCESS
              : ResultCode.INVALID_CREDENTIALS);
      // Verify interaction with the policy/state.
      assertTrue(policy.isStateFinalized());
      assertFalse(policy.isPolicyFinalized());
      assertEquals(policy.getMatchedPassword(), "password");
      if (!isDisabled)
      {
        assertEquals(policy.getMatchedPassword().toString(), "password");
      }
      else
      {
        // If the account is disabled then the password should not have been
        // checked. This is important because we want to avoid potentially
        // expensive password fetches (e.g. PTA).
        assertNull(policy.getMatchedPassword());
      }
    }
    finally
    {
@@ -356,4 +384,5 @@
      assertTrue(policy.isPolicyFinalized());
    }
  }
}