From e695f124cf06c758d6d853ebbb773b07d0b2449f Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 31 Aug 2011 20:17:51 +0000
Subject: [PATCH] OPENDJ-262: Implement pass through authentication (PTA)
---
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java | 1070 ++++++++++++++++++++++++++++++++++++++++++++++
opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java | 288 +++++++++--
2 files changed, 1,294 insertions(+), 64 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index 583ee85..a84ea63 100644
--- a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -29,6 +29,7 @@
+import java.io.Closeable;
import java.util.List;
import org.opends.messages.Message;
@@ -51,52 +52,100 @@
{
/**
- * LDAP PTA policy state implementation.
+ * An LDAP connection which will be used in order to search for or
+ * authenticate users.
*/
- private static final class StateImpl extends AuthenticationPolicyState
+ static interface Connection extends Closeable
{
- private final PolicyImpl policy;
-
-
-
- private StateImpl(PolicyImpl policy)
- {
- this.policy = policy;
- }
+ /**
+ * Closes this connection.
+ */
+ @Override
+ void close();
/**
- * {@inheritDoc}
+ * Returns the name of the user whose entry matches the provided search
+ * criteria.
+ *
+ * @param baseDN
+ * The search base DN.
+ * @param scope
+ * The search scope.
+ * @param filter
+ * The search filter.
+ * @return The name of the user whose entry matches the provided search
+ * criteria, or {@code null} if no matching user entry was found.
+ * @throws DirectoryException
+ * If the search returned more than one entry, or if the search
+ * failed unexpectedly.
*/
- public boolean passwordMatches(ByteString password)
- throws DirectoryException
- {
- // TODO: perform PTA here.
- return false;
- }
+ ByteString search(DN baseDN, SearchScope scope, SearchFilter filter)
+ throws DirectoryException;
/**
- * {@inheritDoc}
+ * Performs a simple bind for the user.
+ *
+ * @param username
+ * The user name (usually a bind DN).
+ * @param password
+ * The user's password.
+ * @throws DirectoryException
+ * If the credentials were invalid, or the authentication failed
+ * unexpectedly.
*/
- public AuthenticationPolicy getAuthenticationPolicy()
- {
- return policy;
- }
+ void simpleBind(ByteString username, ByteString password)
+ throws DirectoryException;
+ }
+ /**
+ * An interface for obtaining connections: users of this interface will obtain
+ * a connection, perform a single operation (search or bind), and then close
+ * it.
+ */
+ static interface ConnectionFactory
+ {
/**
- * {@inheritDoc}
+ * Returns a connection which can be used in order to search for or
+ * authenticate users.
+ *
+ * @return The connection.
+ * @throws DirectoryException
+ * If an unexpected error occurred while attempting to obtain a
+ * connection.
*/
- public void finalizeStateAfterBind() throws DirectoryException
- {
- // TODO: cache password if needed.
- }
+ Connection getConnection() throws DirectoryException;
+ }
+
+
+ /**
+ * An interface for obtaining a connection factory for LDAP connections to a
+ * named LDAP server.
+ */
+ static interface LDAPConnectionFactoryProvider
+ {
+ /**
+ * Returns a connection factory which can be used for obtaining connections
+ * to the specified LDAP server.
+ *
+ * @param host
+ * The LDAP server host name.
+ * @param port
+ * The LDAP server port.
+ * @param options
+ * The LDAP connection options.
+ * @return A connection factory which can be used for obtaining connections
+ * to the specified LDAP server.
+ */
+ ConnectionFactory getLDAPConnectionFactory(String host, int port,
+ LDAPPassThroughAuthenticationPolicyCfg options);
}
@@ -108,36 +157,25 @@
ConfigurationChangeListener<LDAPPassThroughAuthenticationPolicyCfg>
{
- private PolicyImpl(LDAPPassThroughAuthenticationPolicyCfg configuration)
+ // Current configuration.
+ private LDAPPassThroughAuthenticationPolicyCfg configuration;
+
+
+
+ private PolicyImpl(
+ final LDAPPassThroughAuthenticationPolicyCfg configuration)
{
this.configuration = configuration;
}
- // Current configuration.
- private LDAPPassThroughAuthenticationPolicyCfg configuration;
-
-
-
/**
* {@inheritDoc}
*/
- public boolean isConfigurationChangeAcceptable(
- LDAPPassThroughAuthenticationPolicyCfg configuration,
- List<Message> unacceptableReasons)
- {
- // The configuration is always valid.
- return true;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
+ @Override
public ConfigChangeResult applyConfigurationChange(
- LDAPPassThroughAuthenticationPolicyCfg configuration)
+ final LDAPPassThroughAuthenticationPolicyCfg configuration)
{
// TODO: close and re-open connections if servers have changed.
this.configuration = configuration;
@@ -149,6 +187,30 @@
/**
* {@inheritDoc}
*/
+ @Override
+ public AuthenticationPolicyState createAuthenticationPolicyState(
+ final Entry userEntry, final long time) throws DirectoryException
+ {
+ return new StateImpl(this);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void finalizeAuthenticationPolicy()
+ {
+ // TODO: release pooled connections, etc.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public DN getDN()
{
return configuration.dn();
@@ -159,20 +221,13 @@
/**
* {@inheritDoc}
*/
- public AuthenticationPolicyState createAuthenticationPolicyState(
- Entry userEntry, long time) throws DirectoryException
+ @Override
+ public boolean isConfigurationChangeAcceptable(
+ final LDAPPassThroughAuthenticationPolicyCfg configuration,
+ final List<Message> unacceptableReasons)
{
- return new StateImpl(this);
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- public void finalizeAuthenticationPolicy()
- {
- // TODO: release pooled connections, etc.
+ // The configuration is always valid.
+ return true;
}
}
@@ -180,13 +235,117 @@
/**
+ * LDAP PTA policy state implementation.
+ */
+ private static final class StateImpl extends AuthenticationPolicyState
+ {
+
+ private final PolicyImpl policy;
+
+
+
+ private StateImpl(final PolicyImpl policy)
+ {
+ this.policy = policy;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void finalizeStateAfterBind() throws DirectoryException
+ {
+ // TODO: cache password if needed.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AuthenticationPolicy getAuthenticationPolicy()
+ {
+ return policy;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean passwordMatches(final ByteString password)
+ throws DirectoryException
+ {
+ // TODO: perform PTA here.
+ return false;
+ }
+
+ }
+
+
+
+ // The provider which should be used by policies to create LDAP connections.
+ private final LDAPConnectionFactoryProvider provider;
+
+ /**
+ * The default LDAP connection factory provider.
+ */
+ private static final LDAPConnectionFactoryProvider DEFAULT_PROVIDER =
+ new LDAPConnectionFactoryProvider()
+ {
+
+ @Override
+ public ConnectionFactory getLDAPConnectionFactory(final String host,
+ final int port, final LDAPPassThroughAuthenticationPolicyCfg options)
+ {
+ // TODO: not yet implemented.
+ return null;
+ }
+
+ };
+
+
+
+ /**
+ * Public default constructor used by the admin framework. This will use the
+ * default LDAP connection factory provider.
+ */
+ public LDAPPassThroughAuthenticationPolicyFactory()
+ {
+ this(DEFAULT_PROVIDER);
+ }
+
+
+
+ /**
+ * Package private constructor allowing unit tests to provide mock connection
+ * implementations.
+ *
+ * @param provider
+ * The LDAP connection factory provider implementation which LDAP PTA
+ * authentication policies will use.
+ */
+ LDAPPassThroughAuthenticationPolicyFactory(
+ final LDAPConnectionFactoryProvider provider)
+ {
+ this.provider = provider;
+ }
+
+
+
+ /**
* {@inheritDoc}
*/
+ @Override
public AuthenticationPolicy createAuthenticationPolicy(
- LDAPPassThroughAuthenticationPolicyCfg configuration)
+ final LDAPPassThroughAuthenticationPolicyCfg configuration)
throws ConfigException, InitializationException
{
- PolicyImpl policy = new PolicyImpl(configuration);
+ final PolicyImpl policy = new PolicyImpl(configuration);
configuration.addLDAPPassThroughChangeListener(policy);
return policy;
}
@@ -196,9 +355,10 @@
/**
* {@inheritDoc}
*/
+ @Override
public boolean isConfigurationAcceptable(
- LDAPPassThroughAuthenticationPolicyCfg configuration,
- List<Message> unacceptableReasons)
+ final LDAPPassThroughAuthenticationPolicyCfg configuration,
+ final List<Message> unacceptableReasons)
{
// The configuration is always valid.
return true;
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
new file mode 100644
index 0000000..9265c3a
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java
@@ -0,0 +1,1070 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Copyright 2011 ForgeRock AS.
+ */
+package org.opends.server.extensions;
+
+
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.LDAPPassThroughAuthenticationPolicyCfgDefn.MappingPolicy;
+import org.opends.server.admin.std.server.AuthenticationPolicyCfg;
+import org.opends.server.admin.std.server.LDAPPassThroughAuthenticationPolicyCfg;
+import org.opends.server.api.AuthenticationPolicy;
+import org.opends.server.api.AuthenticationPolicyState;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.extensions.LDAPPassThroughAuthenticationPolicyFactory.Connection;
+import org.opends.server.extensions.LDAPPassThroughAuthenticationPolicyFactory.ConnectionFactory;
+import org.opends.server.types.*;
+import org.opends.server.util.StaticUtils;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+
+/**
+ * Test LDAP authentication mappingPolicy implementation.
+ */
+public class LDAPPassThroughAuthenticationPolicyTestCase extends
+ ExtensionsTestCase
+{
+
+ static class CloseEvent extends Event<Void>
+ {
+ private final GetConnectionEvent getConnectionEvent;
+
+
+
+ CloseEvent(GetConnectionEvent getConnectionEvent)
+ {
+ this.getConnectionEvent = getConnectionEvent;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean matchesEvent(Event<?> event)
+ {
+ if (event instanceof CloseEvent)
+ {
+ CloseEvent closeEvent = (CloseEvent) event;
+ return getConnectionEvent.matchesEvent(closeEvent.getConnectionEvent);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("CloseEvent(");
+ builder.append(getConnectionEvent);
+ builder.append(')');
+ return builder;
+ }
+
+ }
+
+
+
+ static abstract class Event<T>
+ {
+
+ public final boolean equals(Object obj)
+ {
+ if (obj instanceof Event<?>)
+ {
+ return matchesEvent((Event<?>) obj);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ public final String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ return toString(builder).toString();
+ }
+
+
+
+ T getResult()
+ {
+ return null;
+ }
+
+
+
+ abstract boolean matchesEvent(Event<?> event);
+
+
+
+ abstract StringBuilder toString(StringBuilder builder);
+ }
+
+
+
+ static class GetConnectionEvent extends Event<DirectoryException>
+ {
+ private final GetLDAPConnectionFactoryEvent fevent;
+ private final ResultCode resultCode;
+
+
+
+ GetConnectionEvent(GetLDAPConnectionFactoryEvent fevent)
+ {
+ this(fevent, ResultCode.SUCCESS);
+ }
+
+
+
+ GetConnectionEvent(GetLDAPConnectionFactoryEvent fevent,
+ ResultCode resultCode)
+ {
+ this.fevent = fevent;
+ this.resultCode = resultCode;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ DirectoryException getResult()
+ {
+ if (resultCode != ResultCode.SUCCESS)
+ {
+ return new DirectoryException(resultCode,
+ resultCode.getResultCodeName());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean matchesEvent(Event<?> event)
+ {
+ if (event instanceof GetConnectionEvent)
+ {
+ GetConnectionEvent getConnectionEvent = (GetConnectionEvent) event;
+ return fevent.matchesEvent(getConnectionEvent.fevent);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("GetConnectionEvent(");
+ builder.append(fevent);
+ builder.append(')');
+ return builder;
+ }
+
+ }
+
+
+
+ static class GetLDAPConnectionFactoryEvent extends Event<Void>
+ {
+ private final String hostPort;
+ private final LDAPPassThroughAuthenticationPolicyCfg options;
+
+
+
+ GetLDAPConnectionFactoryEvent(String hostPort,
+ LDAPPassThroughAuthenticationPolicyCfg options)
+ {
+ this.hostPort = hostPort;
+ this.options = options;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean matchesEvent(Event<?> event)
+ {
+ if (event instanceof GetLDAPConnectionFactoryEvent)
+ {
+ GetLDAPConnectionFactoryEvent providerEvent = (GetLDAPConnectionFactoryEvent) event;
+
+ return hostPort.equals(providerEvent.hostPort)
+ && options == providerEvent.options;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("GetLDAPConnectionFactoryEvent(");
+ builder.append(hostPort);
+ builder.append(", ");
+ builder.append(options);
+ builder.append(')');
+ return builder;
+ }
+
+ }
+
+
+
+ static final class MockConnection implements
+ LDAPPassThroughAuthenticationPolicyFactory.Connection
+ {
+
+ private final GetConnectionEvent getConnectionEvent;
+ private final MockProvider mockProvider;
+
+
+
+ MockConnection(MockProvider mockProvider,
+ GetConnectionEvent getConnectionEvent)
+ {
+ this.mockProvider = mockProvider;
+ this.getConnectionEvent = getConnectionEvent;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close()
+ {
+ CloseEvent event = new CloseEvent(getConnectionEvent);
+ mockProvider.assertNextEventExpected(event);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ByteString search(DN baseDN, SearchScope scope, SearchFilter filter)
+ throws DirectoryException
+ {
+ SearchEvent event = new SearchEvent(getConnectionEvent, baseDN, scope,
+ filter);
+ Object result = mockProvider.assertNextEventExpected(event);
+ if (result instanceof ByteString)
+ {
+ return (ByteString) result;
+ }
+ else
+ {
+ throw (DirectoryException) result;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void simpleBind(ByteString username, ByteString password)
+ throws DirectoryException
+ {
+ SimpleBindEvent event = new SimpleBindEvent(getConnectionEvent,
+ username.toString(), password.toString());
+ DirectoryException e = mockProvider.assertNextEventExpected(event);
+ if (e != null) throw e;
+ }
+
+ }
+
+
+
+ static final class MockFactory implements
+ LDAPPassThroughAuthenticationPolicyFactory.ConnectionFactory
+ {
+ private final GetLDAPConnectionFactoryEvent getLDAPConnectionFactoryEvent;
+ private final MockProvider mockProvider;
+
+
+
+ MockFactory(MockProvider mockProvider,
+ GetLDAPConnectionFactoryEvent providerEvent)
+ {
+ this.mockProvider = mockProvider;
+ this.getLDAPConnectionFactoryEvent = providerEvent;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Connection getConnection() throws DirectoryException
+ {
+ GetConnectionEvent event = new GetConnectionEvent(
+ getLDAPConnectionFactoryEvent);
+
+ DirectoryException e = mockProvider.assertNextEventExpected(event);
+ if (e != null)
+ {
+ throw e;
+ }
+ else
+ {
+ return new MockConnection(mockProvider, event);
+ }
+ }
+
+ }
+
+
+
+ final class MockPolicyCfg implements LDAPPassThroughAuthenticationPolicyCfg
+ {
+ private final SortedSet<DN> baseDNs = new TreeSet<DN>();
+ private final SortedSet<AttributeType> mappedAttributes = new TreeSet<AttributeType>();
+ private MappingPolicy mappingPolicy = MappingPolicy.UNMAPPED;
+ private final SortedSet<String> primaryServers = new TreeSet<String>();
+ private final SortedSet<String> secondaryServers = new TreeSet<String>();
+
+
+
+ public void addChangeListener(
+ ConfigurationChangeListener<AuthenticationPolicyCfg> listener)
+ {
+ // Do nothing.
+ }
+
+
+
+ public void addLDAPPassThroughChangeListener(
+ ConfigurationChangeListener<LDAPPassThroughAuthenticationPolicyCfg> listener)
+ {
+ // Do nothing.
+ }
+
+
+
+ public Class<? extends LDAPPassThroughAuthenticationPolicyCfg> configurationClass()
+ {
+ return LDAPPassThroughAuthenticationPolicyCfg.class;
+ }
+
+
+
+ public DN dn()
+ {
+ return policyDN;
+ }
+
+
+
+ public long getConnectionTimeout()
+ {
+ return 3000;
+ }
+
+
+
+ public String getJavaClass()
+ {
+ return LDAPPassThroughAuthenticationPolicyFactory.class.getName();
+ }
+
+
+
+ public SortedSet<AttributeType> getMappedAttribute()
+ {
+ return mappedAttributes;
+ }
+
+
+
+ public SortedSet<DN> getMappedSearchBaseDN()
+ {
+ return baseDNs;
+ }
+
+
+
+ public DN getMappedSearchBindDN()
+ {
+ return searchBindDN;
+ }
+
+
+
+ public String getMappedSearchBindPassword()
+ {
+ return "searchPassword";
+ }
+
+
+
+ public MappingPolicy getMappingPolicy()
+ {
+ return mappingPolicy;
+ }
+
+
+
+ public SortedSet<String> getPrimaryRemoteLDAPServer()
+ {
+ return primaryServers;
+ }
+
+
+
+ public SortedSet<String> getSecondaryRemoteLDAPServer()
+ {
+ return secondaryServers;
+ }
+
+
+
+ public SortedSet<String> getSSLCipherSuite()
+ {
+ return new TreeSet<String>();
+ }
+
+
+
+ public SortedSet<String> getSSLProtocol()
+ {
+ return new TreeSet<String>();
+ }
+
+
+
+ public String getTrustManagerProvider()
+ {
+ return "ignored";
+ }
+
+
+
+ public DN getTrustManagerProviderDN()
+ {
+ return trustManagerDN;
+ }
+
+
+
+ public boolean isUseSSL()
+ {
+ return false;
+ }
+
+
+
+ public boolean isUseTCPKeepAlive()
+ {
+ return false;
+ }
+
+
+
+ public boolean isUseTCPNoDelay()
+ {
+ return false;
+ }
+
+
+
+ public void removeChangeListener(
+ ConfigurationChangeListener<AuthenticationPolicyCfg> listener)
+ {
+ // Do nothing.
+ }
+
+
+
+ public void removeLDAPPassThroughChangeListener(
+ ConfigurationChangeListener<LDAPPassThroughAuthenticationPolicyCfg> listener)
+ {
+ // Do nothing.
+ }
+
+
+
+ MockPolicyCfg withBaseDN(String baseDN)
+ {
+ try
+ {
+ baseDNs.add(DN.decode(baseDN));
+ }
+ catch (DirectoryException e)
+ {
+ throw new RuntimeException(e);
+ }
+ return this;
+ }
+
+
+
+ MockPolicyCfg withMappedAttribute(String atype)
+ {
+ mappedAttributes.add(DirectoryServer.getAttributeType(
+ StaticUtils.toLowerCase(atype), true));
+ return this;
+ }
+
+
+
+ MockPolicyCfg withMappingPolicy(MappingPolicy policy)
+ {
+ this.mappingPolicy = policy;
+ return this;
+ }
+
+
+
+ MockPolicyCfg withPrimaryServer(String hostPort)
+ {
+ primaryServers.add(hostPort);
+ return this;
+ }
+
+
+
+ MockPolicyCfg withSecondaryServer(String hostPort)
+ {
+ secondaryServers.add(hostPort);
+ return this;
+ }
+ }
+
+
+
+ static final class MockProvider implements
+ LDAPPassThroughAuthenticationPolicyFactory.LDAPConnectionFactoryProvider
+ {
+
+ private final Queue<Event<?>> expectedEvents = new LinkedList<Event<?>>();
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public ConnectionFactory getLDAPConnectionFactory(String host, int port,
+ LDAPPassThroughAuthenticationPolicyCfg options)
+ {
+ GetLDAPConnectionFactoryEvent event = new GetLDAPConnectionFactoryEvent(
+ host + ":" + port, options);
+ assertNextEventExpected(event);
+ return new MockFactory(this, event);
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ <T> T assertNextEventExpected(Event<T> actualEvent)
+ {
+ Event<?> expectedEvent = expectedEvents.poll();
+ if (expectedEvent == null)
+ {
+ fail("Unexpected event: " + actualEvent);
+ }
+ else
+ {
+ assertEquals(actualEvent, expectedEvent);
+ }
+ return ((Event<T>) expectedEvent).getResult();
+ }
+
+
+
+ MockProvider withExpectedEvent(Event<?> expectedEvent)
+ {
+ expectedEvents.add(expectedEvent);
+ return this;
+ }
+ }
+
+
+
+ static class SearchEvent extends Event<Object>
+ {
+ private final DN baseDN;
+ private final GetConnectionEvent cevent;
+ private final SearchFilter filter;
+ private final ResultCode resultCode;
+ private final SearchScope scope;
+ private final ByteString username;
+
+
+
+ SearchEvent(GetConnectionEvent cevent, DN baseDN, SearchScope scope,
+ SearchFilter filter)
+ {
+ this(cevent, baseDN, scope, filter, null, ResultCode.SUCCESS);
+ }
+
+
+
+ SearchEvent(GetConnectionEvent cevent, DN baseDN, SearchScope scope,
+ SearchFilter filter, ByteString username)
+ {
+ this(cevent, baseDN, scope, filter, username, ResultCode.SUCCESS);
+ }
+
+
+
+ SearchEvent(GetConnectionEvent cevent, DN baseDN, SearchScope scope,
+ SearchFilter filter, ResultCode resultCode)
+ {
+ this(cevent, baseDN, scope, filter, null, resultCode);
+ }
+
+
+
+ private SearchEvent(GetConnectionEvent cevent, DN baseDN,
+ SearchScope scope, SearchFilter filter, ByteString username,
+ ResultCode resultCode)
+ {
+ this.cevent = cevent;
+ this.baseDN = baseDN;
+ this.scope = scope;
+ this.filter = filter;
+ this.username = username;
+ this.resultCode = resultCode;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ Object getResult()
+ {
+ return resultCode == ResultCode.SUCCESS ? username
+ : new DirectoryException(resultCode, resultCode.getResultCodeName());
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean matchesEvent(Event<?> event)
+ {
+ if (event instanceof SearchEvent)
+ {
+ SearchEvent searchEvent = (SearchEvent) event;
+ return cevent.matchesEvent(searchEvent.cevent)
+ && baseDN.equals(searchEvent.baseDN)
+ && scope.equals(searchEvent.scope)
+ && filter.equals(searchEvent.filter);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("SearchEvent(");
+ builder.append(cevent);
+ builder.append(", ");
+ builder.append(baseDN);
+ builder.append(", ");
+ builder.append(scope);
+ builder.append(", ");
+ builder.append(filter);
+ builder.append(')');
+ return builder;
+ }
+
+ }
+
+
+
+ static class SimpleBindEvent extends Event<DirectoryException>
+ {
+ private final GetConnectionEvent cevent;
+ private final String password;
+ private final ResultCode resultCode;
+ private final String username;
+
+
+
+ SimpleBindEvent(GetConnectionEvent cevent, String username, String password)
+ {
+ this(cevent, username, password, ResultCode.SUCCESS);
+ }
+
+
+
+ SimpleBindEvent(GetConnectionEvent cevent, String username,
+ String password, ResultCode resultCode)
+ {
+ this.cevent = cevent;
+ this.username = username;
+ this.password = password;
+ this.resultCode = resultCode;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ DirectoryException getResult()
+ {
+ if (resultCode != ResultCode.SUCCESS)
+ {
+ return new DirectoryException(resultCode,
+ resultCode.getResultCodeName());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ boolean matchesEvent(Event<?> event)
+ {
+ if (event instanceof SimpleBindEvent)
+ {
+ SimpleBindEvent simpleBindEvent = (SimpleBindEvent) event;
+ return cevent.matchesEvent(simpleBindEvent.cevent)
+ && username.equals(simpleBindEvent.username)
+ && password.equals(simpleBindEvent.password);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ StringBuilder toString(StringBuilder builder)
+ {
+ builder.append("SimpleBindEvent(");
+ builder.append(cevent);
+ builder.append(", ");
+ builder.append(username);
+ builder.append(", ");
+ builder.append(password);
+ builder.append(')');
+ return builder;
+ }
+
+ }
+
+
+
+ private final String phost1 = "phost1:11";
+ private final String phost2 = "phost2:22";
+ private final String phost3 = "phost3:33";
+ private final String shost1 = "shost1:11";
+ private final String shost2 = "shost2:22";
+ private final String shost3 = "shost3:33";
+
+ private DN policyDN;
+ private final String policyDNString = "cn=test mappingPolicy,o=test";
+
+ private DN searchBindDN;
+ private final String searchBindDNString = "cn=search bind dn";
+
+ private DN trustManagerDN;
+ private final String trustManagerDNString = "cn=ignored";
+
+ private final String adDNString = "cn=ad user,o=ad";
+ private final String opendjDNString = "cn=test user,o=opendj";
+ private Entry userEntry;
+ private final String userPassword = "password";
+
+
+
+ /**
+ * Ensures that the Directory Server is running and creates a test backend
+ * containing a single test user.
+ *
+ * @throws Exception
+ * If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void beforeClass() throws Exception
+ {
+ TestCaseUtils.startServer();
+
+ policyDN = DN.decode(policyDNString);
+ trustManagerDN = DN.decode(trustManagerDNString);
+ searchBindDN = DN.decode(searchBindDNString);
+ userEntry = TestCaseUtils.makeEntry(
+ /* @formatter:off */
+ "dn: " + opendjDNString,
+ "objectClass: top",
+ "objectClass: person",
+ "sn: user",
+ "cn: test user",
+ "aduser: " + adDNString,
+ "uid: aduser"
+ /* @formatter:on */
+ );
+ }
+
+
+
+ /**
+ * Tests that initial connection errors are handled properly.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = false)
+ public void testInitialConnectionFailure() throws Exception
+ {
+ LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg().withPrimaryServer(
+ phost1);
+
+ GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ GetConnectionEvent ce = new GetConnectionEvent(fe, ResultCode.UNAVAILABLE);
+
+ MockProvider provider = new MockProvider().withExpectedEvent(fe)
+ .withExpectedEvent(ce);
+
+ LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+
+ AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+
+ assertEquals(state.getAuthenticationPolicy(), policy);
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (DirectoryException e)
+ {
+ assertEquals(e.getResultCode(), ResultCode.UNAVAILABLE);
+ }
+
+ state.finalizeStateAfterBind();
+ policy.finalizeAuthenticationPolicy();
+ }
+
+
+
+ /**
+ * Tests the unmapped policy where the connection fails during bind.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = false)
+ public void testUnmappedPolicyConnectionFailure() throws Exception
+ {
+ LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg().withPrimaryServer(
+ phost1);
+
+ GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ GetConnectionEvent ce = new GetConnectionEvent(fe);
+
+ MockProvider provider = new MockProvider()
+ .withExpectedEvent(fe)
+ .withExpectedEvent(ce)
+ .withExpectedEvent(
+ new SimpleBindEvent(ce, opendjDNString, userPassword,
+ ResultCode.UNAVAILABLE)).withExpectedEvent(new CloseEvent(ce));
+
+ LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+
+ AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+
+ assertEquals(state.getAuthenticationPolicy(), policy);
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (DirectoryException e)
+ {
+ assertEquals(e.getResultCode(), ResultCode.UNAVAILABLE);
+ }
+
+ state.finalizeStateAfterBind();
+ policy.finalizeAuthenticationPolicy();
+ }
+
+
+
+ /**
+ * Tests the unmapped policy with invalid credentials.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = false)
+ public void testUnmappedPolicyInvalidCredentials() throws Exception
+ {
+ LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg().withPrimaryServer(
+ phost1);
+
+ GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ GetConnectionEvent ce = new GetConnectionEvent(fe);
+
+ MockProvider provider = new MockProvider()
+ .withExpectedEvent(fe)
+ .withExpectedEvent(ce)
+ .withExpectedEvent(
+ new SimpleBindEvent(ce, opendjDNString, userPassword,
+ ResultCode.INVALID_CREDENTIALS))
+ .withExpectedEvent(new CloseEvent(ce));
+
+ LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+
+ AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+
+ assertEquals(state.getAuthenticationPolicy(), policy);
+ assertFalse(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ state.finalizeStateAfterBind();
+ policy.finalizeAuthenticationPolicy();
+ }
+
+
+
+ /**
+ * Tests the unmapped policy with valid credentials.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = false)
+ public void testUnmappedPolicyValidCredentials() throws Exception
+ {
+ LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg().withPrimaryServer(
+ phost1);
+
+ GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ GetConnectionEvent ce = new GetConnectionEvent(fe);
+
+ MockProvider provider = new MockProvider()
+ .withExpectedEvent(fe)
+ .withExpectedEvent(ce)
+ .withExpectedEvent(
+ new SimpleBindEvent(ce, opendjDNString, userPassword))
+ .withExpectedEvent(new CloseEvent(ce));
+
+ LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+
+ AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+ AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+
+ assertEquals(state.getAuthenticationPolicy(), policy);
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ state.finalizeStateAfterBind();
+ policy.finalizeAuthenticationPolicy();
+ }
+
+
+
+ private MockPolicyCfg mockCfg()
+ {
+ return new MockPolicyCfg();
+ }
+}
--
Gitblit v1.10.0