/*
* 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 legal-notices/CDDLv1_0.txt
* 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 legal-notices/CDDLv1_0.txt.
* 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
*
*
* Portions Copyright 2011-2015 ForgeRock AS.
*/
import org.opends.server.extensions.LDAPPassThroughAuthenticationPolicyFactory.LDAPConnectionFactory;
/**
* Test LDAP authentication mappingPolicy implementation.
*/
@SuppressWarnings("javadoc")
public class LDAPPassThroughAuthenticationPolicyTestCase extends
{
{
{
this.getConnectionEvent = getConnectionEvent;
}
/** {@inheritDoc} */
{
if (event instanceof CloseEvent)
{
}
return false;
}
/** {@inheritDoc} */
{
return builder;
}
}
static abstract class Event<T>
{
{
{
}
return false;
}
{
}
T getResult()
{
return null;
}
}
{
{
}
final ResultCode resultCode)
{
this.resultCode = resultCode;
}
/** {@inheritDoc} */
{
{
}
return null;
}
/** {@inheritDoc} */
{
if (event instanceof GetConnectionEvent)
{
}
return false;
}
/** {@inheritDoc} */
{
return builder;
}
}
{
{
}
/** {@inheritDoc} */
{
if (event instanceof GetLDAPConnectionFactoryEvent)
{
}
return false;
}
/** {@inheritDoc} */
{
return builder;
}
}
static final class MockConnection implements
{
{
this.mockProvider = mockProvider;
this.getConnectionEvent = getConnectionEvent;
}
/** {@inheritDoc} */
public void close()
{
}
/** {@inheritDoc} */
{
{
}
else
{
throw (DirectoryException) result;
}
}
/** {@inheritDoc} */
throws DirectoryException
{
final DirectoryException e = mockProvider
if (e != null)
{
throw e;
}
}
}
static final class MockFactory implements
{
{
this.mockProvider = mockProvider;
}
/** {@inheritDoc} */
public void close()
{
// Nothing to do.
}
/** {@inheritDoc} */
{
final DirectoryException e = mockProvider
if (e != null)
{
throw e;
}
else
{
}
}
}
{
/** Unlimited. */
private int timeoutMS;
private boolean usePasswordCaching;
public void addChangeListener(
{
// Do nothing.
}
public void addLDAPPassThroughChangeListener(
{
// Do nothing.
}
{
return LDAPPassThroughAuthenticationPolicyCfg.class;
}
{
return policyDN;
}
public long getConnectionTimeout()
{
return timeoutMS;
}
{
return LDAPPassThroughAuthenticationPolicyFactory.class.getName();
}
{
return mappedAttributes;
}
{
return baseDNs;
}
{
return mappedSearchBindDN;
}
{
return mappedSearchBindPassword;
}
{
return mappingPolicy;
}
{
return primaryServers;
}
{
return secondaryServers;
}
{
return new TreeSet<>();
}
{
return new TreeSet<>();
}
{
return "ignored";
}
{
return trustManagerDN;
}
public boolean isUseSSL()
{
return false;
}
public boolean isUseTCPKeepAlive()
{
return false;
}
public boolean isUseTCPNoDelay()
{
return false;
}
public void removeChangeListener(
{
// Do nothing.
}
public void removeLDAPPassThroughChangeListener(
{
// Do nothing.
}
{
try
{
}
catch (final DirectoryException e)
{
throw new RuntimeException(e);
}
return this;
}
{
return this;
}
{
return this;
}
{
this.mappingPolicy = policy;
return this;
}
{
return this;
}
{
return this;
}
{
this.mappedSearchBindDN = value;
return this;
}
{
this.mappedSearchBindPassword = value;
return this;
}
{
this.mappedSearchBindPasswordEnvVar = value;
return this;
}
{
this.mappedSearchBindPasswordFile = value;
return this;
}
{
return this;
}
{
this.usePasswordCaching = usePasswordCaching;
return this;
}
/** {@inheritDoc} */
{
return mappedSearchBindPasswordEnvVar;
}
/** {@inheritDoc} */
{
return mappedSearchBindPasswordFile;
}
/** {@inheritDoc} */
{
return mappedSearchBindPasswordProperty;
}
/** {@inheritDoc} */
public long getCachedPasswordTTL()
{
return 86400;
}
/** {@inheritDoc} */
{
return "Salted SHA-1";
}
/** {@inheritDoc} */
{
try
{
}
catch (DirectoryException e)
{
throw new RuntimeException(e);
}
}
/** {@inheritDoc} */
public boolean isUsePasswordCaching()
{
return usePasswordCaching;
}
}
static final class MockProvider implements
{
{
{
}
{
monitorRunnables.remove(this);
return true;
}
{
return 0;
}
{
return null;
}
{
return null;
}
{
return 0;
}
public boolean isCancelled()
{
return false;
}
public boolean isDone()
{
return false;
}
{
return runnable;
}
}
/** All methods unused excepted scheduleWithFixedDelay. */
{
throws InterruptedException
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
throws InterruptedException
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
public boolean isShutdown()
{
return false;
}
public boolean isTerminated()
{
return false;
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
return future;
}
public void shutdown()
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
{
throw new UnsupportedOperationException();
}
};
/** {@inheritDoc} */
{
return new MockFactory(this, event);
}
/** {@inheritDoc} */
{
return mockScheduler;
}
/** {@inheritDoc} */
{
return currentTime;
}
/** {@inheritDoc} */
public long getCurrentTimeMS()
{
try
{
}
catch (DirectoryException e)
{
throw new RuntimeException(e);
}
}
{
}
@SuppressWarnings("unchecked")
{
if (expectedEvent == null)
{
}
else
{
}
}
{
return this;
}
{
this.currentTime = currentTime;
return this;
}
void runMonitorTasks()
{
{
}
}
}
final class MockServer
{
/** Waits for an incoming client connection. */
{
{
accept();
}
}
abstract class Action
{
}
/** Blocks the server until it is released. */
{
void release()
{
}
{
}
}
/** Close the client socket. */
{
{
}
}
/** Read the next message and check it matches the expected message. */
{
private final int messageID;
{
this.expectedOp = expectedOp;
}
{
// Read next message.
// Check message ID matches.
// Check protocol op matches.
}
}
/** Sends a message. */
{
private final int messageID;
{
}
{
}
}
private volatile Exception e;
{
this.serverSocket = serverSocket;
}
{
if (e != null)
{
throw e;
}
}
int getPort()
{
return serverSocket.getLocalPort();
}
{
{
/** {@inheritDoc} */
public void run()
{
{
try
{
}
catch (final Exception e)
{
MockServer.this.e = e;
break;
}
}
// Release test thread.
}
return this;
}
{
if (serverThread != null)
{
}
}
{
return this;
}
{
return this;
}
{
return this;
}
{
return this;
}
{
return this;
}
{
}
{
return socket;
}
{
return socket;
}
}
{
{
}
final ResultCode resultCode)
{
}
{
}
final ResultCode resultCode)
{
this.resultCode = resultCode;
}
/** {@inheritDoc} */
{
}
/** {@inheritDoc} */
{
if (event instanceof SearchEvent)
{
}
return false;
}
/** {@inheritDoc} */
{
return builder;
}
}
{
{
}
{
this.resultCode = resultCode;
}
/** {@inheritDoc} */
{
{
return new DirectoryException(resultCode,
resultCode.getName());
}
return null;
}
/** {@inheritDoc} */
{
if (event instanceof SimpleBindEvent)
{
}
return false;
}
/** {@inheritDoc} */
{
return builder;
}
}
/**
* Ensures that the Directory Server is running and creates a test backend
* containing a single test user.
*
* @throws Exception
* If an unexpected problem occurs.
*/
{
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"aduser: " + adDNString,
"uid: aduser"
/* @formatter:on */
);
}
/**
* Tests that failures during the search are handled properly.
* <p>
* Non-fatal errors (e.g. entry not found, too many entries returned) should
* not cause the search connection to be closed.
*
* @param searchResultCode
* The search result code.
* @throws Exception
* If an unexpected exception occurred.
*/
public void testConnectionFailureDuringSearch(
{
// Mock configuration.
// Create the provider and its list of expected events.
"(uid=aduser)", searchResultCode));
{
// The connection will fail and be closed immediately, and the pool will
// retry on new connection.
}
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
// There should be no more pending events.
// Cached connections should be closed when the policy is finalized.
if (!isServiceError(searchResultCode))
{
}
// Tear down and check final state.
}
/**
* Tests that failures to authenticate a search connection are handled
* properly.
* <p>
* Any kind of failure occurring while trying to authenticate a search
* connection should result in the connection being closed and periodically
* retried.
*
* @param bindResultCode
* The bind result code.
* @throws Exception
* If an unexpected exception occurred.
*/
public void testConnectionFailureDuringSearchBind(
{
// Mock configuration.
// Create the provider and its list of expected events.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
// Tear down and check final state.
}
/**
* Returns test data for {@link #testConnectionFailureDuringSearchBind}.
*
* @return Test data for {@link #testConnectionFailureDuringSearchBind}.
*/
{
// @formatter:off
return new Object[][] {
{ ResultCode.NO_SUCH_OBJECT },
};
// @formatter:on
}
/**
* Returns test data for {@link #testConnectionFailureDuringSearch}.
*
* @return Test data for {@link #testConnectionFailureDuringSearch}.
*/
{
// @formatter:off
return new Object[][] {
{ ResultCode.NO_SUCH_OBJECT },
};
// @formatter:on
}
/**
* Tests that failures to obtain a search connection are handled properly.
*
* @param connectResultCode
* The connection failure result code.
* @throws Exception
* If an unexpected exception occurred.
*/
public void testConnectionFailureDuringSearchGetConnection(
{
// Mock configuration.
// Create the provider and its list of expected events.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
// Tear down and check final state.
}
/**
* Returns test data for
* {@link #testConnectionFailureDuringSearchGetConnection}.
*
* @return Test data for
* {@link #testConnectionFailureDuringSearchGetConnection}.
*/
{
// @formatter:off
return new Object[][] {
};
// @formatter:on
}
/**
* Tests fail-over between 2 primary servers then to the secondary data
* center.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search, then bind.
"(uid=aduser)", adDNString))
// Repeat again using cached connection to shost1: search, then bind.
// phost1 and phost2 will have been marked as failed and won't be tried.
// Now simulate monitor thread run in which phost2 is determined to be
// available again.
// Connections should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate 3 times test above fail-over.
for (int i = 0; i < 3; i++)
{
// Run monitor which should try to connect to phost1&2 and determine that
// phost2 is available again.
if (i == 2)
{
}
// Perform authentication.
}
// Cached connections should be closed when the policy is finalized
// (primaries first, then secondaries).
// Tear down and check final state.
}
/**
* Tests that searches which fail in one LB pool are automatically retried in
* the secondary LB pool.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search (fail), and retry on shost1
"(uid=aduser)", adDNString))
// Now simulate shost1 going down as well.
// phost1 will have been marked as failed and won't be retried.
// 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.
// Connections should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate 3 times, second should fail.
for (int i = 0; i < 3; i++)
{
// Perform authentication.
switch (i)
{
case 0:
// First attempt should succeed.
break;
case 1:
// Second attempt should fail.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
break;
case 2:
// Third attempt should succeed, once the monitor has run.
break;
}
}
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* 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.
*/
public void testIsConfigurationAcceptable(
throws Exception
{
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory();
isValid);
}
/**
* Returns test data for {@link #testIsConfigurationAcceptable}.
*
* @return Test data for {@link #testIsConfigurationAcceptable}.
*/
{
// @formatter:off
return new Object[][] {
/* cfg, isValid */
// Test server configuration.
// Test mapped search parameters.
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindDN(null).withMappedSearchBindPassword(null), true },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPassword(null), false },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordProperty("org.opendj.dummy.property"), false },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordProperty("java.version"), true },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordEnvironmentVariable("ORG_OPENDJ_DUMMY_ENVVAR"), false },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordFile("dummy_file.txt"), false },
{ mockCfg().withMappingPolicy(MappingPolicy.MAPPED_SEARCH).withMappedSearchBindPasswordFile("config/admin-keystore.pin"), true },
};
// @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.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search (fail), and retry on phost2
"(uid=aduser)", adDNString))
// Now simulate phost2 going down as well.
// phost1 will have been marked as failed and won't be retried.
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.
// Note that the bind will be load-balanced.
"(uid=aduser)", adDNString))
// Connections should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate 3 times, second should fail.
for (int i = 0; i < 3; i++)
{
// Perform authentication.
switch (i)
{
case 0:
// First attempt should succeed.
break;
case 1:
// Second attempt should fail.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
break;
case 2:
// Third attempt should succeed, once the monitor has run.
break;
}
}
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Tests valid bind which times out at the client. These should trigger a
* CLIENT_SIDE_TIMEOUT result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
.withConnectionTimeout(500);
// Mock server.
// Test connect and close.
try
{
fail("Bind attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests valid bind which never receives a response because the server
* abruptly closes the connection.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
// Test connect and close.
try
{
fail("Bind attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests bind which receives a disconnect notification. The error result code
* should be passed back to the called.
*
* @throws Exception
* If an unexpected exception occurred.
*/
public void testLDAPConnectionFactoryBindDisconnectNotification()
throws Exception
{
// Mock configuration.
// Mock server.
.thenAccept()
// Test connect and close.
try
{
fail("Bind attempt should have failed");
}
catch (final DirectoryException e)
{
// Should be the result code sent by the server.
}
finally
{
}
}
/**
* Tests bind with invalid credentials which should return a
* INVALID_CREDENTIALS result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
public void testLDAPConnectionFactoryBindInvalidCredentials()
throws Exception
{
// Mock configuration.
// Mock server.
// Test connect and close.
try
{
fail("Bind attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests bind which returns an error result. The error result code should be
* passed back to the caller.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
// Test connect and close.
try
{
fail("Bind attempt should have failed");
}
catch (final DirectoryException e)
{
}
finally
{
}
}
/**
* Tests valid bind returning success.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
// Test connect and close.
try
{
}
finally
{
}
}
/**
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
// Test connect and close.
try
{
connection.close();
}
finally
{
}
}
/**
* Tests that invalid ports are handled properly. These should trigger a
* CLIENT_SIDE_CONNECT_ERROR result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Test connect and close.
try
{
fail("Connect attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests that unknown hosts are handled properly. These should trigger a
* CLIENT_SIDE_CONNECT_ERROR result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// FIXME: can we guarantee that "unknownhost" does not exist?
try
{
fail("Connect attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests valid search which times out at the client. These should trigger a
* CLIENT_SIDE_TIMEOUT result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
.withConnectionTimeout(500);
// Mock server.
// Test connect and close.
try
{
fail("Search attempt should have timed out");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests valid search which never receives a response because the server
* abruptly closes the connection.
*
* @throws Exception
* If an unexpected exception occurred.
*/
public void testLDAPConnectionFactorySearchConnectionClosed()
throws Exception
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests valid search which receives a disconnect notification. The error
* result code should be passed back to the called.
*
* @throws Exception
* If an unexpected exception occurred.
*/
public void testLDAPConnectionFactorySearchDisconnectNotification()
throws Exception
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
// Should be the result code sent by the server.
}
finally
{
}
}
/**
* Tests valid search returning no results are handled properly. These should
* trigger a CLIENT_SIDE_NO_RESULTS_RETURNED result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
}
finally
{
}
}
/**
* Tests search returning no entries and an error result. The error result
* code should be passed back to the caller.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
// Should be the result code sent by the server.
}
finally
{
}
}
/**
* Tests valid search returning a single entry followed by a size limit
* exceeded error are handled properly. These should trigger a
* CLIENT_SIDE_MORE_RESULTS_TO_RETURN result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
}
finally
{
}
}
/**
* Tests valid search returning a single entry works properly.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
}
finally
{
}
}
/**
* Tests valid search returning a single entry followed by a time limit
* exceeded error are handled properly. These should trigger a
* CLIENT_SIDE_MORE_RESULTS_TO_RETURN result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
e.getMessage());
}
finally
{
}
}
/**
* Tests valid search returning many results are handled properly. These
* should trigger a CLIENT_SIDE_MORE_RESULTS_TO_RETURN result code.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Mock server.
.thenAccept()
.thenReceive(1,
// Test connect and close.
try
{
fail("Search attempt should have failed");
}
catch (final DirectoryException e)
{
}
finally
{
}
}
/**
* Tests load balancing across 3 servers.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search, then bind.
"(uid=aduser)", adDNString))
// Get connection for phost2, then search, then bind.
"(uid=aduser)", adDNString))
// Get connection for phost3, then search, then bind.
"(uid=aduser)", adDNString))
// Repeat again using cached connection to phost1: search, then bind.
// Repeat again using cached connection to phost2: search, then bind.
// Repeat again using cached connection to phost3: search, then bind.
// Connections should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Cycle twice through the LB pool.
for (int i = 0; i < 6; i++)
{
// Perform authentication.
}
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* 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.
* <p>
* Non-fatal errors (e.g. entry not found) should not cause the bind
* connection to be closed.
*
* @param mappingPolicy
* The mapping policy.
* @param bindResultCode
* The bind result code.
* @throws Exception
* If an unexpected exception occurred.
*/
public void testMappingPolicyAuthentication(
throws Exception
{
// Mock configuration.
.withBaseDN("o=ad");
// Create the provider and its list of expected events.
// Add search events if doing a mapped search.
{
"(uid=aduser)", adDNString));
// Connection should be cached until the policy is finalized.
}
// Add bind events.
new SimpleBindEvent(ceBind,
if (isServiceError(bindResultCode))
{
// The connection will fail and be closed immediately, and the pool will
// retry on new connection.
}
// Connection should be cached until the policy is finalized or until the
// connection fails.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
switch (bindResultCode.asEnum())
{
case SUCCESS:
break;
case INVALID_CREDENTIALS:
break;
default:
try
{
fail("password match did not fail");
}
catch (final DirectoryException e)
{
// No valid connections available so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
break;
}
// There should be no more pending events.
// Cached connections should be closed when the policy is finalized.
{
}
if (!isServiceError(bindResultCode))
{
}
// Tear down and check final state.
}
/**
* Returns test data for {@link #testMappingPolicyAuthentication}.
*
* @return Test data for {@link #testMappingPolicyAuthentication}.
*/
{
// @formatter:off
return new Object[][] {
/* policy, bind result code */
};
// @formatter:on
}
/**
* Tests that mapped PTA fails when no match attribute values are found.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create the provider and its list of expected events.
// Obtain policy and state.
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"aduser: " + adDNString
/* @formatter:on */
);
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No mapping attributes so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
// There should be no more pending events.
// Tear down and check final state.
}
/**
* Tests that mapped PTA uses an appropriate filter when multiple match
* attributes are defined.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create the provider and its list of expected events.
// Add search events.
"(|(uid1=one)(uid2=two)(uid3=three))", adDNString));
// Connection should be cached until the policy is finalized.
// Add bind events.
// Connection should be cached until the policy is finalized.
// Obtain policy and state.
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"aduser: " + adDNString,
"uid1: one",
"uid2: two",
"uid3: three"
/* @formatter:on */
);
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
// There should be no more pending events.
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Tests that mapped PTA uses an appropriate filter when multiple match
* attribute values are found.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create the provider and its list of expected events.
// Add search events.
"(|(uid1=one)(uid1=two)(uid1=three))", adDNString));
// Connection should be cached until the policy is finalized.
// Add bind events.
// Connection should be cached until the policy is finalized.
// Obtain policy and state.
/* @formatter:off */
"dn: " + opendjDNString,
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"aduser: " + adDNString,
"uid1: one",
"uid1: two",
"uid1: three"
/* @formatter:on */
);
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
// There should be no more pending events.
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Tests that mapped PTA performs searches across multiple base DNs if
* configured.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create the provider and its list of expected events.
// Add search events.
"(uid=aduser)", adDNString));
// Connection should be cached until the policy is finalized.
// Add bind events.
// Connection should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform authentication.
// There should be no more pending events.
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Test for issue OPENDJ-292
* (https://bugster.forgerock.org/jira/browse/OPENDJ-292). This test checks
* that the last exception is correctly cached in the case where initial
* connection attempts fail.
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search, then bind.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate twice, the second time was causing a NPE because none of the
// factories were available and an attempt was made to throw a null
// exception.
for (int i = 0; i < 2; i++)
{
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No mapping attributes so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
}
// Tear down and check final state.
}
/**
* Test for issue OPENDJ-292
* (https://bugster.forgerock.org/jira/browse/OPENDJ-292). This test checks
* that the last exception is correctly cached in the case where a usable
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search, then bind.
"(uid=aduser)", adDNString))
// Repeat and fail-over to shost1.
"(uid=aduser)", adDNString))
// Repeat, but fail on shost1 as well, leaving no available servers.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate four times, the fourth time was causing a NPE because none
// of the factories were available and an attempt was made to throw a null
// exception.
for (int i = 0; i < 4; i++)
{
// Perform authentication.
if (i < 2)
{
}
else
{
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No mapping attributes so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
}
}
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Test for issue OPENDJ-294
* (https://bugster.forgerock.org/jira/browse/OPENDJ-294). Password
* configuration changes do not seem to be taking effect.
*
* @throws Exception
* If an unexpected exception occurred.
*/
@SuppressWarnings("unchecked")
{
// Mock configurations.
.withMappedSearchBindPassword("newSearchPassword");
// Create all the events.
// First of all the connection factories are created.
// Get connection for phost1, then search, then bind.
// Now simulate monitor thread run which will fail for the same reason.
// again.
// First of all the connection factories are created.
"newSearchPassword"))
"(uid=aduser)", adDNString))
// Re-run monitor - again nothing to do.
// Connections should be cached until the policy is finalized.
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Authenticate twice, correcting the configuration in between.
// Perform authentication.
try
{
fail("password match unexpectedly succeeded");
}
catch (final DirectoryException e)
{
// No mapping attributes so this should always fail with
// INVALID_CREDENTIALS.
e.getMessage());
}
// Run monitor which should try to connect to phost1 and fail again.
// Change the configuration, fixing the password.
// Run monitor which should now have nothing to do.
// Perform authentication, which should now succeed.
// Run monitor which should still have nothing to do.
// Cached connections should be closed when the policy is finalized.
// Tear down and check final state.
}
/**
* Test for issue OPENDJ-290
*
* @throws Exception
* If an unexpected exception occurred.
*/
{
// Mock configuration.
// Create all the events.
// First of all the connection factories are created.
// Get connection then bind twice creating two pooled connections.
// Once two pooled connections are created, retry but this time simulate the
// first connection failing. The attempt should automatically retry on a new
// connection.
// Use a connection pool directly for this test.
// Authenticate three times, the third time was failing because the pool
// would not retry the operation on a new connection.
// Don't release c1 because we want to force creation of a second connection.
// Release both the connections.
// This was failing because the pool would not retry with a new connection.
// There should be no more pending events.
// Cached connections should be closed when the pool is closed.
// Tear down and check final state.
}
/**
* Returns test data for {@link #testPasswordCaching}.
*
* @return Test data for {@link #testPasswordCaching}.
*/
{
// @formatter:off
return new Object[][] {
/* cacheState, matchesCache, matchesReal */
{ "notPresent", false, false },
{ "notPresent", false, true },
{ "notPresent", true, false },
{ "notPresent", true, true },
{ "presentValid", false, false },
{ "presentValid", false, true },
{ "presentValid", true, false },
{ "presentValid", true, true },
{ "presentExpired", false, false },
{ "presentExpired", false, true },
{ "presentExpired", true, false },
{ "presentExpired", true, true },
};
// @formatter:on
}
/**
* Tests password caching functionality.
*
* @param cacheState
* A string indicating the state of the cached password in the user's
* entry.
* @param matchesCache
* {@code true} if the presented password should match the cached
* password.
* @param matchesReal
* {@code true} if the presented password should match the real
* password.
* @throws Exception
* If an unexpected exception occurred.
*/
boolean matchesReal) throws Exception
{
// Create an empty test backend 'o=test'
// Choose arbitrary date.
final boolean expectPTA;
final boolean expectCacheInfo;
final boolean expectCacheUpdate;
final boolean expectedBindResultIsSuccess;
final String testCachedPasswordTime;
{
expectPTA = true;
/* @formatter:off */
"dn: cn=test user,o=test",
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user"
/* @formatter:on */
);
}
{
expectCacheInfo = true;
// Create an entry whose cached password is 10s old.
/* @formatter:off */
"dn: cn=test user,o=test",
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"ds-pta-cached-password: " + testCachedPassword,
"ds-pta-cached-password-time: " + testCachedPasswordTime
/* @formatter:on */
);
}
else
{
// presentExpired
expectPTA = true;
expectCacheInfo = true;
// Create an entry whose cached password is more than 1 day old.
/* @formatter:off */
"dn: cn=test user,o=test",
"objectClass: top",
"objectClass: person",
"sn: user",
"cn: test user",
"ds-pta-cached-password: " + testCachedPassword,
"ds-pta-cached-password-time: " + testCachedPasswordTime
/* @formatter:on */
);
}
final String presentedPassword;
if (matchesCache)
{
presentedPassword = "password";
}
else
{
presentedPassword = "doesNotMatchCache";
}
// Add the test entry.
// Mock configuration.
// Create all the events.
if (expectPTA)
{
// Get connection then bind twice creating two pooled connections.
}
// Obtain policy and state.
final LDAPPassThroughAuthenticationPolicyFactory factory = new LDAPPassThroughAuthenticationPolicyFactory(
provider);
// Perform the authentication.
// Check that the password has been cached if needed.
.valueOf("cn=test user,o=test"));
if (expectCacheInfo)
{
if (expectCacheUpdate)
{
}
else
{
}
}
else
{
}
// Tear down and check final state.
}
{
return new MockPolicyCfg();
}
{
return new MockServer(serverSocket);
}
throws LDAPException
{
}
{
}
final ResultCode resultCode)
{
}
throws DirectoryException
{
}
throws LDAPException
{
}
{
}
}