From 87a32e534959a6ffaf12c6d69ce98197f7bee596 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)

---
 opends/src/server/org/opends/server/api/AuthenticationPolicyState.java |  295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 273 insertions(+), 22 deletions(-)

diff --git a/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java b/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
index b71ae6c..7b2973e 100644
--- a/opends/src/server/org/opends/server/api/AuthenticationPolicyState.java
+++ b/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;
 }

--
Gitblit v1.10.0