From 94af7fde4bf372b77568f47e066925073748cb1f Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 29 Sep 2011 09:50:04 +0000
Subject: [PATCH] Issue OPENDJ-262: Implement pass through authentication (PTA)
---
opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java | 230 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 217 insertions(+), 13 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index 8539712..500f1e1 100644
--- a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -30,6 +30,7 @@
import static org.opends.messages.ExtensionMessages.*;
+import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
@@ -54,13 +55,18 @@
import org.opends.server.api.*;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
import org.opends.server.loggers.debug.DebugLogger;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1Exception;
+import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.*;
+import org.opends.server.schema.GeneralizedTimeSyntax;
+import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.tools.LDAPReader;
import org.opends.server.tools.LDAPWriter;
import org.opends.server.types.*;
+import org.opends.server.util.TimeThread;
@@ -73,7 +79,6 @@
// TODO: handle password policy response controls? AD?
// TODO: custom aliveness pings
- // TODO: cache password
// TODO: improve debug logging and error messages.
/**
@@ -1527,12 +1532,23 @@
/**
- * Returns the current time in milli-seconds in order to perform cached
- * password expiration checks.
+ * Returns the current time in order to perform cached password expiration
+ * checks. The returned string will be formatted as a a generalized time
+ * string
*
- * @return The current time in milli-seconds.
+ * @return The current time.
*/
- long getCurrentTimeMillis();
+ String getCurrentTime();
+
+
+
+ /**
+ * Returns the current time in order to perform cached password expiration
+ * checks.
+ *
+ * @return The current time in MS.
+ */
+ long getCurrentTimeMS();
}
@@ -1616,13 +1632,22 @@
private final class StateImpl extends AuthenticationPolicyState
{
- private ByteString cachedPassword = null;
+ private final AttributeType cachedPasswordAttribute;
+ private final AttributeType cachedPasswordTimeAttribute;
+
+ private ByteString newCachedPassword = null;
+
private StateImpl(final Entry userEntry)
{
super(userEntry);
+
+ this.cachedPasswordAttribute = DirectoryServer.getAttributeType(
+ OP_ATTR_PTAPOLICY_CACHED_PASSWORD, true);
+ this.cachedPasswordTimeAttribute = DirectoryServer.getAttributeType(
+ OP_ATTR_PTAPOLICY_CACHED_PASSWORD_TIME, true);
}
@@ -1633,10 +1658,52 @@
@Override
public void finalizeStateAfterBind() throws DirectoryException
{
- if (cachedPassword != null)
+ sharedLock.lock();
+ try
{
- // TODO: persist cached password if needed.
- cachedPassword = null;
+ if (cfg.isUsePasswordCaching() && newCachedPassword != null)
+ {
+ // Update the user's entry to contain the cached password and
+ // time stamp.
+ ByteString encodedPassword = pwdStorageScheme
+ .encodePasswordWithScheme(newCachedPassword);
+
+ List<RawModification> modifications =
+ new ArrayList<RawModification>(2);
+ modifications.add(RawModification.create(ModificationType.REPLACE,
+ OP_ATTR_PTAPOLICY_CACHED_PASSWORD, encodedPassword));
+ modifications.add(RawModification.create(ModificationType.REPLACE,
+ OP_ATTR_PTAPOLICY_CACHED_PASSWORD_TIME,
+ provider.getCurrentTime()));
+
+ InternalClientConnection conn = InternalClientConnection
+ .getRootConnection();
+ ModifyOperation internalModify = conn.processModify(userEntry
+ .getDN().toString(), modifications);
+
+ ResultCode resultCode = internalModify.getResultCode();
+ if (resultCode != ResultCode.SUCCESS)
+ {
+ // The modification failed for some reason. This should not
+ // prevent the bind from succeeded since we are only updating
+ // cache data. However, the performance of the server may be
+ // impacted, so log a debug warning message.
+ if (debugEnabled())
+ {
+ TRACER.debugWarning(
+ "An error occurred while trying to update the LDAP PTA "
+ + "cached password for user %s: %s", userEntry.getDN()
+ .toString(), String.valueOf(internalModify
+ .getErrorMessage()));
+ }
+ }
+
+ newCachedPassword = null;
+ }
+ }
+ finally
+ {
+ sharedLock.unlock();
}
}
@@ -1663,8 +1730,13 @@
sharedLock.lock();
try
{
- // First of determine the user name to use when binding to the remote
- // directory.
+ // First check the cached password if enabled and available.
+ if (passwordMatchesCachedPassword(password))
+ {
+ return true;
+ }
+
+ // The cache lookup failed, so perform full PTA.
ByteString username = null;
switch (cfg.getMappingPolicy())
@@ -1820,6 +1892,11 @@
{
connection = bindFactory.getConnection();
connection.simpleBind(username, password);
+
+ // The password matched, so cache it, it will be stored in the
+ // user's entry when the state is finalized and only if caching is
+ // enabled.
+ newCachedPassword = password;
return true;
}
catch (final DirectoryException e)
@@ -1852,6 +1929,118 @@
sharedLock.unlock();
}
}
+
+
+
+ private boolean passwordMatchesCachedPassword(ByteString password)
+ {
+ if (!cfg.isUsePasswordCaching())
+ {
+ return false;
+ }
+
+ // First determine if the cached password time is present and valid.
+ boolean foundValidCachedPasswordTime = false;
+
+ List<Attribute> cptlist = userEntry
+ .getAttribute(cachedPasswordTimeAttribute);
+ if (cptlist != null && !cptlist.isEmpty())
+ {
+ foundCachedPasswordTime:
+ {
+ for (Attribute attribute : cptlist)
+ {
+ // Ignore any attributes with options.
+ if (!attribute.hasOptions())
+ {
+ for (AttributeValue value : attribute)
+ {
+ try
+ {
+ long cachedPasswordTime = GeneralizedTimeSyntax
+ .decodeGeneralizedTimeValue(value.getNormalizedValue());
+ long currentTime = provider.getCurrentTimeMS();
+ long expiryTime = cachedPasswordTime
+ + (cfg.getCachedPasswordTTL() * 1000);
+ foundValidCachedPasswordTime = (expiryTime > currentTime);
+ }
+ catch (DirectoryException e)
+ {
+ // Fall-through and give up immediately.
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+
+ break foundCachedPasswordTime;
+ }
+ }
+ }
+ }
+ }
+
+ if (!foundValidCachedPasswordTime)
+ {
+ // The cached password time was not found or it has expired, so give
+ // up immediately.
+ return false;
+ }
+
+ // Next determine if there is a cached password.
+ ByteString cachedPassword = null;
+
+ List<Attribute> cplist = userEntry
+ .getAttribute(cachedPasswordAttribute);
+ if (cplist != null && !cplist.isEmpty())
+ {
+ foundCachedPassword:
+ {
+ for (Attribute attribute : cplist)
+ {
+ // Ignore any attributes with options.
+ if (!attribute.hasOptions())
+ {
+ for (AttributeValue value : attribute)
+ {
+ cachedPassword = value.getValue();
+ break foundCachedPassword;
+ }
+ }
+ }
+ }
+ }
+
+ if (cachedPassword == null)
+ {
+ // The cached password was not found, so give up immediately.
+ return false;
+ }
+
+ // Decode the password and match it according to its storage scheme.
+ try
+ {
+ String[] userPwComponents = UserPasswordSyntax
+ .decodeUserPassword(cachedPassword.toString());
+ PasswordStorageScheme<?> scheme = DirectoryServer
+ .getPasswordStorageScheme(userPwComponents[0]);
+ if (scheme != null)
+ {
+ return scheme.passwordMatches(password,
+ ByteString.valueOf(userPwComponents[1]));
+ }
+ }
+ catch (DirectoryException e)
+ {
+ // Unable to decode the cached password, so give up.
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+
+ return false;
+ }
}
@@ -1867,6 +2056,8 @@
private ConnectionFactory searchFactory = null;
private ConnectionFactory bindFactory = null;
+ private PasswordStorageScheme<?> pwdStorageScheme = null;
+
private PolicyImpl(
@@ -2062,6 +2253,12 @@
bindFactory = new FailoverLoadBalancer(primaryBindLoadBalancer,
secondaryBindLoadBalancer, scheduler);
}
+
+ if (cfg.isUsePasswordCaching())
+ {
+ pwdStorageScheme = DirectoryServer.getPasswordStorageScheme(cfg
+ .getCachedPasswordStorageSchemeDN());
+ }
}
@@ -2137,9 +2334,16 @@
- public long getCurrentTimeMillis()
+ public String getCurrentTime()
{
- return System.currentTimeMillis();
+ return TimeThread.getGMTTime();
+ }
+
+
+
+ public long getCurrentTimeMS()
+ {
+ return TimeThread.getTime();
}
};
--
Gitblit v1.10.0