From 37718ae75138d7699fdc1da4e6cdbdd34b5dc95c Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 15 Sep 2011 11:00:33 +0000
Subject: [PATCH] Issue OPENDJ-262: Implement pass through authentication (PTA)
---
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyTestCase.java | 17
opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java | 1268 ++++++++++++++++++++++++++++-----------------------------
2 files changed, 629 insertions(+), 656 deletions(-)
diff --git a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
index dbd18eb..6283310 100644
--- a/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
+++ b/opends/src/server/org/opends/server/extensions/LDAPPassThroughAuthenticationPolicyFactory.java
@@ -76,6 +76,83 @@
// 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.
+
+ /**
+ * A factory which returns pre-authenticated connections for searches.
+ * <p>
+ * Package private for testing.
+ */
+ static final class AuthenticatedConnectionFactory implements
+ ConnectionFactory
+ {
+
+ private final ConnectionFactory factory;
+ private final DN username;
+ private final String password;
+
+
+
+ /**
+ * Creates a new authenticated connection factory which will bind on
+ * connect.
+ *
+ * @param factory
+ * The underlying connection factory whose connections are to be
+ * authenticated.
+ * @param username
+ * The username taken from the configuration.
+ * @param password
+ * The password taken from the configuration.
+ */
+ AuthenticatedConnectionFactory(final ConnectionFactory factory,
+ final DN username, final String password)
+ {
+ this.factory = factory;
+ this.username = username;
+ this.password = password;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ factory.close();
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Connection getConnection() throws DirectoryException
+ {
+ final Connection connection = factory.getConnection();
+ if (username != null && !username.isNullDN() && password != null
+ && password.length() > 0)
+ {
+ try
+ {
+ connection.simpleBind(ByteString.valueOf(username.toString()),
+ ByteString.valueOf(password));
+ }
+ catch (final DirectoryException e)
+ {
+ connection.close();
+ throw e;
+ }
+ }
+ return connection;
+ }
+
+ }
+
+
/**
* An LDAP connection which will be used in order to search for or
@@ -137,9 +214,19 @@
* a connection, perform a single operation (search or bind), and then close
* it.
*/
- static interface ConnectionFactory
+ static interface ConnectionFactory extends Closeable
{
/**
+ * {@inheritDoc}
+ * <p>
+ * Must never throw an exception.
+ */
+ @Override
+ void close();
+
+
+
+ /**
* Returns a connection which can be used in order to search for or
* authenticate users.
*
@@ -154,26 +241,275 @@
/**
- * An interface for obtaining a connection factory for LDAP connections to a
- * named LDAP server.
+ * PTA connection pool.
+ * <p>
+ * Package private for testing.
*/
- static interface LDAPConnectionFactoryProvider
+ static final class ConnectionPool implements ConnectionFactory, Closeable
{
+
/**
- * Returns a connection factory which can be used for obtaining connections
- * to the specified LDAP server.
- *
- * @param host
- * The LDAP server host name.
- * @param port
- * The LDAP server port.
- * @param options
- * The LDAP connection options.
- * @return A connection factory which can be used for obtaining connections
- * to the specified LDAP server.
+ * Pooled connection's intercept close and release connection back to the
+ * pool.
*/
- ConnectionFactory getLDAPConnectionFactory(String host, int port,
- LDAPPassThroughAuthenticationPolicyCfg options);
+ private final class PooledConnection implements Connection
+ {
+ private final Connection connection;
+ private boolean connectionIsClosed = false;
+
+
+
+ private PooledConnection(final Connection connection)
+ {
+ this.connection = connection;
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ if (!connectionIsClosed)
+ {
+ connectionIsClosed = true;
+
+ // Guarded by PolicyImpl
+ if (poolIsClosed)
+ {
+ connection.close();
+ }
+ else
+ {
+ connectionPool.offer(connection);
+ }
+ availableConnections.release();
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ByteString search(final DN baseDN, final SearchScope scope,
+ final SearchFilter filter) throws DirectoryException
+ {
+ try
+ {
+ return connection.search(baseDN, scope, filter);
+ }
+ catch (final DirectoryException e)
+ {
+ // Don't put the connection back in the pool if it has failed.
+ closeConnectionOnFatalError(e);
+ throw e;
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void simpleBind(final ByteString username,
+ final ByteString password) throws DirectoryException
+ {
+ try
+ {
+ connection.simpleBind(username, password);
+ }
+ catch (final DirectoryException e)
+ {
+ // Don't put the connection back in the pool if it has failed.
+ closeConnectionOnFatalError(e);
+ throw e;
+ }
+ }
+
+
+
+ private void closeConnectionOnFatalError(final DirectoryException e)
+ {
+ if (isFatalResultCode(e.getResultCode()))
+ {
+ if (!connectionIsClosed)
+ {
+ connectionIsClosed = true;
+ connection.close();
+ availableConnections.release();
+ }
+ }
+ }
+
+ }
+
+
+
+ // Guarded by PolicyImpl.lock.
+ private boolean poolIsClosed = false;
+
+ private final ConnectionFactory factory;
+ private final int poolSize = Runtime.getRuntime().availableProcessors() * 2;
+ private final Semaphore availableConnections = new Semaphore(poolSize);
+ private final Queue<Connection> connectionPool =
+ new ConcurrentLinkedQueue<Connection>();
+
+
+
+ /**
+ * Creates a new connection pool for the provided factory.
+ *
+ * @param factory
+ * The underlying connection factory whose connections are to be
+ * pooled.
+ */
+ ConnectionPool(final ConnectionFactory factory)
+ {
+ this.factory = factory;
+ }
+
+
+
+ /**
+ * Release all connections: do we want to block?
+ */
+ @Override
+ public void close()
+ {
+ // No need for synchronization as this can only be called with the
+ // policy's exclusive lock.
+ poolIsClosed = true;
+
+ Connection connection;
+ while ((connection = connectionPool.poll()) != null)
+ {
+ connection.close();
+ }
+
+ factory.close();
+
+ // Since we have the exclusive lock, there should be no more connections
+ // in use.
+ if (availableConnections.availablePermits() != poolSize)
+ {
+ throw new IllegalStateException(
+ "Pool has remaining connections open after close");
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Connection getConnection() throws DirectoryException
+ {
+ // This should only be called with the policy's shared lock.
+ if (poolIsClosed)
+ {
+ throw new IllegalStateException("pool is closed");
+ }
+
+ availableConnections.acquireUninterruptibly();
+
+ // There is either a pooled connection or we are allowed to create
+ // one.
+ Connection connection = connectionPool.poll();
+ if (connection == null)
+ {
+ try
+ {
+ connection = factory.getConnection();
+ }
+ catch (final DirectoryException e)
+ {
+ availableConnections.release();
+ throw e;
+ }
+ }
+
+ return new PooledConnection(connection);
+ }
+ }
+
+
+
+ /**
+ * A simplistic two-way fail-over connection factory implementation.
+ * <p>
+ * Package private for testing.
+ */
+ static final class FailoverConnectionFactory implements ConnectionFactory,
+ Closeable
+ {
+ private final ConnectionFactory primary;
+ private final ConnectionFactory secondary;
+
+
+
+ /**
+ * Creates a new fail-over connection factory which will always try the
+ * primary connection factory first, before trying the second.
+ *
+ * @param primary
+ * The primary connection factory.
+ * @param secondary
+ * The secondary connection factory.
+ */
+ FailoverConnectionFactory(final ConnectionFactory primary,
+ final ConnectionFactory secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+
+
+ /**
+ * Close underlying load-balancers.
+ */
+ @Override
+ public void close()
+ {
+ primary.close();
+ if (secondary != null)
+ {
+ secondary.close();
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Connection getConnection() throws DirectoryException
+ {
+ 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();
+ }
+ }
+ }
+
}
@@ -337,14 +673,14 @@
throw new DirectoryException(
ResultCode.CLIENT_SIDE_MORE_RESULTS_TO_RETURN,
ERR_LDAP_PTA_CONNECTION_SEARCH_SIZE_LIMIT.get(host, port,
- String.valueOf(options.dn()), String.valueOf(baseDN),
+ String.valueOf(cfg.dn()), String.valueOf(baseDN),
String.valueOf(filter)));
default:
// The search failed for some reason.
throw new DirectoryException(resultCode,
ERR_LDAP_PTA_CONNECTION_SEARCH_FAILED.get(host, port,
- String.valueOf(options.dn()), String.valueOf(baseDN),
+ String.valueOf(cfg.dn()), String.valueOf(baseDN),
String.valueOf(filter), resultCode.getIntValue(),
resultCode.getResultCodeName(),
searchResult.getErrorMessage()));
@@ -366,7 +702,7 @@
throw new DirectoryException(
ResultCode.CLIENT_SIDE_MORE_RESULTS_TO_RETURN,
ERR_LDAP_PTA_CONNECTION_SEARCH_SIZE_LIMIT.get(host, port,
- String.valueOf(options.dn()), String.valueOf(baseDN),
+ String.valueOf(cfg.dn()), String.valueOf(baseDN),
String.valueOf(filter)));
}
@@ -376,7 +712,7 @@
throw new DirectoryException(
ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED,
ERR_LDAP_PTA_CONNECTION_SEARCH_NO_MATCHES.get(host, port,
- String.valueOf(options.dn()), String.valueOf(baseDN),
+ String.valueOf(cfg.dn()), String.valueOf(baseDN),
String.valueOf(filter)));
}
@@ -418,7 +754,7 @@
// The bind failed for some reason.
throw new DirectoryException(resultCode,
ERR_LDAP_PTA_CONNECTION_BIND_FAILED.get(host, port,
- String.valueOf(options.dn()), String.valueOf(username),
+ String.valueOf(cfg.dn()), String.valueOf(username),
resultCode.getIntValue(), resultCode.getResultCodeName(),
bindResponse.getErrorMessage()));
}
@@ -436,23 +772,6 @@
* {@inheritDoc}
*/
@Override
- public String toString()
- {
- final StringBuilder builder = new StringBuilder();
- builder.append("LDAPConnection(");
- builder.append(String.valueOf(ldapSocket.getLocalSocketAddress()));
- builder.append(", ");
- builder.append(String.valueOf(ldapSocket.getRemoteSocketAddress()));
- builder.append(')');
- return builder.toString();
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
protected void finalize()
{
close();
@@ -474,7 +793,7 @@
{
throw new DirectoryException(ResultCode.valueOf(extendedResponse
.getResultCode()), ERR_LDAP_PTA_CONNECTION_DISCONNECTING.get(
- host, port, String.valueOf(options.dn()),
+ host, port, String.valueOf(cfg.dn()),
extendedResponse.getErrorMessage()));
}
}
@@ -482,7 +801,7 @@
// Unexpected response type.
throw new DirectoryException(ResultCode.CLIENT_SIDE_DECODING_ERROR,
ERR_LDAP_PTA_CONNECTION_WRONG_RESPONSE.get(host, port,
- String.valueOf(options.dn()),
+ String.valueOf(cfg.dn()),
String.valueOf(responseMessage.getProtocolOp())));
}
@@ -503,45 +822,45 @@
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_TIMEOUT,
ERR_LDAP_PTA_CONNECTION_TIMEOUT.get(host, port,
- String.valueOf(options.dn())), e);
+ String.valueOf(cfg.dn())), e);
}
else if (e.getCause() instanceof IOException)
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_LDAP_PTA_CONNECTION_OTHER_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
else
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_DECODING_ERROR,
ERR_LDAP_PTA_CONNECTION_DECODE_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
}
catch (final LDAPException e)
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_DECODING_ERROR,
ERR_LDAP_PTA_CONNECTION_DECODE_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
catch (final SocketTimeoutException e)
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_TIMEOUT,
ERR_LDAP_PTA_CONNECTION_TIMEOUT.get(host, port,
- String.valueOf(options.dn())), e);
+ String.valueOf(cfg.dn())), e);
}
catch (final IOException e)
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_LDAP_PTA_CONNECTION_OTHER_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
if (responseMessage == null)
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_LDAP_PTA_CONNECTION_CLOSED.get(host, port,
- String.valueOf(options.dn())));
+ String.valueOf(cfg.dn())));
}
return responseMessage;
}
@@ -562,7 +881,7 @@
{
throw new DirectoryException(ResultCode.CLIENT_SIDE_SERVER_DOWN,
ERR_LDAP_PTA_CONNECTION_OTHER_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
}
}
@@ -571,7 +890,7 @@
private final String host;
private final int port;
- private final LDAPPassThroughAuthenticationPolicyCfg options;
+ private final LDAPPassThroughAuthenticationPolicyCfg cfg;
private final int timeoutMS;
@@ -584,19 +903,19 @@
* The server host name.
* @param port
* The server port.
- * @param options
- * The options (SSL).
+ * @param cfg
+ * The configuration (for SSL).
*/
LDAPConnectionFactory(final String host, final int port,
- final LDAPPassThroughAuthenticationPolicyCfg options)
+ final LDAPPassThroughAuthenticationPolicyCfg cfg)
{
this.host = host;
this.port = port;
- this.options = options;
+ this.cfg = cfg;
// Normalize the timeoutMS to an integer (admin framework ensures that the
// value is non-negative).
- this.timeoutMS = (int) Math.min(options.getConnectionTimeout(),
+ this.timeoutMS = (int) Math.min(cfg.getConnectionTimeout(),
Integer.MAX_VALUE);
}
@@ -606,6 +925,17 @@
* {@inheritDoc}
*/
@Override
+ public void close()
+ {
+ // Nothing to do.
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public Connection getConnection() throws DirectoryException
{
try
@@ -624,24 +954,24 @@
try
{
- // Set ldapSocket options before connecting.
- plainSocket.setTcpNoDelay(options.isUseTCPNoDelay());
- plainSocket.setKeepAlive(options.isUseTCPKeepAlive());
+ // Set ldapSocket cfg before connecting.
+ plainSocket.setTcpNoDelay(cfg.isUseTCPNoDelay());
+ plainSocket.setKeepAlive(cfg.isUseTCPKeepAlive());
plainSocket.setSoTimeout(timeoutMS);
// Connect the ldapSocket.
plainSocket.connect(socketAddress, timeoutMS);
- if (options.isUseSSL())
+ if (cfg.isUseSSL())
{
// Obtain the optional configured trust manager which will be used
// in order to determine the trust of the remote LDAP server.
TrustManager[] tm = null;
- DN trustManagerDN = options.getTrustManagerProviderDN();
+ final DN trustManagerDN = cfg.getTrustManagerProviderDN();
if (trustManagerDN != null)
{
- TrustManagerProvider<?> trustManagerProvider = DirectoryServer
- .getTrustManagerProvider(trustManagerDN);
+ final TrustManagerProvider<?> trustManagerProvider =
+ DirectoryServer.getTrustManagerProvider(trustManagerDN);
if (trustManagerProvider != null)
{
tm = trustManagerProvider.getTrustManagers();
@@ -649,25 +979,26 @@
}
// Create the SSL context and initialize it.
- SSLContext sslContext = SSLContext.getInstance("TLS");
+ final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null /* key managers */, tm, null /* rng */);
// Create the SSL socket.
- SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
- SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
- plainSocket, host, port, true);
+ final SSLSocketFactory sslSocketFactory = sslContext
+ .getSocketFactory();
+ final SSLSocket sslSocket = (SSLSocket) sslSocketFactory
+ .createSocket(plainSocket, host, port, true);
ldapSocket = sslSocket;
sslSocket.setUseClientMode(true);
- if (!options.getSSLProtocol().isEmpty())
+ if (!cfg.getSSLProtocol().isEmpty())
{
- sslSocket.setEnabledProtocols(options.getSSLProtocol().toArray(
+ sslSocket.setEnabledProtocols(cfg.getSSLProtocol().toArray(
new String[0]));
}
- if (!options.getSSLCipherSuite().isEmpty())
+ if (!cfg.getSSLCipherSuite().isEmpty())
{
- sslSocket.setEnabledCipherSuites(options.getSSLCipherSuite()
- .toArray(new String[0]));
+ sslSocket.setEnabledCipherSuites(cfg.getSSLCipherSuite().toArray(
+ new String[0]));
}
// Force TLS negotiation.
@@ -735,7 +1066,7 @@
}
throw new DirectoryException(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
ERR_LDAP_PTA_CONNECT_UNKNOWN_HOST.get(host, port,
- String.valueOf(options.dn()), host), e);
+ String.valueOf(cfg.dn()), host), e);
}
catch (final ConnectException e)
{
@@ -745,7 +1076,7 @@
}
throw new DirectoryException(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
ERR_LDAP_PTA_CONNECT_ERROR.get(host, port,
- String.valueOf(options.dn()), port), e);
+ String.valueOf(cfg.dn()), port), e);
}
catch (final SocketTimeoutException e)
{
@@ -755,7 +1086,7 @@
}
throw new DirectoryException(ResultCode.CLIENT_SIDE_TIMEOUT,
ERR_LDAP_PTA_CONNECT_TIMEOUT.get(host, port,
- String.valueOf(options.dn())), e);
+ String.valueOf(cfg.dn())), e);
}
catch (final SSLException e)
{
@@ -765,7 +1096,7 @@
}
throw new DirectoryException(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
ERR_LDAP_PTA_CONNECT_SSL_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
catch (final Exception e)
{
@@ -775,10 +1106,77 @@
}
throw new DirectoryException(ResultCode.CLIENT_SIDE_CONNECT_ERROR,
ERR_LDAP_PTA_CONNECT_OTHER_ERROR.get(host, port,
- String.valueOf(options.dn()), e.getMessage()), e);
+ String.valueOf(cfg.dn()), e.getMessage()), e);
}
}
+ }
+
+
+
+ /**
+ * An interface for obtaining a connection factory for LDAP connections to a
+ * named LDAP server.
+ */
+ static interface LDAPConnectionFactoryProvider
+ {
+ /**
+ * Returns a connection factory which can be used for obtaining connections
+ * to the specified LDAP server.
+ *
+ * @param host
+ * The LDAP server host name.
+ * @param port
+ * The LDAP server port.
+ * @param cfg
+ * The LDAP connection configuration.
+ * @return A connection factory which can be used for obtaining connections
+ * to the specified LDAP server.
+ */
+ ConnectionFactory getLDAPConnectionFactory(String host, int port,
+ LDAPPassThroughAuthenticationPolicyCfg cfg);
+ }
+
+
+
+ /**
+ * A simplistic load-balancer connection factory implementation using
+ * approximately round-robin balancing.
+ */
+ static final class LoadBalancer implements ConnectionFactory, Closeable
+ {
+ private final ConnectionFactory[] factories;
+ private final AtomicInteger nextIndex = new AtomicInteger();
+ private final int maxIndex;
+
+
+
+ /**
+ * Creates a new load-balancer which will distribute connection requests
+ * across a set of underlying connection factories.
+ *
+ * @param factories
+ * The list of underlying connection factories.
+ */
+ LoadBalancer(final ConnectionFactory[] factories)
+ {
+ this.factories = factories;
+ this.maxIndex = factories.length;
+ }
+
+
+
+ /**
+ * Close underlying connection pools.
+ */
+ @Override
+ public void close()
+ {
+ for (final ConnectionFactory factory : factories)
+ {
+ factory.close();
+ }
+ }
@@ -786,16 +1184,68 @@
* {@inheritDoc}
*/
@Override
- public String toString()
+ public Connection getConnection() throws DirectoryException
{
- final StringBuilder builder = new StringBuilder();
- builder.append("LDAPConnectionFactory(");
- builder.append(host);
- builder.append(':');
- builder.append(port);
- builder.append(')');
- return builder.toString();
+ 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()
+ {
+ // A round robin pool of one connection factories is unlikely in
+ // practice and requires special treatment.
+ if (maxIndex == 1)
+ {
+ return 0;
+ }
+
+ // Determine the next factory to use: avoid blocking algorithm.
+ int oldNextIndex;
+ int newNextIndex;
+ do
+ {
+ oldNextIndex = nextIndex.get();
+ newNextIndex = oldNextIndex + 1;
+ if (newNextIndex == maxIndex)
+ {
+ newNextIndex = 0;
+ }
+ }
+ while (!nextIndex.compareAndSet(oldNextIndex, newNextIndex));
+
+ // There's a potential, but benign, race condition here: other threads
+ // could jump in and rotate through the list before we return the
+ // connection factory.
+ return oldNextIndex;
+ }
+
}
@@ -808,485 +1258,6 @@
{
/**
- * A factory which returns pre-authenticated connections for searches.
- */
- private final class AuthenticatedConnectionFactory implements
- ConnectionFactory
- {
-
- private final ConnectionFactory factory;
-
-
-
- private AuthenticatedConnectionFactory(final ConnectionFactory factory)
- {
- this.factory = factory;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Connection getConnection() throws DirectoryException
- {
- final DN username = configuration.getMappedSearchBindDN();
- final String password = configuration.getMappedSearchBindPassword();
-
- final Connection connection = factory.getConnection();
- if (username != null && !username.isNullDN() && password != null
- && password.length() > 0)
- {
- try
- {
- connection.simpleBind(ByteString.valueOf(username.toString()),
- ByteString.valueOf(password));
- }
- catch (final DirectoryException e)
- {
- connection.close();
- throw e;
- }
- }
- return connection;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString()
- {
- final StringBuilder builder = new StringBuilder();
- builder.append("AuthenticationConnectionFactory(");
- builder.append(factory);
- builder.append(')');
- return builder.toString();
- }
-
- }
-
-
-
- /**
- * PTA connection pool.
- */
- private final class ConnectionPool implements ConnectionFactory, Closeable
- {
-
- /**
- * Pooled connection's intercept close and release connection back to the
- * pool.
- */
- private final class PooledConnection implements Connection
- {
- private final Connection connection;
- private boolean connectionIsClosed = false;
-
-
-
- private PooledConnection(final Connection connection)
- {
- this.connection = connection;
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close()
- {
- if (!connectionIsClosed)
- {
- connectionIsClosed = true;
-
- // Guarded by PolicyImpl
- if (poolIsClosed)
- {
- connection.close();
- }
- else
- {
- connectionPool.offer(connection);
- }
- availableConnections.release();
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ByteString search(final DN baseDN, final SearchScope scope,
- final SearchFilter filter) throws DirectoryException
- {
- try
- {
- return connection.search(baseDN, scope, filter);
- }
- catch (final DirectoryException e)
- {
- // Don't put the connection back in the pool if it has failed.
- closeConnectionOnFatalError(e);
- throw e;
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void simpleBind(final ByteString username,
- final ByteString password) throws DirectoryException
- {
- try
- {
- connection.simpleBind(username, password);
- }
- catch (final DirectoryException e)
- {
- // Don't put the connection back in the pool if it has failed.
- closeConnectionOnFatalError(e);
- throw e;
- }
- }
-
-
-
- private void closeConnectionOnFatalError(final DirectoryException e)
- {
- if (isFatalResultCode(e.getResultCode()))
- {
- if (!connectionIsClosed)
- {
- connectionIsClosed = true;
- connection.close();
- availableConnections.release();
- }
- }
- }
-
- }
-
-
-
- // Guarded by PolicyImpl.lock.
- private boolean poolIsClosed = false;
-
- private final ConnectionFactory factory;
- private final int poolSize =
- Runtime.getRuntime().availableProcessors() * 2;
- private final Semaphore availableConnections = new Semaphore(poolSize);
- private final ConcurrentLinkedQueue<Connection> connectionPool =
- new ConcurrentLinkedQueue<Connection>();
-
-
-
- private ConnectionPool(final ConnectionFactory factory)
- {
- this.factory = factory;
- }
-
-
-
- /**
- * Release all connections: do we want to block?
- */
- @Override
- public void close()
- {
- // No need for synchronization as this can only be called with the
- // policy's exclusive lock.
- poolIsClosed = true;
-
- Connection connection;
- while ((connection = connectionPool.poll()) != null)
- {
- connection.close();
- }
-
- // Since we have the exclusive lock, there should be no more connections
- // in use.
- if (availableConnections.availablePermits() != poolSize)
- {
- throw new IllegalStateException(
- "Pool has remaining connections open after close");
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Connection getConnection() throws DirectoryException
- {
- // This should only be called with the policy's shared lock.
- if (poolIsClosed)
- {
- throw new IllegalStateException("pool is closed");
- }
-
- availableConnections.acquireUninterruptibly();
-
- // There is either a pooled connection or we are allowed to create
- // one.
- Connection connection = connectionPool.poll();
- if (connection == null)
- {
- try
- {
- connection = factory.getConnection();
- }
- catch (final DirectoryException e)
- {
- availableConnections.release();
- throw e;
- }
- }
-
- return new PooledConnection(connection);
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString()
- {
- final StringBuilder builder = new StringBuilder();
- builder.append("ConnectionPool(");
- builder.append(factory);
- builder.append(", poolSize=");
- builder.append(poolSize);
- builder.append(", inPool=");
- builder.append(connectionPool.size());
- builder.append(", available=");
- builder.append(availableConnections.availablePermits());
- builder.append(')');
- return builder.toString();
- }
- }
-
-
-
- /**
- * A simplistic two-way fail-over connection factory implementation.
- */
- private final class FailoverConnectionFactory implements ConnectionFactory,
- Closeable
- {
- private final LoadBalancer primary;
- private final LoadBalancer secondary;
-
-
-
- private FailoverConnectionFactory(final LoadBalancer primary,
- final LoadBalancer secondary)
- {
- this.primary = primary;
- this.secondary = secondary;
- }
-
-
-
- /**
- * Close underlying load-balancers.
- */
- @Override
- public void close()
- {
- primary.close();
- if (secondary != null)
- {
- secondary.close();
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Connection getConnection() throws DirectoryException
- {
- 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();
- }
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString()
- {
- final StringBuilder builder = new StringBuilder();
- builder.append("FailoverConnectionFactory(");
- builder.append(primary);
- builder.append(", ");
- builder.append(secondary);
- builder.append(')');
- return builder.toString();
- }
-
- }
-
-
-
- /**
- * A simplistic load-balancer connection factory implementation using
- * approximately round-robin balancing.
- */
- private final class LoadBalancer implements ConnectionFactory, Closeable
- {
- private final ConnectionPool[] factories;
- private final AtomicInteger nextIndex = new AtomicInteger();
- private final int maxIndex;
-
-
-
- private LoadBalancer(final ConnectionPool[] factories)
- {
- this.factories = factories;
- this.maxIndex = factories.length;
- }
-
-
-
- /**
- * Close underlying connection pools.
- */
- @Override
- public void close()
- {
- for (final ConnectionPool pool : factories)
- {
- pool.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;
- }
- }
- }
- }
-
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString()
- {
- final StringBuilder builder = new StringBuilder();
- builder.append("LoadBalancer(");
- builder.append(nextIndex);
- for (final ConnectionFactory factory : factories)
- {
- builder.append(", ");
- builder.append(factory);
- }
- builder.append(')');
- return builder.toString();
- }
-
-
-
- // Determine the start index.
- private int getStartIndex()
- {
- // A round robin pool of one connection factories is unlikely in
- // practice and requires special treatment.
- if (maxIndex == 1)
- {
- return 0;
- }
-
- // Determine the next factory to use: avoid blocking algorithm.
- int oldNextIndex;
- int newNextIndex;
- do
- {
- oldNextIndex = nextIndex.get();
- newNextIndex = oldNextIndex + 1;
- if (newNextIndex == maxIndex)
- {
- newNextIndex = 0;
- }
- }
- while (!nextIndex.compareAndSet(oldNextIndex, newNextIndex));
-
- // There's a potential, but benign, race condition here: other threads
- // could jump in and rotate through the list before we return the
- // connection factory.
- return oldNextIndex;
- }
-
- }
-
-
-
- /**
* LDAP PTA policy state implementation.
*/
private final class StateImpl extends AuthenticationPolicyState
@@ -1344,7 +1315,7 @@
// directory.
ByteString username = null;
- switch (configuration.getMappingPolicy())
+ switch (cfg.getMappingPolicy())
{
case UNMAPPED:
// The bind DN is the name of the user's entry.
@@ -1352,8 +1323,7 @@
break;
case MAPPED_BIND:
// The bind DN is contained in an attribute in the user's entry.
- mapBind: for (final AttributeType at : configuration
- .getMappedAttribute())
+ mapBind: for (final AttributeType at : cfg.getMappedAttribute())
{
final List<Attribute> attributes = userEntry.getAttribute(at);
if (attributes != null && !attributes.isEmpty())
@@ -1378,10 +1348,10 @@
* references a non-user entry.
*/
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
- ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(String
- .valueOf(userEntry.getDN()), String.valueOf(configuration
- .dn()), mappedAttributesAsString(configuration
- .getMappedAttribute())));
+ ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(
+ String.valueOf(userEntry.getDN()),
+ String.valueOf(cfg.dn()),
+ mappedAttributesAsString(cfg.getMappedAttribute())));
}
break;
@@ -1392,7 +1362,7 @@
// Construct the search filter.
final LinkedList<SearchFilter> filterComponents =
new LinkedList<SearchFilter>();
- for (final AttributeType at : configuration.getMappedAttribute())
+ for (final AttributeType at : cfg.getMappedAttribute())
{
final List<Attribute> attributes = userEntry.getAttribute(at);
if (attributes != null && !attributes.isEmpty())
@@ -1417,10 +1387,10 @@
* references a non-user entry.
*/
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
- ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(String
- .valueOf(userEntry.getDN()), String.valueOf(configuration
- .dn()), mappedAttributesAsString(configuration
- .getMappedAttribute())));
+ ERR_LDAP_PTA_MAPPING_ATTRIBUTE_NOT_FOUND.get(
+ String.valueOf(userEntry.getDN()),
+ String.valueOf(cfg.dn()),
+ mappedAttributesAsString(cfg.getMappedAttribute())));
}
final SearchFilter filter;
@@ -1435,7 +1405,7 @@
// Now search the configured base DNs, stopping at the first
// success.
- for (final DN baseDN : configuration.getMappedSearchBaseDN())
+ for (final DN baseDN : cfg.getMappedSearchBaseDN())
{
Connection connection = null;
try
@@ -1457,8 +1427,8 @@
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
ERR_LDAP_PTA_MAPPED_SEARCH_TOO_MANY_CANDIDATES.get(
String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()),
- String.valueOf(baseDN), String.valueOf(filter)));
+ String.valueOf(cfg.dn()), String.valueOf(baseDN),
+ String.valueOf(filter)));
default:
// We don't want to propagate this internal error to the
// client. We should log it and map it to a more appropriate
@@ -1466,8 +1436,7 @@
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
ERR_LDAP_PTA_MAPPED_SEARCH_FAILED.get(
String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()),
- e.getMessageObject()), e);
+ String.valueOf(cfg.dn()), e.getMessageObject()), e);
}
}
finally
@@ -1487,8 +1456,7 @@
throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
ERR_LDAP_PTA_MAPPED_SEARCH_NO_CANDIDATES.get(
String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()),
- String.valueOf(filter)));
+ String.valueOf(cfg.dn()), String.valueOf(filter)));
}
break;
@@ -1513,12 +1481,10 @@
// We don't want to propagate this internal error to the
// client. We should log it and map it to a more appropriate
// error.
- throw new DirectoryException(
- ResultCode.INVALID_CREDENTIALS,
+ throw new DirectoryException(ResultCode.INVALID_CREDENTIALS,
ERR_LDAP_PTA_MAPPED_BIND_FAILED.get(
String.valueOf(userEntry.getDN()),
- String.valueOf(configuration.dn()), e.getMessageObject()),
- e);
+ String.valueOf(cfg.dn()), e.getMessageObject()), e);
}
}
finally
@@ -1544,7 +1510,7 @@
private final WriteLock exclusiveLock = lock.writeLock();
// Current configuration.
- private LDAPPassThroughAuthenticationPolicyCfg configuration;
+ private LDAPPassThroughAuthenticationPolicyCfg cfg;
private FailoverConnectionFactory searchFactory = null;
private FailoverConnectionFactory bindFactory = null;
@@ -1564,13 +1530,13 @@
*/
@Override
public ConfigChangeResult applyConfigurationChange(
- final LDAPPassThroughAuthenticationPolicyCfg configuration)
+ final LDAPPassThroughAuthenticationPolicyCfg cfg)
{
exclusiveLock.lock();
try
{
closeConnections();
- initializeConfiguration(configuration);
+ initializeConfiguration(cfg);
}
finally
{
@@ -1603,7 +1569,7 @@
exclusiveLock.lock();
try
{
- configuration.removeLDAPPassThroughChangeListener(this);
+ cfg.removeLDAPPassThroughChangeListener(this);
closeConnections();
}
finally
@@ -1620,7 +1586,7 @@
@Override
public DN getDN()
{
- return configuration.dn();
+ return cfg.dn();
}
@@ -1630,11 +1596,11 @@
*/
@Override
public boolean isConfigurationChangeAcceptable(
- final LDAPPassThroughAuthenticationPolicyCfg configuration,
+ final LDAPPassThroughAuthenticationPolicyCfg cfg,
final List<Message> unacceptableReasons)
{
return LDAPPassThroughAuthenticationPolicyFactory.this
- .isConfigurationAcceptable(configuration, unacceptableReasons);
+ .isConfigurationAcceptable(cfg, unacceptableReasons);
}
@@ -1666,9 +1632,9 @@
private void initializeConfiguration(
- final LDAPPassThroughAuthenticationPolicyCfg configuration)
+ final LDAPPassThroughAuthenticationPolicyCfg cfg)
{
- this.configuration = configuration;
+ this.cfg = cfg;
// Use two pools per server: one for authentication (bind) and one for
// searches. Even if the searches are performed anonymously we cannot use
@@ -1679,7 +1645,7 @@
final LoadBalancer primarySearchLoadBalancer;
final LoadBalancer primaryBindLoadBalancer;
- Set<String> servers = configuration.getPrimaryRemoteLDAPServer();
+ Set<String> servers = cfg.getPrimaryRemoteLDAPServer();
ConnectionPool[] searchPool = new ConnectionPool[servers.size()];
ConnectionPool[] bindPool = new ConnectionPool[servers.size()];
int index = 0;
@@ -1687,7 +1653,9 @@
{
final ConnectionFactory factory = newLDAPConnectionFactory(hostPort);
searchPool[index] = new ConnectionPool(
- new AuthenticatedConnectionFactory(factory));
+ new AuthenticatedConnectionFactory(factory,
+ cfg.getMappedSearchBindDN(),
+ cfg.getMappedSearchBindPassword()));
bindPool[index++] = new ConnectionPool(factory);
}
primarySearchLoadBalancer = new LoadBalancer(searchPool);
@@ -1697,7 +1665,7 @@
final LoadBalancer secondarySearchLoadBalancer;
final LoadBalancer secondaryBindLoadBalancer;
- servers = configuration.getSecondaryRemoteLDAPServer();
+ servers = cfg.getSecondaryRemoteLDAPServer();
if (servers.isEmpty())
{
secondarySearchLoadBalancer = null;
@@ -1712,7 +1680,9 @@
{
final ConnectionFactory factory = newLDAPConnectionFactory(hostPort);
searchPool[index] = new ConnectionPool(
- new AuthenticatedConnectionFactory(factory));
+ new AuthenticatedConnectionFactory(factory,
+ cfg.getMappedSearchBindDN(),
+ cfg.getMappedSearchBindPassword()));
bindPool[index++] = new ConnectionPool(factory);
}
secondarySearchLoadBalancer = new LoadBalancer(searchPool);
@@ -1733,7 +1703,7 @@
final int colonIndex = hostPort.lastIndexOf(":");
final String hostname = hostPort.substring(0, colonIndex);
final int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
- return provider.getLDAPConnectionFactory(hostname, port, configuration);
+ return provider.getLDAPConnectionFactory(hostname, port, cfg);
}
}
@@ -1766,9 +1736,9 @@
@Override
public ConnectionFactory getLDAPConnectionFactory(final String host,
- final int port, final LDAPPassThroughAuthenticationPolicyCfg options)
+ final int port, final LDAPPassThroughAuthenticationPolicyCfg cfg)
{
- return new LDAPConnectionFactory(host, port, options);
+ return new LDAPConnectionFactory(host, port, cfg);
}
};
@@ -1809,6 +1779,51 @@
+ private static boolean isServerAddressValid(
+ final LDAPPassThroughAuthenticationPolicyCfg configuration,
+ final List<Message> unacceptableReasons, final String hostPort)
+ {
+ final int colonIndex = hostPort.lastIndexOf(":");
+ final int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
+ if (port < 1 || port > 65535)
+ {
+ if (unacceptableReasons != null)
+ {
+ final Message msg = ERR_LDAP_PTA_INVALID_PORT_NUMBER.get(
+ String.valueOf(configuration.dn()), hostPort);
+ unacceptableReasons.add(msg);
+ }
+ return false;
+ }
+ return true;
+ }
+
+
+
+ private static String mappedAttributesAsString(
+ final Collection<AttributeType> attributes)
+ {
+ switch (attributes.size())
+ {
+ case 0:
+ return "";
+ case 1:
+ return attributes.iterator().next().getNameOrOID();
+ default:
+ final StringBuilder builder = new StringBuilder();
+ final Iterator<AttributeType> i = attributes.iterator();
+ builder.append(i.next().getNameOrOID());
+ while (i.hasNext())
+ {
+ builder.append(", ");
+ builder.append(i.next().getNameOrOID());
+ }
+ return builder.toString();
+ }
+ }
+
+
+
/**
* Public default constructor used by the admin framework. This will use the
* default LDAP connection factory provider.
@@ -1864,13 +1879,13 @@
// capabilities).
boolean configurationIsAcceptable = true;
- for (String hostPort : configuration.getPrimaryRemoteLDAPServer())
+ for (final String hostPort : configuration.getPrimaryRemoteLDAPServer())
{
configurationIsAcceptable &= isServerAddressValid(configuration,
unacceptableReasons, hostPort);
}
- for (String hostPort : configuration.getSecondaryRemoteLDAPServer())
+ for (final String hostPort : configuration.getSecondaryRemoteLDAPServer())
{
configurationIsAcceptable &= isServerAddressValid(configuration,
unacceptableReasons, hostPort);
@@ -1878,49 +1893,4 @@
return configurationIsAcceptable;
}
-
-
-
- private static boolean isServerAddressValid(
- final LDAPPassThroughAuthenticationPolicyCfg configuration,
- final List<Message> unacceptableReasons, String hostPort)
- {
- final int colonIndex = hostPort.lastIndexOf(":");
- final int port = Integer.parseInt(hostPort.substring(colonIndex + 1));
- if (port < 1 || port > 65535)
- {
- if (unacceptableReasons != null)
- {
- Message msg = ERR_LDAP_PTA_INVALID_PORT_NUMBER.get(
- String.valueOf(configuration.dn()), hostPort);
- unacceptableReasons.add(msg);
- }
- return false;
- }
- return true;
- }
-
-
-
- private static String mappedAttributesAsString(
- Collection<AttributeType> attributes)
- {
- switch (attributes.size())
- {
- case 0:
- return "";
- case 1:
- return attributes.iterator().next().getNameOrOID();
- default:
- StringBuilder builder = new StringBuilder();
- Iterator<AttributeType> i = attributes.iterator();
- builder.append(i.next().getNameOrOID());
- while (i.hasNext())
- {
- builder.append(", ");
- builder.append(i.next().getNameOrOID());
- }
- return builder.toString();
- }
- }
}
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 d6fe354..d43921d 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
@@ -403,6 +403,16 @@
}
}
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close()
+ {
+ // Nothing to do.
+ }
+
}
@@ -1200,22 +1210,15 @@
private final String phost2 = "phost2:22";
private final String phost3 = "phost3:33";
private final String shost1 = "shost1:11";
- private final String shost2 = "shost2:22";
-
- private final String shost3 = "shost3:33";
private DN policyDN;
-
private final String policyDNString = "cn=test policy,o=test";
private DN searchBindDN;
-
private final String searchBindDNString = "cn=search bind dn";
private DN trustManagerDN;
-
private final String trustManagerDNString = "cn=ignored";
private final String adDNString = "uid=aduser,o=ad";
private final String opendjDNString = "cn=test user,o=opendj";
private Entry userEntry;
-
private final String userPassword = "password";
--
Gitblit v1.10.0