From af1b4bead731b2dc8f25e4db507afab0428054d0 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Tue, 20 Sep 2011 11:29:12 +0000
Subject: [PATCH] Issue OPENDJ-262: Implement pass through authentication (PTA)

---
 opendj-sdk/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java                            |  295 +++++++++++++++++++-
 opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java                                 |  241 ----------------
 opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java                            |   21 +
 opendj-sdk/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java   |    8 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java |  141 ++++++----
 opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java    |    4 
 opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java                                |   40 +-
 opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java                            |   21 +
 opendj-sdk/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java                     |   12 
 9 files changed, 442 insertions(+), 341 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
index b71ae6c..7b2973e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
+++ b/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;
 }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java b/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
index dd4103a..f7e83d0 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV1Control.java
+++ b/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() ||
diff --git a/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java b/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
index 079bceb..0a916a8 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/controls/ProxiedAuthV2Control.java
+++ b/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() ||
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java b/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
index 868a183..47cabf7 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/PasswordPolicyState.java
+++ b/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)
       {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java b/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
index f2e46de..ef5b606 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/core/SearchOperationBasis.java
+++ b/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)
       {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index 304f2a8..4583814 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/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);
       }
 
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java b/opendj-sdk/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
index aa06ad0..c835838 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/extensions/PlainSASLMechanismHandler.java
+++ b/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);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java b/opendj-sdk/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
index 7866523..7fce649 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/workflowelement/localbackend/LocalBackendBindOperation.java
+++ b/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())
         {
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java
index c7fcb9c..7fcdece 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/api/AuthenticationPolicyTestCase.java
+++ b/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());
     }
   }
+
 }

--
Gitblit v1.10.0