Merge staging/1.1.0 changes back to master.
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
index c047b91..e6b79e1 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
@@ -154,7 +154,7 @@
// Always disconnect
finally {
- ldapService.disconnect(searchConnection);
+ searchConnection.close();
}
}
@@ -226,7 +226,7 @@
// Always disconnect
finally {
- ldapService.disconnect(ldapConnection);
+ ldapConnection.close();
}
}
@@ -335,7 +335,7 @@
// Always disconnect
finally {
- ldapService.disconnect(ldapConnection);
+ ldapConnection.close();
}
}
return null;
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
index 67ed4de..a16c7de 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/LDAPConnectionService.java
@@ -20,19 +20,15 @@
package org.apache.guacamole.auth.ldap;
import com.google.inject.Inject;
-import java.io.IOException;
+import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.filter.ExprNode;
-import org.apache.directory.api.ldap.model.message.BindRequest;
-import org.apache.directory.api.ldap.model.message.BindRequestImpl;
-import org.apache.directory.api.ldap.model.message.BindResponse;
-import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.url.LdapUrl;
-import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
@@ -61,41 +57,58 @@
/**
* Creates a new instance of LdapNetworkConnection, configured as required
- * to use whichever encryption method is requested within
- * guacamole.properties.
+ * to use the given encryption method to communicate with the LDAP server
+ * at the given hostname and port. The returned LdapNetworkConnection is
+ * configured for use but is not yet connected nor bound to the LDAP
+ * server. It will not be bound until a bind operation is explicitly
+ * requested, and will not be connected until it is used in an LDAP
+ * operation (such as a bind).
+ *
+ * @param host
+ * The hostname or IP address of the LDAP server.
+ *
+ * @param port
+ * The TCP port that the LDAP server is listening on.
+ *
+ * @param encryptionMethod
+ * The encryption method that should be used to communicate with the
+ * LDAP server.
*
* @return
- * A new LdapNetworkConnection instance which has already been
- * configured to use the encryption method requested within
- * guacamole.properties.
+ * A new instance of LdapNetworkConnection which uses the given
+ * encryption method to communicate with the LDAP server at the given
+ * hostname and port.
*
* @throws GuacamoleException
- * If an error occurs while parsing guacamole.properties, or if the
- * requested encryption method is actually not implemented (a bug).
+ * If the requested encryption method is actually not implemented (a
+ * bug).
*/
- private LdapNetworkConnection createLDAPConnection() throws GuacamoleException {
+ private LdapNetworkConnection createLDAPConnection(String host, int port,
+ EncryptionMethod encryptionMethod) throws GuacamoleException {
- String host = confService.getServerHostname();
- int port = confService.getServerPort();
-
+ LdapConnectionConfig config = new LdapConnectionConfig();
+ config.setLdapHost(host);
+ config.setLdapPort(port);
+
// Map encryption method to proper connection and socket factory
- EncryptionMethod encryptionMethod = confService.getEncryptionMethod();
switch (encryptionMethod) {
// Unencrypted LDAP connection
case NONE:
logger.debug("Connection to LDAP server without encryption.");
- return new LdapNetworkConnection(host, port);
+ break;
// LDAP over SSL (LDAPS)
case SSL:
logger.debug("Connecting to LDAP server using SSL/TLS.");
- return new LdapNetworkConnection(host, port, true);
+ config.setUseSsl(true);
+ break;
// LDAP + STARTTLS
case STARTTLS:
logger.debug("Connecting to LDAP server using STARTTLS.");
- return new LdapNetworkConnection(host, port);
+ config.setUseTls(true);
+ break;
// The encryption method, though known, is not actually
// implemented. If encountered, this would be a bug.
@@ -104,10 +117,215 @@
}
+ return new LdapNetworkConnection(config);
+
}
/**
- * Binds to the LDAP server using the provided user DN and password.
+ * Creates a new instance of LdapNetworkConnection, configured as required
+ * to use whichever encryption method, hostname, and port are requested
+ * within guacamole.properties. The returned LdapNetworkConnection is
+ * configured for use but is not yet connected nor bound to the LDAP
+ * server. It will not be bound until a bind operation is explicitly
+ * requested, and will not be connected until it is used in an LDAP
+ * operation (such as a bind).
+ *
+ * @return
+ * A new LdapNetworkConnection instance which has already been
+ * configured to use the encryption method, hostname, and port
+ * requested within guacamole.properties.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while parsing guacamole.properties, or if the
+ * requested encryption method is actually not implemented (a bug).
+ */
+ private LdapNetworkConnection createLDAPConnection()
+ throws GuacamoleException {
+ return createLDAPConnection(
+ confService.getServerHostname(),
+ confService.getServerPort(),
+ confService.getEncryptionMethod());
+ }
+
+ /**
+ * Creates a new instance of LdapNetworkConnection, configured as required
+ * to use whichever encryption method, hostname, and port are specified
+ * within the given LDAP URL. The returned LdapNetworkConnection is
+ * configured for use but is not yet connected nor bound to the LDAP
+ * server. It will not be bound until a bind operation is explicitly
+ * requested, and will not be connected until it is used in an LDAP
+ * operation (such as a bind).
+ *
+ * @param url
+ * The LDAP URL containing the details which should be used to connect
+ * to the LDAP server.
+ *
+ * @return
+ * A new LdapNetworkConnection instance which has already been
+ * configured to use the encryption method, hostname, and port
+ * specified within the given LDAP URL.
+ *
+ * @throws GuacamoleException
+ * If the given URL is not a valid LDAP URL, or if the encryption
+ * method indicated by the URL is known but not actually implemented (a
+ * bug).
+ */
+ private LdapNetworkConnection createLDAPConnection(String url)
+ throws GuacamoleException {
+
+ // Parse provided LDAP URL
+ LdapUrl ldapUrl;
+ try {
+ ldapUrl = new LdapUrl(url);
+ }
+ catch (LdapException e) {
+ logger.debug("Cannot connect to LDAP URL \"{}\": URL is invalid.", url, e);
+ throw new GuacamoleServerException("Invalid LDAP URL.", e);
+ }
+
+ // Retrieve hostname from URL, bailing out if no hostname is present
+ String host = ldapUrl.getHost();
+ if (host == null || host.isEmpty()) {
+ logger.debug("Cannot connect to LDAP URL \"{}\": no hostname is present.", url);
+ throw new GuacamoleServerException("LDAP URL contains no hostname.");
+ }
+
+ // Parse encryption method from URL scheme
+ EncryptionMethod encryptionMethod = EncryptionMethod.NONE;
+ if (LdapUrl.LDAPS_SCHEME.equals(ldapUrl.getScheme()))
+ encryptionMethod = EncryptionMethod.SSL;
+
+ // Use STARTTLS for otherwise unencrypted ldap:// URLs if the main
+ // LDAP connection requires STARTTLS
+ else if (confService.getEncryptionMethod() == EncryptionMethod.STARTTLS) {
+ logger.debug("Using STARTTLS for LDAP URL \"{}\" as the main LDAP "
+ + "connection described in guacamole.properties is "
+ + "configured to use STARTTLS.", url);
+ encryptionMethod = EncryptionMethod.STARTTLS;
+ }
+
+ // If no post is specified within the URL, use the default port
+ // dictated by the encryption method
+ int port = ldapUrl.getPort();
+ if (port < 1)
+ port = encryptionMethod.DEFAULT_PORT;
+
+ return createLDAPConnection(host, port, encryptionMethod);
+
+ }
+
+ /**
+ * Binds to the LDAP server indicated by the given LdapNetworkConnection
+ * using the given credentials. If the LdapNetworkConnection is not yet
+ * connected, an LDAP connection is first established. The provided
+ * credentials will be stored within the LdapConnectionConfig of the given
+ * LdapNetworkConnection. If the bind operation fails, the given
+ * LdapNetworkConnection is automatically closed.
+ *
+ * @param ldapConnection
+ * The LdapNetworkConnection describing the connection to the LDAP
+ * server. This LdapNetworkConnection is modified as a result of this
+ * call and will be automatically closed if this call fails.
+ *
+ * @param userDN
+ * The DN of the user to bind as, or null to bind anonymously.
+ *
+ * @param password
+ * The password to use when binding as the specified user, or null to
+ * attempt to bind without a password.
+ *
+ * @return
+ * A bound LDAP connection, or null if the connection could not be
+ * bound.
+ */
+ private LdapNetworkConnection bindAs(LdapNetworkConnection ldapConnection,
+ Dn userDN, String password) {
+
+ // Add credentials to existing config
+ LdapConnectionConfig config = ldapConnection.getConfig();
+ config.setName(userDN.getName());
+ config.setCredentials(password);
+
+ try {
+ // Connect and bind using provided credentials
+ ldapConnection.bind();
+ }
+
+ // Disconnect if an authentication error occurs, but log that failure
+ // only at the debug level (such failures are expected)
+ catch (LdapAuthenticationException e) {
+ ldapConnection.close();
+ logger.debug("Bind attempt with LDAP server as user \"{}\" failed.", userDN, e);
+ return null;
+ }
+
+ // Disconnect for all other bind failures, as well, logging those at
+ // the error level
+ catch (LdapException e) {
+ ldapConnection.close();
+ logger.error("Binding with the LDAP server at \"{}\" as user "
+ + "\"{}\" failed: {}", config.getLdapHost(), userDN, e.getMessage());
+ logger.debug("Unable to bind to LDAP server.", e);
+ return null;
+ }
+
+ return ldapConnection;
+
+ }
+
+ /**
+ * Binds to the LDAP server indicated by a given LdapNetworkConnection
+ * using the credentials that were used to bind another
+ * LdapNetworkConnection. If the LdapNetworkConnection about to be bound is
+ * not yet connected, an LDAP connection is first established. The
+ * credentials from the other LdapNetworkConnection will be stored within
+ * the LdapConnectionConfig of the given LdapNetworkConnection. If the bind
+ * operation fails, the given LdapNetworkConnection is automatically
+ * closed.
+ *
+ * @param ldapConnection
+ * The LdapNetworkConnection describing the connection to the LDAP
+ * server. This LdapNetworkConnection is modified as a result of this
+ * call and will be automatically closed if this call fails.
+ *
+ * @param useCredentialsFrom
+ * A bound LdapNetworkConnection whose bind credentials should be
+ * copied for use within this bind operation.
+ *
+ * @return
+ * A bound LDAP connection, or null if the connection could not be
+ * bound.
+ */
+ private LdapNetworkConnection bindAs(LdapNetworkConnection ldapConnection,
+ LdapNetworkConnection useCredentialsFrom) {
+
+ // Copy bind username and password from original config
+ LdapConnectionConfig ldapConfig = useCredentialsFrom.getConfig();
+ String username = ldapConfig.getName();
+ String password = ldapConfig.getCredentials();
+
+ // Parse bind username as an LDAP DN
+ Dn userDN;
+ try {
+ userDN = new Dn(username);
+ }
+ catch (LdapInvalidDnException e) {
+ logger.error("Credentials of existing connection cannot be used. "
+ + "The username used (\"{}\") is not a valid DN.", username);
+ logger.debug("Cannot bind using invalid DN.", e);
+ ldapConnection.close();
+ return null;
+ }
+
+ // Bind using username/password from existing connection
+ return bindAs(ldapConnection, userDN, password);
+
+ }
+
+ /**
+ * Binds to the LDAP server using the provided user DN and password. The
+ * hostname, port, and encryption method of the LDAP server are determined
+ * from guacamole.properties.
*
* @param userDN
* The DN of the user to bind as, or null to bind anonymously.
@@ -121,134 +339,41 @@
* bound.
*
* @throws GuacamoleException
- * If the configuration details relevant to binding to the LDAP server
- * cannot be read.
+ * If an error occurs while parsing guacamole.properties, or if the
+ * configured encryption method is actually not implemented (a bug).
*/
public LdapNetworkConnection bindAs(Dn userDN, String password)
throws GuacamoleException {
-
- // Get ldapConnection and try to connect and bind.
- LdapNetworkConnection ldapConnection = createLDAPConnection();
- try {
-
- // Connect to LDAP server
- ldapConnection.connect();
-
- // Explicitly start TLS if requested
- if (confService.getEncryptionMethod() == EncryptionMethod.STARTTLS)
- ldapConnection.startTls();
-
- // Bind using provided credentials
- BindRequest bindRequest = new BindRequestImpl();
- bindRequest.setDn(userDN);
- bindRequest.setCredentials(password);
- BindResponse bindResponse = ldapConnection.bind(bindRequest);
-
- if (bindResponse.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS) {
- ldapConnection.close();
- logger.debug("LDAP bind attempt failed: {}", bindResponse.toString());
- return null;
- }
-
- }
-
- // Disconnect if an error occurs during bind
- catch (LdapException e) {
- ldapConnection.close();
- logger.debug("Unable to bind to LDAP server.", e);
- return null;
- }
-
- return ldapConnection;
-
+ return bindAs(createLDAPConnection(), userDN, password);
}
-
+
/**
- * Establishes a new network connection to the LDAP server indicated by the
- * given LDAP referral URL. The credentials used to bind with the referred
- * LDAP server will be the same as those used to bind with the original
- * connection.
- *
- * @param ldapConnection
- * The LDAP connection that bind credentials should be copied from.
+ * Binds to the LDAP server indicated by the given LDAP URL using the
+ * credentials that were used to bind an existing LdapNetworkConnection.
*
* @param url
- * The URL of the referred LDAP server to which a new network
- * connection should be established.
+ * The LDAP URL containing the details which should be used to connect
+ * to the LDAP server.
+ *
+ * @param useCredentialsFrom
+ * A bound LdapNetworkConnection whose bind credentials should be
+ * copied for use within this bind operation.
*
* @return
- * A LdapNetworkConnection representing a network connection to the
- * LDAP server specified in the URL, or null if the specified URL is
- * invalid.
- */
- public LdapNetworkConnection getReferralConnection(
- LdapNetworkConnection ldapConnection, String url) {
-
- LdapConnectionConfig ldapConfig = ldapConnection.getConfig();
- LdapConnectionConfig referralConfig = new LdapConnectionConfig();
-
- // Copy bind name and password from original config
- referralConfig.setName(ldapConfig.getName());
- referralConfig.setCredentials(ldapConfig.getCredentials());
-
- LdapUrl referralUrl;
- try {
- referralUrl = new LdapUrl(url);
- }
- catch (LdapException e) {
- logger.debug("Referral URL \"{}\" is invalid.", url, e);
- return null;
- }
-
- // Look for host - if not there, bail out.
- String host = referralUrl.getHost();
- if (host == null || host.isEmpty()) {
- logger.debug("Referral URL \"{}\" is invalid as it contains "
- + "no hostname.", url );
- return null;
- }
-
- referralConfig.setLdapHost(host);
-
- // Look for port, or assign a default.
- int port = referralUrl.getPort();
- if (port < 1)
- referralConfig.setLdapPort(389);
- else
- referralConfig.setLdapPort(port);
-
- // Deal with SSL connections
- if (referralUrl.getScheme().equals(LdapUrl.LDAPS_SCHEME))
- referralConfig.setUseSsl(true);
- else
- referralConfig.setUseSsl(false);
-
- return new LdapNetworkConnection(referralConfig);
-
- }
-
- /**
- * Disconnects the given LDAP connection, logging any failure to do so
- * appropriately.
+ * A bound LDAP connection, or null if the connection could not be
+ * bound.
*
- * @param ldapConnection
- * The LDAP connection to disconnect.
+ * @throws GuacamoleException
+ * If the given URL is not a valid LDAP URL, or if the encryption
+ * method indicated by the URL is known but not actually implemented (a
+ * bug).
*/
- public void disconnect(LdapConnection ldapConnection) {
-
- // Attempt disconnect
- try {
- ldapConnection.close();
- }
-
- // Warn if disconnect unexpectedly fails
- catch (IOException e) {
- logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage());
- logger.debug("LDAP disconnect failed.", e);
- }
-
+ public LdapNetworkConnection bindAs(String url,
+ LdapNetworkConnection useCredentialsFrom)
+ throws GuacamoleException {
+ return bindAs(createLDAPConnection(url), useCredentialsFrom);
}
-
+
/**
* Generate a SearchRequest object using the given Base DN and filter
* and retrieving other properties from the LDAP configuration service.
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java
index fcae4d6..229eb1b 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ObjectQueryService.java
@@ -41,8 +41,6 @@
import org.apache.directory.api.ldap.model.filter.PresenceNode;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.name.Dn;
-import org.apache.directory.api.ldap.model.url.LdapUrl;
-import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
@@ -250,14 +248,15 @@
// Connect to referred LDAP server to retrieve further results, ensuring the network
// connection is always closed when it will no longer be used
- try (LdapNetworkConnection referralConnection = ldapService.getReferralConnection(ldapConnection, url)) {
+ try (LdapNetworkConnection referralConnection = ldapService.bindAs(url, ldapConnection)) {
if (referralConnection != null) {
logger.debug("Following referral to \"{}\"...", url);
entries.addAll(search(referralConnection, baseDN, query, searchHop + 1));
}
else
- logger.debug("Could not follow referral to "
- + "\"{}\" as the URL is invalid.", url);
+ logger.debug("Could not bind with LDAP "
+ + "server indicated by referral "
+ + "URL \"{}\".", url);
}
catch (GuacamoleException e) {
logger.warn("Referral to \"{}\" could not be followed: {}", url, e.getMessage());