From 6992706b3b92b889db4b3b603107a6ee9fd09f17 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 16 Sep 2011 17:36:51 +0000
Subject: [PATCH] Issue OPENDJ-262: Implement pass through authentication (PTA)
---
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java | 1581 +++++++++++++++++++++++++------------------
opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java | 555 +++++++++++---
2 files changed, 1,346 insertions(+), 790 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index 6283310..1b32e4c 100644
--- a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -37,8 +37,7 @@
import java.io.IOException;
import java.net.*;
import java.util.*;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Semaphore;
+import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
@@ -49,10 +48,7 @@
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.server.*;
-import org.opends.server.api.AuthenticationPolicy;
-import org.opends.server.api.AuthenticationPolicyFactory;
-import org.opends.server.api.AuthenticationPolicyState;
-import org.opends.server.api.TrustManagerProvider;
+import org.opends.server.api.*;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugLogger;
@@ -72,11 +68,353 @@
AuthenticationPolicyFactory<LDAPPassThroughAuthenticationPolicyCfg>
{
- // TODO: retry operations transparently until all connections exhausted.
// TODO: handle password policy response controls? AD?
- // TODO: periodically ping offline servers in order to detect when they come
- // back.
// TODO: provide alternative cfg for search password.
+ // TODO: custom aliveness pings
+ // TODO: manage account lockout
+ // TODO: cache password
+
+ /**
+ * A simplistic load-balancer connection factory implementation using
+ * approximately round-robin balancing.
+ */
+ static abstract class AbstractLoadBalancer implements ConnectionFactory,
+ Runnable
+ {
+ /**
+ * A connection which automatically retries operations on other servers.
+ */
+ private final class FailoverConnection implements Connection
+ {
+ private Connection connection;
+ private MonitoredConnectionFactory factory;
+ private final int startIndex;
+ private int nextIndex;
+
+
+
+ private FailoverConnection(final int startIndex)
+ throws DirectoryException
+ {
+ this.startIndex = nextIndex = startIndex;
+
+ DirectoryException lastException = null;
+ do
+ {
+ factory = factories[nextIndex];
+ if (factory.isAvailable())
+ {
+ try
+ {
+ if (factory.isAvailable)
+ {
+ connection = factory.getConnection();
+ incrementNextIndex();
+ return;
+ }
+ }
+ catch (final DirectoryException e)
+ {
+ // Ignore this error and try the next factory.
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ lastException = e;
+ }
+ }
+ incrementNextIndex();
+ }
+ while (nextIndex != startIndex);
+
+ // All the factories have been tried so give up and throw the exception.
+ throw lastException;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ connection.close();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString search(final DN baseDN, final SearchScope scope,
+ final SearchFilter filter) throws DirectoryException
+ {
+ for (;;)
+ {
+ try
+ {
+ return connection.search(baseDN, scope, filter);
+ }
+ catch (final DirectoryException e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ handleDirectoryException(e);
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void simpleBind(final ByteString username,
+ final ByteString password) throws DirectoryException
+ {
+ for (;;)
+ {
+ try
+ {
+ connection.simpleBind(username, password);
+ return;
+ }
+ catch (final DirectoryException e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ handleDirectoryException(e);
+ }
+ }
+ }
+
+
+
+ private void handleDirectoryException(final DirectoryException e)
+ throws DirectoryException
+ {
+ // If the error does not indicate that the connection has failed, then
+ // pass this back to the caller.
+ if (!isFatalResultCode(e.getResultCode()))
+ {
+ throw e;
+ }
+
+ // The associated server is unavailable, so close the connection and
+ // try the next connection factory.
+ connection.close();
+ factory.isAvailable = false;
+
+ while (nextIndex != startIndex)
+ {
+ factory = factories[nextIndex];
+ if (factory.isAvailable())
+ {
+ try
+ {
+ if (factory.isAvailable)
+ {
+ connection = factory.getConnection();
+ incrementNextIndex();
+ return;
+ }
+ }
+ catch (final DirectoryException de)
+ {
+ // Ignore this error and try the next factory.
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, de);
+ }
+ }
+ }
+ incrementNextIndex();
+ }
+
+ // All the factories have been tried so give up and throw the exception.
+ throw e;
+ }
+
+
+
+ private void incrementNextIndex()
+ {
+ // Try the next index.
+ if (++nextIndex == maxIndex)
+ {
+ nextIndex = 0;
+ }
+ }
+
+ }
+
+
+
+ /**
+ * A connection factory which caches its online/offline state in order to
+ * avoid unnecessary connection attempts when it is known to be offline.
+ */
+ private final class MonitoredConnectionFactory implements ConnectionFactory
+ {
+ private final ConnectionFactory factory;
+ private volatile boolean isAvailable = true;
+
+
+
+ private MonitoredConnectionFactory(final ConnectionFactory factory)
+ {
+ this.factory = factory;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ factory.close();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Connection getConnection() throws DirectoryException
+ {
+ try
+ {
+ final Connection connection = factory.getConnection();
+ isAvailable = true;
+ return connection;
+ }
+ catch (final DirectoryException e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ isAvailable = false;
+ throw e;
+ }
+ }
+
+
+
+ private boolean isAvailable()
+ {
+ return isAvailable;
+ }
+ }
+
+
+
+ private final MonitoredConnectionFactory[] factories;
+ private final int maxIndex;
+ private final ScheduledFuture<?> monitorFuture;
+
+
+
+ /**
+ * Creates a new abstract load-balancer.
+ *
+ * @param factories
+ * The list of underlying connection factories.
+ * @param scheduler
+ * The monitoring scheduler.
+ */
+ AbstractLoadBalancer(final ConnectionFactory[] factories,
+ final ScheduledExecutorService scheduler)
+ {
+ this.factories = new MonitoredConnectionFactory[factories.length];
+ this.maxIndex = factories.length;
+
+ for (int i = 0; i < maxIndex; i++)
+ {
+ this.factories[i] = new MonitoredConnectionFactory(factories[i]);
+ }
+
+ this.monitorFuture = scheduler.scheduleWithFixedDelay(this, 5, 5,
+ TimeUnit.SECONDS);
+ }
+
+
+
+ /**
+ * Close underlying connection pools.
+ */
+ @Override
+ public final void close()
+ {
+ monitorFuture.cancel(true);
+
+ for (final ConnectionFactory factory : factories)
+ {
+ factory.close();
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final Connection getConnection() throws DirectoryException
+ {
+ final int startIndex = getStartIndex();
+ return new FailoverConnection(startIndex);
+ }
+
+
+
+ /**
+ * Try to connect to any offline connection factories.
+ */
+ @Override
+ public void run()
+ {
+ for (final MonitoredConnectionFactory factory : factories)
+ {
+ if (!factory.isAvailable())
+ {
+ try
+ {
+ factory.getConnection().close();
+ }
+ catch (final DirectoryException e)
+ {
+ if (debugEnabled())
+ {
+ TRACER.debugCaught(DebugLogLevel.ERROR, e);
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Return the start which should be used for the next connection attempt.
+ *
+ * @return The start which should be used for the next connection attempt.
+ */
+ abstract int getStartIndex();
+
+ }
+
+
/**
* A factory which returns pre-authenticated connections for searches.
@@ -245,7 +583,7 @@
* <p>
* Package private for testing.
*/
- static final class ConnectionPool implements ConnectionFactory, Closeable
+ static final class ConnectionPool implements ConnectionFactory
{
/**
@@ -445,13 +783,8 @@
* <p>
* Package private for testing.
*/
- static final class FailoverConnectionFactory implements ConnectionFactory,
- Closeable
+ static final class FailoverLoadBalancer extends AbstractLoadBalancer
{
- private final ConnectionFactory primary;
- private final ConnectionFactory secondary;
-
-
/**
* Creates a new fail-over connection factory which will always try the
@@ -461,27 +794,14 @@
* The primary connection factory.
* @param secondary
* The secondary connection factory.
+ * @param scheduler
+ * The monitoring scheduler.
*/
- FailoverConnectionFactory(final ConnectionFactory primary,
- final ConnectionFactory secondary)
+ FailoverLoadBalancer(final ConnectionFactory primary,
+ final ConnectionFactory secondary,
+ final ScheduledExecutorService scheduler)
{
- this.primary = primary;
- this.secondary = secondary;
- }
-
-
-
- /**
- * Close underlying load-balancers.
- */
- @Override
- public void close()
- {
- primary.close();
- if (secondary != null)
- {
- secondary.close();
- }
+ super(new ConnectionFactory[] { primary, secondary }, scheduler);
}
@@ -490,24 +810,10 @@
* {@inheritDoc}
*/
@Override
- public Connection getConnection() throws DirectoryException
+ int getStartIndex()
{
- if (secondary == null)
- {
- // No fail-over so just use the primary.
- return primary.getConnection();
- }
- else
- {
- try
- {
- return primary.getConnection();
- }
- catch (final DirectoryException e)
- {
- return secondary.getConnection();
- }
- }
+ // Always start with the primaries.
+ return 0;
}
}
@@ -1116,9 +1422,9 @@
/**
* An interface for obtaining a connection factory for LDAP connections to a
- * named LDAP server.
+ * named LDAP server and the monitoring scheduler.
*/
- static interface LDAPConnectionFactoryProvider
+ static interface Provider
{
/**
* Returns a connection factory which can be used for obtaining connections
@@ -1135,6 +1441,17 @@
*/
ConnectionFactory getLDAPConnectionFactory(String host, int port,
LDAPPassThroughAuthenticationPolicyCfg cfg);
+
+
+
+ /**
+ * Returns the scheduler which should be used to periodically ping
+ * connection factories to determine when they are online.
+ *
+ * @return The scheduler which should be used to periodically ping
+ * connection factories to determine when they are online.
+ */
+ ScheduledExecutorService getScheduledExecutorService();
}
@@ -1143,9 +1460,8 @@
* A simplistic load-balancer connection factory implementation using
* approximately round-robin balancing.
*/
- static final class LoadBalancer implements ConnectionFactory, Closeable
+ static final class RoundRobinLoadBalancer extends AbstractLoadBalancer
{
- private final ConnectionFactory[] factories;
private final AtomicInteger nextIndex = new AtomicInteger();
private final int maxIndex;
@@ -1157,67 +1473,23 @@
*
* @param factories
* The list of underlying connection factories.
+ * @param scheduler
+ * The monitoring scheduler.
*/
- LoadBalancer(final ConnectionFactory[] factories)
+ RoundRobinLoadBalancer(final ConnectionFactory[] factories,
+ final ScheduledExecutorService scheduler)
{
- this.factories = factories;
+ super(factories, scheduler);
this.maxIndex = factories.length;
}
/**
- * Close underlying connection pools.
- */
- @Override
- public void close()
- {
- for (final ConnectionFactory factory : factories)
- {
- factory.close();
- }
- }
-
-
-
- /**
* {@inheritDoc}
*/
@Override
- public Connection getConnection() throws DirectoryException
- {
- final int startIndex = getStartIndex();
- int index = startIndex;
- for (;;)
- {
- final ConnectionFactory factory = factories[index];
-
- try
- {
- return factory.getConnection();
- }
- catch (final DirectoryException e)
- {
- // Try the next index.
- if (++index == maxIndex)
- {
- index = 0;
- }
-
- // If all the factories have been tried then give up and throw the
- // exception.
- if (index == startIndex)
- {
- throw e;
- }
- }
- }
- }
-
-
-
- // Determine the start index.
- private int getStartIndex()
+ int getStartIndex()
{
// A round robin pool of one connection factories is unlikely in
// practice and requires special treatment.
@@ -1512,8 +1784,8 @@
// Current configuration.
private LDAPPassThroughAuthenticationPolicyCfg cfg;
- private FailoverConnectionFactory searchFactory = null;
- private FailoverConnectionFactory bindFactory = null;
+ private ConnectionFactory searchFactory = null;
+ private ConnectionFactory bindFactory = null;
@@ -1642,8 +1914,10 @@
// authenticated user.
// Create load-balancers for primary servers.
- final LoadBalancer primarySearchLoadBalancer;
- final LoadBalancer primaryBindLoadBalancer;
+ final RoundRobinLoadBalancer primarySearchLoadBalancer;
+ final RoundRobinLoadBalancer primaryBindLoadBalancer;
+ final ScheduledExecutorService scheduler = provider
+ .getScheduledExecutorService();
Set<String> servers = cfg.getPrimaryRemoteLDAPServer();
ConnectionPool[] searchPool = new ConnectionPool[servers.size()];
@@ -1658,18 +1932,16 @@
cfg.getMappedSearchBindPassword()));
bindPool[index++] = new ConnectionPool(factory);
}
- primarySearchLoadBalancer = new LoadBalancer(searchPool);
- primaryBindLoadBalancer = new LoadBalancer(bindPool);
+ primarySearchLoadBalancer = new RoundRobinLoadBalancer(searchPool,
+ scheduler);
+ primaryBindLoadBalancer = new RoundRobinLoadBalancer(bindPool, scheduler);
// Create load-balancers for secondary servers.
- final LoadBalancer secondarySearchLoadBalancer;
- final LoadBalancer secondaryBindLoadBalancer;
-
servers = cfg.getSecondaryRemoteLDAPServer();
if (servers.isEmpty())
{
- secondarySearchLoadBalancer = null;
- secondaryBindLoadBalancer = null;
+ searchFactory = primarySearchLoadBalancer;
+ bindFactory = primaryBindLoadBalancer;
}
else
{
@@ -1685,14 +1957,15 @@
cfg.getMappedSearchBindPassword()));
bindPool[index++] = new ConnectionPool(factory);
}
- secondarySearchLoadBalancer = new LoadBalancer(searchPool);
- secondaryBindLoadBalancer = new LoadBalancer(bindPool);
+ final RoundRobinLoadBalancer secondarySearchLoadBalancer =
+ new RoundRobinLoadBalancer(searchPool, scheduler);
+ final RoundRobinLoadBalancer secondaryBindLoadBalancer =
+ new RoundRobinLoadBalancer(bindPool, scheduler);
+ searchFactory = new FailoverLoadBalancer(primarySearchLoadBalancer,
+ secondarySearchLoadBalancer, scheduler);
+ bindFactory = new FailoverLoadBalancer(primaryBindLoadBalancer,
+ secondaryBindLoadBalancer, scheduler);
}
-
- searchFactory = new FailoverConnectionFactory(primarySearchLoadBalancer,
- secondarySearchLoadBalancer);
- bindFactory = new FailoverConnectionFactory(primaryBindLoadBalancer,
- secondaryBindLoadBalancer);
}
@@ -1725,15 +1998,32 @@
}
// The provider which should be used by policies to create LDAP connections.
- private final LDAPConnectionFactoryProvider provider;
+ private final Provider provider;
/**
* The default LDAP connection factory provider.
*/
- private static final LDAPConnectionFactoryProvider DEFAULT_PROVIDER =
- new LDAPConnectionFactoryProvider()
+ private static final Provider DEFAULT_PROVIDER = new Provider()
{
+ // Global scheduler used for periodically monitoring connection factories in
+ // order to detect when they are online.
+ private final ScheduledExecutorService scheduler = Executors
+ .newScheduledThreadPool(2, new ThreadFactory()
+ {
+
+ @Override
+ public Thread newThread(final Runnable r)
+ {
+ final Thread t = new DirectoryThread(r,
+ "LDAP PTA connection monitor thread");
+ t.setDaemon(true);
+ return t;
+ }
+ });
+
+
+
@Override
public ConnectionFactory getLDAPConnectionFactory(final String host,
final int port, final LDAPPassThroughAuthenticationPolicyCfg cfg)
@@ -1741,6 +2031,14 @@
return new LDAPConnectionFactory(host, port, cfg);
}
+
+
+ @Override
+ public ScheduledExecutorService getScheduledExecutorService()
+ {
+ return scheduler;
+ }
+
};
@@ -1843,8 +2141,7 @@
* The LDAP connection factory provider implementation which LDAP PTA
* authentication policies will use.
*/
- LDAPPassThroughAuthenticationPolicyFactory(
- final LDAPConnectionFactoryProvider provider)
+ LDAPPassThroughAuthenticationPolicyFactory(final Provider provider)
{
this.provider = provider;
}
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
index d43921d..62d697a 100644
--- 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
@@ -36,8 +36,7 @@
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
import org.opends.messages.Message;
import org.opends.server.TestCaseUtils;
@@ -385,6 +384,16 @@
/**
* {@inheritDoc}
*/
+ public void close()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
@Override
public Connection getConnection() throws DirectoryException
{
@@ -403,16 +412,6 @@
}
}
-
-
- /**
- * {@inheritDoc}
- */
- public void close()
- {
- // Nothing to do.
- }
-
}
@@ -666,10 +665,236 @@
static final class MockProvider implements
- LDAPPassThroughAuthenticationPolicyFactory.LDAPConnectionFactoryProvider
+ LDAPPassThroughAuthenticationPolicyFactory.Provider
{
+ private final class MockScheduledFuture implements ScheduledFuture<Void>
+ {
+ private final Runnable runnable;
+
+
+
+ MockScheduledFuture(final Runnable runnable)
+ {
+ this.runnable = runnable;
+ }
+
+
+
+ public boolean cancel(final boolean mayInterruptIfRunning)
+ {
+ monitorRunnables.remove(this);
+ return true;
+ }
+
+
+
+ public int compareTo(final Delayed o)
+ {
+ return 0;
+ }
+
+
+
+ public Void get() throws InterruptedException, ExecutionException
+ {
+ return null;
+ }
+
+
+
+ public Void get(final long timeout, final TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException
+ {
+ return null;
+ }
+
+
+
+ public long getDelay(final TimeUnit unit)
+ {
+ return 0;
+ }
+
+
+
+ public boolean isCancelled()
+ {
+ return false;
+ }
+
+
+
+ public boolean isDone()
+ {
+ return false;
+ }
+
+
+
+ Runnable getRunnable()
+ {
+ return runnable;
+ }
+ }
+
+
+
private final Queue<Event<?>> expectedEvents = new LinkedList<Event<?>>();
+ private final List<MockScheduledFuture> monitorRunnables = new LinkedList<MockScheduledFuture>();
+
+ // All methods unused excepted scheduleWithFixedDelay.
+ private final ScheduledExecutorService mockScheduler = new ScheduledExecutorService()
+ {
+
+ @Override
+ public boolean awaitTermination(final long timeout, final TimeUnit unit)
+ throws InterruptedException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public void execute(final Runnable command)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ final Collection<? extends Callable<T>> tasks)
+ throws InterruptedException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ final Collection<? extends Callable<T>> tasks, final long timeout,
+ final TimeUnit unit) throws InterruptedException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> T invokeAny(final Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> T invokeAny(final Collection<? extends Callable<T>> tasks,
+ final long timeout, final TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public boolean isShutdown()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public boolean isTerminated()
+ {
+ return false;
+ }
+
+
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(final Callable<V> callable,
+ final long delay, final TimeUnit unit)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public ScheduledFuture<?> schedule(final Runnable command,
+ final long delay, final TimeUnit unit)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(final Runnable command,
+ final long initialDelay, final long period, final TimeUnit unit)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(final Runnable command,
+ final long initialDelay, final long delay, final TimeUnit unit)
+ {
+ final MockScheduledFuture future = new MockScheduledFuture(command);
+ monitorRunnables.add(future);
+ return future;
+ }
+
+
+
+ @Override
+ public void shutdown()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public List<Runnable> shutdownNow()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> Future<T> submit(final Callable<T> task)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public Future<?> submit(final Runnable task)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+
+
+ @Override
+ public <T> Future<T> submit(final Runnable task, final T result)
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
@@ -688,6 +913,16 @@
+ /**
+ * {@inheritDoc}
+ */
+ public ScheduledExecutorService getScheduledExecutorService()
+ {
+ return mockScheduler;
+ }
+
+
+
void assertAllExpectedEventsReceived()
{
assertTrue(expectedEvents.isEmpty());
@@ -717,6 +952,17 @@
expectedEvents.add(expectedEvent);
return this;
}
+
+
+
+ void runMonitorTasks()
+ {
+ for (final MockScheduledFuture task : monitorRunnables)
+ {
+ task.getRunnable().run();
+ }
+ }
+
}
@@ -1517,6 +1763,468 @@
/**
+ * Tests fail-over between 2 primary servers then to the secondary data
+ * center.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testFailOverOnConnect() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1).withPrimaryServer(phost2)
+ .withSecondaryServer(shost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid").withBaseDN("o=ad");
+
+ // Create all the events.
+ final MockProvider provider = new MockProvider();
+
+ // First of all the connection factories are created.
+ final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
+ phost2, cfg);
+ final GetLDAPConnectionFactoryEvent fe3 = new GetLDAPConnectionFactoryEvent(
+ shost1, cfg);
+ provider.expectEvent(fe1).expectEvent(fe2).expectEvent(fe3);
+
+ // Get connection for phost1, then search, then bind.
+ final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1,
+ ResultCode.CLIENT_SIDE_CONNECT_ERROR);
+ final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2,
+ ResultCode.CLIENT_SIDE_CONNECT_ERROR);
+ final GetConnectionEvent ceSearch3 = new GetConnectionEvent(fe3);
+
+ final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1,
+ ResultCode.CLIENT_SIDE_CONNECT_ERROR);
+ final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2,
+ ResultCode.CLIENT_SIDE_CONNECT_ERROR);
+ final GetConnectionEvent ceBind3 = new GetConnectionEvent(fe3);
+
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(ceSearch2)
+ .expectEvent(ceSearch3)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch3, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind1)
+ .expectEvent(ceBind2)
+ .expectEvent(ceBind3)
+ .expectEvent(
+ new SimpleBindEvent(ceBind3, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Repeat again using cached connection to shost1: search, then bind.
+
+ // phost1 and phost2 will have been marked as failed and won't be tried.
+ provider.expectEvent(
+ new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind3, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Now simulate monitor thread run in which phost2 is determined to be
+ // available again.
+ final GetConnectionEvent ceSearch2ok = new GetConnectionEvent(fe2);
+ final GetConnectionEvent ceBind2ok = new GetConnectionEvent(fe2);
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(ceSearch2ok)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2ok, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS)).expectEvent(ceBind1)
+ .expectEvent(ceBind2ok);
+
+ provider.expectEvent(
+ new SearchEvent(ceSearch2ok, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind2ok, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connections should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+
+ // Authenticate 3 times test above fail-over.
+ for (int i = 0; i < 3; i++)
+ {
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Run monitor which should try to connect to phost1&2 and determine that
+ // phost2 is available again.
+ if (i == 2)
+ {
+ provider.runMonitorTasks();
+ }
+
+ // Perform authentication.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ state.finalizeStateAfterBind();
+ }
+
+ // Cached connections should be closed when the policy is finalized
+ // (primaries first, then secondaries).
+ provider.expectEvent(new CloseEvent(ceSearch2ok));
+ provider.expectEvent(new CloseEvent(ceSearch3));
+ provider.expectEvent(new CloseEvent(ceBind2ok));
+ provider.expectEvent(new CloseEvent(ceBind3));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
+ * Tests that searches which fail in one LB pool are automatically retried in
+ * the secondary LB pool.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testFBRetrySearchOnFailure() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1).withSecondaryServer(shost1)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid").withBaseDN("o=ad");
+
+ // Create all the events.
+ final MockProvider provider = new MockProvider();
+
+ // First of all the connection factories are created.
+ final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
+ shost1, cfg);
+ provider.expectEvent(fe1).expectEvent(fe2);
+
+ // Get connection for phost1, then search (fail), and retry on shost1
+ final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
+ final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
+ final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
+
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch1, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.UNAVAILABLE))
+ .expectEvent(new CloseEvent(ceSearch1))
+ .expectEvent(ceSearch2)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind1)
+ .expectEvent(
+ new SimpleBindEvent(ceBind1, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Now simulate shost1 going down as well.
+
+ // phost1 will have been marked as failed and won't be retried.
+ provider.expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.UNAVAILABLE)).expectEvent(
+ new CloseEvent(ceSearch2));
+
+ // Now simulate phost1 coming back and fail back to it.
+
+ // Now simulate monitor thread run in which phost1 and shost1 are determined
+ // to be available again.
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch1, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(ceSearch2)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS));
+
+ provider.expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind1, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connections should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+
+ // Authenticate 3 times, second should fail.
+ for (int i = 0; i < 3; i++)
+ {
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ switch (i)
+ {
+ case 0:
+ // First attempt should succeed.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+ break;
+ case 1:
+ // Second attempt should fail.
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (final DirectoryException e)
+ {
+ // No valid connections available so this should always fail with
+ // INVALID_CREDENTIALS.
+ assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
+ e.getMessage());
+ }
+ break;
+ case 2:
+ // Third attempt should succeed, once the monitor has run.
+ provider.runMonitorTasks();
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+ break;
+ }
+
+ state.finalizeStateAfterBind();
+ }
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch1));
+ provider.expectEvent(new CloseEvent(ceSearch2));
+ provider.expectEvent(new CloseEvent(ceBind1));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
+ * Tests configuration validation.
+ *
+ * @param cfg
+ * The configuration to be tested.
+ * @param isValid
+ * Whether or not the provided configuration is valid.
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true, dataProvider = "testIsConfigurationAcceptableData")
+ public void testIsConfigurationAcceptable(
+ final LDAPPassThroughAuthenticationPolicyCfg cfg, final boolean isValid)
+ throws Exception
+ {
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory();
+ assertEquals(
+ factory.isConfigurationAcceptable(cfg, new LinkedList<Message>()),
+ isValid);
+ }
+
+
+
+ /**
+ * Returns test data for {@link #testIsConfigurationAcceptable}.
+ *
+ * @return Test data for {@link #testIsConfigurationAcceptable}.
+ */
+ @DataProvider
+ public Object[][] testIsConfigurationAcceptableData()
+ {
+ // @formatter:off
+ return new Object[][] {
+ /* cfg, isValid */
+ { mockCfg().withPrimaryServer("test:1"), true },
+ { mockCfg().withPrimaryServer("test:65535"), true },
+ { mockCfg().withPrimaryServer("test:0"), false },
+ { mockCfg().withPrimaryServer("test:65536"), false },
+ { mockCfg().withPrimaryServer("test:1000000"), false },
+ { mockCfg().withSecondaryServer("test:1"), true },
+ { mockCfg().withSecondaryServer("test:65535"), true },
+ { mockCfg().withSecondaryServer("test:0"), false },
+ { mockCfg().withSecondaryServer("test:65536"), false },
+ { mockCfg().withSecondaryServer("test:1000000"), false },
+ };
+ // @formatter:on
+ }
+
+
+
+ /**
+ * Tests that searches which fail on one server are automatically retried on
+ * another within the same LB.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testLBRetrySearchOnFailure() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1).withPrimaryServer(phost2)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid").withBaseDN("o=ad");
+
+ // Create all the events.
+ final MockProvider provider = new MockProvider();
+
+ // First of all the connection factories are created.
+ final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
+ phost2, cfg);
+ provider.expectEvent(fe1).expectEvent(fe2);
+
+ // Get connection for phost1, then search (fail), and retry on phost2
+ final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
+ final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
+ final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
+ final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2);
+
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch1, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.UNAVAILABLE))
+ .expectEvent(new CloseEvent(ceSearch1))
+ .expectEvent(ceSearch2)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind1)
+ .expectEvent(
+ new SimpleBindEvent(ceBind1, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Now simulate phost2 going down as well.
+
+ // phost1 will have been marked as failed and won't be retried.
+ provider.expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.UNAVAILABLE)).expectEvent(
+ new CloseEvent(ceSearch2));
+
+ // Now simulate phost1 coming back and fail back to it.
+
+ // Now simulate monitor thread run in which phost1 and shost1 are determined
+ // to be available again.
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch1, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(ceSearch2)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS));
+
+ // Note that the bind will be load-balanced.
+ provider
+ .expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind2)
+ .expectEvent(
+ new SimpleBindEvent(ceBind2, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connections should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+
+ // Authenticate 3 times, second should fail.
+ for (int i = 0; i < 3; i++)
+ {
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ switch (i)
+ {
+ case 0:
+ // First attempt should succeed.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+ break;
+ case 1:
+ // Second attempt should fail.
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (final DirectoryException e)
+ {
+ // No valid connections available so this should always fail with
+ // INVALID_CREDENTIALS.
+ assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
+ e.getMessage());
+ }
+ break;
+ case 2:
+ // Third attempt should succeed, once the monitor has run.
+ provider.runMonitorTasks();
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+ break;
+ }
+
+ state.finalizeStateAfterBind();
+ }
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch1));
+ provider.expectEvent(new CloseEvent(ceSearch2));
+ provider.expectEvent(new CloseEvent(ceBind1));
+ provider.expectEvent(new CloseEvent(ceBind2));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
* Tests valid bind which times out at the client. These should trigger a
* CLIENT_SIDE_TIMEOUT result code.
*
@@ -2339,6 +3047,139 @@
/**
+ * Tests load balancing across 3 servers.
+ *
+ * @throws Exception
+ * If an unexpected exception occurred.
+ */
+ @Test(enabled = true)
+ public void testLoadBalancing() throws Exception
+ {
+ // Mock configuration.
+ final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
+ .withPrimaryServer(phost1).withPrimaryServer(phost2)
+ .withPrimaryServer(phost3)
+ .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
+ .withMappedAttribute("uid").withBaseDN("o=ad");
+
+ // Create all the events.
+ final MockProvider provider = new MockProvider();
+
+ // First of all the connection factories are created.
+ final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
+ phost1, cfg);
+ final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
+ phost2, cfg);
+ final GetLDAPConnectionFactoryEvent fe3 = new GetLDAPConnectionFactoryEvent(
+ phost3, cfg);
+ provider.expectEvent(fe1).expectEvent(fe2).expectEvent(fe3);
+
+ // Get connection for phost1, then search, then bind.
+ final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
+ final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
+ provider
+ .expectEvent(ceSearch1)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch1, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind1)
+ .expectEvent(
+ new SimpleBindEvent(ceBind1, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Get connection for phost2, then search, then bind.
+ final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
+ final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2);
+ provider
+ .expectEvent(ceSearch2)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch2, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind2)
+ .expectEvent(
+ new SimpleBindEvent(ceBind2, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Get connection for phost3, then search, then bind.
+ final GetConnectionEvent ceSearch3 = new GetConnectionEvent(fe3);
+ final GetConnectionEvent ceBind3 = new GetConnectionEvent(fe3);
+ provider
+ .expectEvent(ceSearch3)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch3, searchBindDNString,
+ "searchPassword", ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString))
+ .expectEvent(ceBind3)
+ .expectEvent(
+ new SimpleBindEvent(ceBind3, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Repeat again using cached connection to phost1: search, then bind.
+ provider.expectEvent(
+ new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind1, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Repeat again using cached connection to phost2: search, then bind.
+ provider.expectEvent(
+ new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind2, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Repeat again using cached connection to phost3: search, then bind.
+ provider.expectEvent(
+ new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString)).expectEvent(
+ new SimpleBindEvent(ceBind3, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connections should be cached until the policy is finalized.
+
+ // Obtain policy and state.
+ final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
+ provider);
+ assertTrue(factory.isConfigurationAcceptable(cfg, null));
+ final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
+
+ // Cycle twice through the LB pool.
+ for (int i = 0; i < 6; i++)
+ {
+ final AuthenticationPolicyState state = policy
+ .createAuthenticationPolicyState(userEntry);
+ assertEquals(state.getAuthenticationPolicy(), policy);
+
+ // Perform authentication.
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+
+ state.finalizeStateAfterBind();
+ }
+
+ // Cached connections should be closed when the policy is finalized.
+ provider.expectEvent(new CloseEvent(ceSearch1));
+ provider.expectEvent(new CloseEvent(ceSearch2));
+ provider.expectEvent(new CloseEvent(ceSearch3));
+ provider.expectEvent(new CloseEvent(ceBind1));
+ provider.expectEvent(new CloseEvent(ceBind2));
+ provider.expectEvent(new CloseEvent(ceBind3));
+
+ // Tear down and check final state.
+ policy.finalizeAuthenticationPolicy();
+ provider.assertAllExpectedEventsReceived();
+ }
+
+
+
+ /**
* Tests the different mapping policies: connection attempts will succeed, as
* will any searches, but the final user bind may or may not succeed depending
* on the provided result code.
@@ -2486,123 +3327,63 @@
/**
- * Tests configuration validation.
- *
- * @param cfg
- * The configuration to be tested.
- * @param isValid
- * Whether or not the provided configuration is valid.
- * @throws Exception
- * If an unexpected exception occurred.
- */
- @Test(enabled = true, dataProvider = "testIsConfigurationAcceptableData")
- public void testIsConfigurationAcceptable(
- final LDAPPassThroughAuthenticationPolicyCfg cfg, final boolean isValid)
- throws Exception
- {
- final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory();
- assertEquals(
- factory.isConfigurationAcceptable(cfg, new LinkedList<Message>()),
- isValid);
- }
-
-
-
- /**
- * Returns test data for {@link #testIsConfigurationAcceptable}.
- *
- * @return Test data for {@link #testIsConfigurationAcceptable}.
- */
- @DataProvider
- public Object[][] testIsConfigurationAcceptableData()
- {
- // @formatter:off
- return new Object[][] {
- /* cfg, isValid */
- { mockCfg().withPrimaryServer("test:1"), true },
- { mockCfg().withPrimaryServer("test:65535"), true },
- { mockCfg().withPrimaryServer("test:0"), false },
- { mockCfg().withPrimaryServer("test:65536"), false },
- { mockCfg().withPrimaryServer("test:1000000"), false },
- { mockCfg().withSecondaryServer("test:1"), true },
- { mockCfg().withSecondaryServer("test:65535"), true },
- { mockCfg().withSecondaryServer("test:0"), false },
- { mockCfg().withSecondaryServer("test:65536"), false },
- { mockCfg().withSecondaryServer("test:1000000"), false },
- };
- // @formatter:on
- }
-
-
-
- /**
- * Tests that mapped PTA performs searches across multiple base DNs if
- * configured.
+ * Tests that mapped PTA fails when no match attribute values are found.
*
* @throws Exception
* If an unexpected exception occurred.
*/
@Test(enabled = true)
- public void testMultipleSearchBaseDNs() throws Exception
+ public void testMissingMappingAttributes() throws Exception
{
// Mock configuration.
final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
.withPrimaryServer(phost1)
.withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid").withBaseDN("o=first")
- .withBaseDN("o=second").withBaseDN("o=third");
+ .withMappedAttribute("uid1").withBaseDN("o=ad");
// Create the provider and its list of expected events.
final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
phost1, cfg);
final MockProvider provider = new MockProvider().expectEvent(fe);
- // Add search events.
- GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
- provider
- .expectEvent(ceSearch)
- .expectEvent(
- new SimpleBindEvent(ceSearch, searchBindDNString, "searchPassword",
- ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch, "o=first", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
- .expectEvent(
- new SearchEvent(ceSearch, "o=second", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
- .expectEvent(
- new SearchEvent(ceSearch, "o=third", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString));
- // Connection should be cached until the policy is finalized.
-
- // Add bind events.
- final GetConnectionEvent ceBind = new GetConnectionEvent(fe);
- provider.expectEvent(ceBind).expectEvent(
- new SimpleBindEvent(ceBind, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Connection should be cached until the policy is finalized.
-
// Obtain policy and state.
+ final Entry testUser = TestCaseUtils.makeEntry(
+ /* @formatter:off */
+ "dn: " + opendjDNString,
+ "objectClass: top",
+ "objectClass: person",
+ "sn: user",
+ "cn: test user",
+ "aduser: " + adDNString
+ /* @formatter:on */
+ );
+
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
assertTrue(factory.isConfigurationAcceptable(cfg, null));
final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(userEntry);
+ .createAuthenticationPolicyState(testUser);
assertEquals(state.getAuthenticationPolicy(), policy);
// Perform authentication.
- assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
+ try
+ {
+ state.passwordMatches(ByteString.valueOf(userPassword));
+ fail("password match unexpectedly succeeded");
+ }
+ catch (final DirectoryException e)
+ {
+ // No mapping attributes so this should always fail with
+ // INVALID_CREDENTIALS.
+ assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
+ e.getMessage());
+ }
// There should be no more pending events.
provider.assertAllExpectedEventsReceived();
state.finalizeStateAfterBind();
- // Cached connections should be closed when the policy is finalized.
- provider.expectEvent(new CloseEvent(ceSearch));
- provider.expectEvent(new CloseEvent(ceBind));
-
// Tear down and check final state.
policy.finalizeAuthenticationPolicy();
provider.assertAllExpectedEventsReceived();
@@ -2633,7 +3414,7 @@
final MockProvider provider = new MockProvider().expectEvent(fe);
// Add search events.
- GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ final GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
provider
.expectEvent(ceSearch)
.expectEvent(
@@ -2653,7 +3434,7 @@
// Connection should be cached until the policy is finalized.
// Obtain policy and state.
- Entry testUser = TestCaseUtils.makeEntry(
+ final Entry testUser = TestCaseUtils.makeEntry(
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
@@ -2715,7 +3496,7 @@
final MockProvider provider = new MockProvider().expectEvent(fe);
// Add search events.
- GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ final GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
provider
.expectEvent(ceSearch)
.expectEvent(
@@ -2735,7 +3516,7 @@
// Connection should be cached until the policy is finalized.
// Obtain policy and state.
- Entry testUser = TestCaseUtils.makeEntry(
+ final Entry testUser = TestCaseUtils.makeEntry(
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
@@ -2776,594 +3557,72 @@
/**
- * Tests that mapped PTA fails when no match attribute values are found.
+ * Tests that mapped PTA performs searches across multiple base DNs if
+ * configured.
*
* @throws Exception
* If an unexpected exception occurred.
*/
@Test(enabled = true)
- public void testMissingMappingAttributes() throws Exception
+ public void testMultipleSearchBaseDNs() throws Exception
{
// Mock configuration.
final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
.withPrimaryServer(phost1)
.withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid1").withBaseDN("o=ad");
+ .withMappedAttribute("uid").withBaseDN("o=first")
+ .withBaseDN("o=second").withBaseDN("o=third");
// Create the provider and its list of expected events.
final GetLDAPConnectionFactoryEvent fe = new GetLDAPConnectionFactoryEvent(
phost1, cfg);
final MockProvider provider = new MockProvider().expectEvent(fe);
- // Obtain policy and state.
- Entry testUser = TestCaseUtils.makeEntry(
- /* @formatter:off */
- "dn: " + opendjDNString,
- "objectClass: top",
- "objectClass: person",
- "sn: user",
- "cn: test user",
- "aduser: " + adDNString
- /* @formatter:on */
- );
+ // Add search events.
+ final GetConnectionEvent ceSearch = new GetConnectionEvent(fe);
+ provider
+ .expectEvent(ceSearch)
+ .expectEvent(
+ new SimpleBindEvent(ceSearch, searchBindDNString, "searchPassword",
+ ResultCode.SUCCESS))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=first", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=second", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED))
+ .expectEvent(
+ new SearchEvent(ceSearch, "o=third", SearchScope.WHOLE_SUBTREE,
+ "(uid=aduser)", adDNString));
+ // Connection should be cached until the policy is finalized.
+ // Add bind events.
+ final GetConnectionEvent ceBind = new GetConnectionEvent(fe);
+ provider.expectEvent(ceBind).expectEvent(
+ new SimpleBindEvent(ceBind, adDNString, userPassword,
+ ResultCode.SUCCESS));
+
+ // Connection should be cached until the policy is finalized.
+
+ // Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
assertTrue(factory.isConfigurationAcceptable(cfg, null));
final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(testUser);
+ .createAuthenticationPolicyState(userEntry);
assertEquals(state.getAuthenticationPolicy(), policy);
// Perform authentication.
- try
- {
- state.passwordMatches(ByteString.valueOf(userPassword));
- fail("password match unexpectedly succeeded");
- }
- catch (final DirectoryException e)
- {
- // No mapping attributes so this should always fail with
- // INVALID_CREDENTIALS.
- assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
- e.getMessage());
- }
+ assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
// There should be no more pending events.
provider.assertAllExpectedEventsReceived();
state.finalizeStateAfterBind();
- // Tear down and check final state.
- policy.finalizeAuthenticationPolicy();
- provider.assertAllExpectedEventsReceived();
- }
-
-
-
- /**
- * Tests load balancing across 3 servers.
- *
- * @throws Exception
- * If an unexpected exception occurred.
- */
- @Test(enabled = true)
- public void testLoadBalancing() throws Exception
- {
- // Mock configuration.
- final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
- .withPrimaryServer(phost1).withPrimaryServer(phost2)
- .withPrimaryServer(phost3)
- .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid").withBaseDN("o=ad");
-
- // Create all the events.
- final MockProvider provider = new MockProvider();
-
- // First of all the connection factories are created.
- final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
- phost1, cfg);
- final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
- phost2, cfg);
- final GetLDAPConnectionFactoryEvent fe3 = new GetLDAPConnectionFactoryEvent(
- phost3, cfg);
- provider.expectEvent(fe1).expectEvent(fe2).expectEvent(fe3);
-
- // Get connection for phost1, then search, then bind.
- final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
- final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind1)
- .expectEvent(
- new SimpleBindEvent(ceBind1, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Get connection for phost2, then search, then bind.
- final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
- final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2);
- provider
- .expectEvent(ceSearch2)
- .expectEvent(
- new SimpleBindEvent(ceSearch2, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind2)
- .expectEvent(
- new SimpleBindEvent(ceBind2, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Get connection for phost3, then search, then bind.
- final GetConnectionEvent ceSearch3 = new GetConnectionEvent(fe3);
- final GetConnectionEvent ceBind3 = new GetConnectionEvent(fe3);
- provider
- .expectEvent(ceSearch3)
- .expectEvent(
- new SimpleBindEvent(ceSearch3, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind3)
- .expectEvent(
- new SimpleBindEvent(ceBind3, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Repeat again using cached connection to phost1: search, then bind.
- provider.expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString)).expectEvent(
- new SimpleBindEvent(ceBind1, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Repeat again using cached connection to phost2: search, then bind.
- provider.expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString)).expectEvent(
- new SimpleBindEvent(ceBind2, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Repeat again using cached connection to phost3: search, then bind.
- provider.expectEvent(
- new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString)).expectEvent(
- new SimpleBindEvent(ceBind3, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Connections should be cached until the policy is finalized.
-
- // Obtain policy and state.
- final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
- provider);
- assertTrue(factory.isConfigurationAcceptable(cfg, null));
- final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
-
- // Cycle twice through the LB pool.
- for (int i = 0; i < 6; i++)
- {
- final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(userEntry);
- assertEquals(state.getAuthenticationPolicy(), policy);
-
- // Perform authentication.
- assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
-
- state.finalizeStateAfterBind();
- }
-
// Cached connections should be closed when the policy is finalized.
- provider.expectEvent(new CloseEvent(ceSearch1));
- provider.expectEvent(new CloseEvent(ceSearch2));
- provider.expectEvent(new CloseEvent(ceSearch3));
- provider.expectEvent(new CloseEvent(ceBind1));
- provider.expectEvent(new CloseEvent(ceBind2));
- provider.expectEvent(new CloseEvent(ceBind3));
-
- // Tear down and check final state.
- policy.finalizeAuthenticationPolicy();
- provider.assertAllExpectedEventsReceived();
- }
-
-
-
- /**
- * Tests fail-over between 2 primary servers then to the secondary data
- * center.
- *
- * @throws Exception
- * If an unexpected exception occurred.
- */
- @Test(enabled = true)
- public void testFailOverOnConnect() throws Exception
- {
- // Mock configuration.
- final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
- .withPrimaryServer(phost1).withPrimaryServer(phost2)
- .withSecondaryServer(shost1)
- .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid").withBaseDN("o=ad");
-
- // Create all the events.
- final MockProvider provider = new MockProvider();
-
- // First of all the connection factories are created.
- final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
- phost1, cfg);
- final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
- phost2, cfg);
- final GetLDAPConnectionFactoryEvent fe3 = new GetLDAPConnectionFactoryEvent(
- shost1, cfg);
- provider.expectEvent(fe1).expectEvent(fe2).expectEvent(fe3);
-
- // Get connection for phost1, then search, then bind.
- final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1,
- ResultCode.CLIENT_SIDE_CONNECT_ERROR);
- final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2,
- ResultCode.CLIENT_SIDE_CONNECT_ERROR);
- final GetConnectionEvent ceSearch3 = new GetConnectionEvent(fe3);
-
- final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1,
- ResultCode.CLIENT_SIDE_CONNECT_ERROR);
- final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2,
- ResultCode.CLIENT_SIDE_CONNECT_ERROR);
- final GetConnectionEvent ceBind3 = new GetConnectionEvent(fe3);
-
- provider
- .expectEvent(ceSearch1)
- .expectEvent(ceSearch2)
- .expectEvent(ceSearch3)
- .expectEvent(
- new SimpleBindEvent(ceSearch3, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind1)
- .expectEvent(ceBind2)
- .expectEvent(ceBind3)
- .expectEvent(
- new SimpleBindEvent(ceBind3, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Repeat again using cached connection to shost1: search, then bind.
-
- // Note that LB will cause phost2 to be tried first, hence ceSearch2 then
- // ceSearch1.
- provider
- .expectEvent(ceSearch2)
- .expectEvent(ceSearch1)
- .expectEvent(
- new SearchEvent(ceSearch3, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind2)
- .expectEvent(ceBind1)
- .expectEvent(
- new SimpleBindEvent(ceBind3, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Now simulate phost2 coming back and fail back to it
- final GetConnectionEvent ceSearch2ok = new GetConnectionEvent(fe2);
- final GetConnectionEvent ceBind2ok = new GetConnectionEvent(fe2);
- provider
- .expectEvent(ceSearch1)
- .expectEvent(ceSearch2ok)
- .expectEvent(
- new SimpleBindEvent(ceSearch2ok, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch2ok, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind1)
- .expectEvent(ceBind2ok)
- .expectEvent(
- new SimpleBindEvent(ceBind2ok, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Connections should be cached until the policy is finalized.
-
- // Obtain policy and state.
- final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
- provider);
- assertTrue(factory.isConfigurationAcceptable(cfg, null));
- final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
-
- // Authenticate 3 times test above fail-over.
- for (int i = 0; i < 3; i++)
- {
- final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(userEntry);
- assertEquals(state.getAuthenticationPolicy(), policy);
-
- // Perform authentication.
- assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
-
- state.finalizeStateAfterBind();
- }
-
- // Cached connections should be closed when the policy is finalized
- // (primaries first, then secondaries).
- provider.expectEvent(new CloseEvent(ceSearch2ok));
- provider.expectEvent(new CloseEvent(ceSearch3));
- provider.expectEvent(new CloseEvent(ceBind2ok));
- provider.expectEvent(new CloseEvent(ceBind3));
-
- // Tear down and check final state.
- policy.finalizeAuthenticationPolicy();
- provider.assertAllExpectedEventsReceived();
- }
-
-
-
- /**
- * Tests that searches which fail on one server are automatically retried on
- * another within the same LB.
- *
- * @throws Exception
- * If an unexpected exception occurred.
- */
- @Test(enabled = false)
- public void testLBRetrySearchOnFailure() throws Exception
- {
- // Mock configuration.
- final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
- .withPrimaryServer(phost1).withPrimaryServer(phost2)
- .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid").withBaseDN("o=ad");
-
- // Create all the events.
- final MockProvider provider = new MockProvider();
-
- // First of all the connection factories are created.
- final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
- phost1, cfg);
- final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
- phost2, cfg);
- provider.expectEvent(fe1).expectEvent(fe2);
-
- // Get connection for phost1, then search (fail), and retry on phost2
- final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
- final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
-
- final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
- final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2);
-
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch1))
- .expectEvent(ceSearch2)
- .expectEvent(
- new SimpleBindEvent(ceSearch2, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind1)
- .expectEvent(
- new SimpleBindEvent(ceBind1, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Now simulate phost2 going down as well.
-
- // Note that LB will cause phost2 to be tried first, hence ceSearch2 then
- // ceSearch1.
- provider
- .expectEvent(ceSearch2)
- .expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch2))
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch1));
-
- // Now simulate phost1 coming back and fail back to it.
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString));
-
- // Connections should be cached until the policy is finalized.
-
- // Obtain policy and state.
- final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
- provider);
- assertTrue(factory.isConfigurationAcceptable(cfg, null));
- final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
-
- // Authenticate 3 times, second should fail.
- for (int i = 0; i < 3; i++)
- {
- final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(userEntry);
- assertEquals(state.getAuthenticationPolicy(), policy);
-
- // Perform authentication.
- if (i != 1)
- {
- // First and third attempt should succeed.
- assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
- }
- else
- {
- // Second attempt should fail.
- try
- {
- state.passwordMatches(ByteString.valueOf(userPassword));
- fail("password match unexpectedly succeeded");
- }
- catch (final DirectoryException e)
- {
- // No valid connections available so this should always fail with
- // INVALID_CREDENTIALS.
- assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
- e.getMessage());
- }
- }
-
- state.finalizeStateAfterBind();
- }
-
- // Cached connections should be closed when the policy is finalized.
- provider.expectEvent(new CloseEvent(ceSearch1));
- provider.expectEvent(new CloseEvent(ceBind1));
- provider.expectEvent(new CloseEvent(ceBind2));
-
- // Tear down and check final state.
- policy.finalizeAuthenticationPolicy();
- provider.assertAllExpectedEventsReceived();
- }
-
-
-
- /**
- * Tests that searches which fail in one LB pool are automatically retried in
- * the secondary LB pool.
- *
- * @throws Exception
- * If an unexpected exception occurred.
- */
- @Test(enabled = false)
- public void testFBRetrySearchOnFailure() throws Exception
- {
- // Mock configuration.
- final LDAPPassThroughAuthenticationPolicyCfg cfg = mockCfg()
- .withPrimaryServer(phost1).withSecondaryServer(shost1)
- .withMappingPolicy(MappingPolicy.MAPPED_SEARCH)
- .withMappedAttribute("uid").withBaseDN("o=ad");
-
- // Create all the events.
- final MockProvider provider = new MockProvider();
-
- // First of all the connection factories are created.
- final GetLDAPConnectionFactoryEvent fe1 = new GetLDAPConnectionFactoryEvent(
- phost1, cfg);
- final GetLDAPConnectionFactoryEvent fe2 = new GetLDAPConnectionFactoryEvent(
- shost1, cfg);
- provider.expectEvent(fe1).expectEvent(fe2);
-
- // Get connection for phost1, then search (fail), and retry on shost1
- final GetConnectionEvent ceSearch1 = new GetConnectionEvent(fe1);
- final GetConnectionEvent ceSearch2 = new GetConnectionEvent(fe2);
-
- final GetConnectionEvent ceBind1 = new GetConnectionEvent(fe1);
- final GetConnectionEvent ceBind2 = new GetConnectionEvent(fe2);
-
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch1))
- .expectEvent(ceSearch2)
- .expectEvent(
- new SimpleBindEvent(ceSearch2, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString))
- .expectEvent(ceBind1)
- .expectEvent(
- new SimpleBindEvent(ceBind1, adDNString, userPassword,
- ResultCode.SUCCESS));
-
- // Now simulate shost1 going down as well.
-
- // Note that FO will retry phost1 again (unlike LB case).
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch1))
- .expectEvent(ceSearch2)
- .expectEvent(
- new SearchEvent(ceSearch2, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", ResultCode.UNAVAILABLE))
- .expectEvent(new CloseEvent(ceSearch2));
-
- // Now simulate phost1 coming back and fail back to it.
- provider
- .expectEvent(ceSearch1)
- .expectEvent(
- new SimpleBindEvent(ceSearch1, searchBindDNString,
- "searchPassword", ResultCode.SUCCESS))
- .expectEvent(
- new SearchEvent(ceSearch1, "o=ad", SearchScope.WHOLE_SUBTREE,
- "(uid=aduser)", adDNString));
-
- // Connections should be cached until the policy is finalized.
-
- // Obtain policy and state.
- final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
- provider);
- assertTrue(factory.isConfigurationAcceptable(cfg, null));
- final AuthenticationPolicy policy = factory.createAuthenticationPolicy(cfg);
-
- // Authenticate 3 times, second should fail.
- for (int i = 0; i < 3; i++)
- {
- final AuthenticationPolicyState state = policy
- .createAuthenticationPolicyState(userEntry);
- assertEquals(state.getAuthenticationPolicy(), policy);
-
- // Perform authentication.
- if (i != 1)
- {
- // First and third attempt should succeed.
- assertTrue(state.passwordMatches(ByteString.valueOf(userPassword)));
- }
- else
- {
- // Second attempt should fail.
- try
- {
- state.passwordMatches(ByteString.valueOf(userPassword));
- fail("password match unexpectedly succeeded");
- }
- catch (final DirectoryException e)
- {
- // No valid connections available so this should always fail with
- // INVALID_CREDENTIALS.
- assertEquals(e.getResultCode(), ResultCode.INVALID_CREDENTIALS,
- e.getMessage());
- }
- }
-
- state.finalizeStateAfterBind();
- }
-
- // Cached connections should be closed when the policy is finalized.
- provider.expectEvent(new CloseEvent(ceSearch1));
- provider.expectEvent(new CloseEvent(ceBind1));
- provider.expectEvent(new CloseEvent(ceBind2));
+ provider.expectEvent(new CloseEvent(ceSearch));
+ provider.expectEvent(new CloseEvent(ceBind));
// Tear down and check final state.
policy.finalizeAuthenticationPolicy();
--
Gitblit v1.10.0