diff --git a/doc/licenses/jackson-2.12.2/dep-coordinates.txt b/doc/licenses/jackson-2.12.2/dep-coordinates.txt
index 6dcc7917e..3f73c2e2c 100644
--- a/doc/licenses/jackson-2.12.2/dep-coordinates.txt
+++ b/doc/licenses/jackson-2.12.2/dep-coordinates.txt
@@ -1,4 +1,5 @@
com.fasterxml.jackson.core:jackson-databind:jar:2.12.2
com.fasterxml.jackson.core:jackson-core:jar:2.12.2
com.fasterxml.jackson.core:jackson-annotations:jar:2.12.2
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.12.2
com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.12.2
diff --git a/doc/licenses/snakeyaml-1.27/README b/doc/licenses/snakeyaml-1.27/README
new file mode 100644
index 000000000..3fcd837d6
--- /dev/null
+++ b/doc/licenses/snakeyaml-1.27/README
@@ -0,0 +1,8 @@
+SnakeYAML (https://bitbucket.org/asomov/snakeyaml/)
+---------------------------------------------------
+
+ Version: 1.27
+ From: 'Andrey Somov' (https://bitbucket.org/asomov/)
+ License(s):
+ Apache v2.0
+
diff --git a/doc/licenses/snakeyaml-1.27/dep-coordinates.txt b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt
new file mode 100644
index 000000000..d7cbad91a
--- /dev/null
+++ b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt
@@ -0,0 +1 @@
+org.yaml:snakeyaml:jar:1.27
diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml
index fa9d8a215..5aa8f967d 100644
--- a/extensions/guacamole-auth-ldap/pom.xml
+++ b/extensions/guacamole-auth-ldap/pom.xml
@@ -60,6 +60,16 @@
guice
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
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 833674c1b..654b403c3 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
@@ -19,6 +19,7 @@
package org.apache.guacamole.auth.ldap;
+import org.apache.guacamole.auth.ldap.user.UserLDAPConfiguration;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
@@ -35,6 +36,7 @@ import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
import org.apache.guacamole.auth.ldap.group.UserGroupService;
import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser;
import org.apache.guacamole.auth.ldap.user.LDAPUserContext;
@@ -105,6 +107,9 @@ public class AuthenticationProviderService {
* or queried from the LDAP server, depending on how LDAP authentication
* has been configured.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param username
* The username of the user whose corresponding DN should be returned.
*
@@ -115,18 +120,17 @@ public class AuthenticationProviderService {
* If required properties are missing, and thus the user DN cannot be
* determined.
*/
- private Dn getUserBindDN(String username) throws GuacamoleException {
+ private Dn getUserBindDN(LDAPConfiguration config, String username)
+ throws GuacamoleException {
// If a search DN is provided, search the LDAP directory for the DN
// corresponding to the given username
- String searchBindLogon = confService.getSearchBindDN();
+ String searchBindLogon = config.getSearchBindDN();
if (searchBindLogon != null) {
// Create an LDAP connection using the search account
- LdapNetworkConnection searchConnection = ldapService.bindAs(
- searchBindLogon,
- confService.getSearchBindPassword()
- );
+ LdapNetworkConnection searchConnection = ldapService.bindAs(config,
+ searchBindLogon, config.getSearchBindPassword());
// Warn of failure to find
if (searchConnection == null) {
@@ -138,7 +142,7 @@ public class AuthenticationProviderService {
try {
// Retrieve all DNs associated with the given username
- List userDNs = userService.getUserDNs(searchConnection, username);
+ List userDNs = userService.getUserDNs(config, searchConnection, username);
if (userDNs.isEmpty())
return null;
@@ -161,10 +165,89 @@ public class AuthenticationProviderService {
}
// Otherwise, derive user DN from base DN
- return userService.deriveUserDN(username);
+ return userService.deriveUserDN(config, username);
}
+ /**
+ * Returns a new UserLDAPConfiguration that is connected to an LDAP server
+ * associated with the Guacamole user having the given username and bound
+ * using the provided password. All LDAP servers associated with the given
+ * user are tried until the connection and authentication attempt succeeds.
+ * If no LDAP servers are available, or no LDAP servers are associated with
+ * the given user, null is returned. The Guacamole username will be
+ * internally translated to a fully-qualified LDAP DN according to the
+ * configuration of the LDAP server that is ultimately chosen.
+ *
+ * @param username
+ * The username of the Guacamole user to bind as.
+ *
+ * @param password
+ * The password of the user to bind as.
+ *
+ * @return
+ * A new UserLDAPConfiguration which is bound to an LDAP server using
+ * the provided credentials, or null if no LDAP servers are available
+ * for the given user or connecting/authenticating has failed.
+ *
+ * @throws GuacamoleException
+ * If configuration information for the user's LDAP server(s) cannot
+ * be retrieved.
+ */
+ private UserLDAPConfiguration getLDAPConfiguration(String username,
+ String password) throws GuacamoleException {
+
+ // Get all LDAP server configurations
+ Collection extends LDAPConfiguration> configs = confService.getLDAPConfigurations();
+ if (configs.isEmpty()) {
+ logger.info("Skipping LDAP authentication as no LDAP servers are configured.");
+ return null;
+ }
+
+ // Try each possible LDAP configuration until the TCP connection and
+ // authentication are successful
+ for (LDAPConfiguration config : configs) {
+
+ // Attempt connection only if username matches
+ String translatedUsername = config.appliesTo(username);
+ if (translatedUsername == null) {
+ logger.debug("LDAP server \"{}\" does not match username \"{}\".", config.getServerHostname(), username);
+ continue;
+ }
+
+ logger.debug("LDAP server \"{}\" matched username \"{}\" as \"{}\".",
+ config.getServerHostname(), username, translatedUsername);
+
+ // Derive DN of user within this LDAP server
+ Dn bindDn = getUserBindDN(config, translatedUsername);
+ if (bindDn == null || bindDn.isEmpty()) {
+ logger.info("Unable to determine DN of user \"{}\" using LDAP "
+ + "server \"{}\". Proceeding with next server...",
+ username, config.getServerHostname());
+ continue;
+ }
+
+ // Attempt bind (authentication)
+ LdapNetworkConnection ldapConnection = ldapService.bindAs(config, bindDn.getName(), password);
+ if (ldapConnection == null) {
+ logger.info("Unable to bind as user \"{}\" against LDAP "
+ + "server \"{}\". Proceeding with next server...",
+ username, config.getServerHostname());
+ continue;
+ }
+
+ // Connection and bind were successful
+ logger.info("User \"{}\" was successfully authenticated by LDAP server \"{}\".", username, config.getServerHostname());
+ return new UserLDAPConfiguration(config, translatedUsername, bindDn, ldapConnection);
+
+ }
+
+ // No LDAP connection/authentication attempt succeeded
+ logger.info("User \"{}\" did not successfully authenticate against any LDAP server.", username);
+ return null;
+
+ }
+
/**
* Returns an AuthenticatedUser representing the user authenticated by the
* given credentials. Also adds custom LDAP attributes to the
@@ -196,39 +279,30 @@ public class AuthenticationProviderService {
"Anonymous bind is not currently allowed by the LDAP"
+ " authentication provider.", CredentialsInfo.USERNAME_PASSWORD);
}
-
- Dn bindDn = getUserBindDN(username);
- if (bindDn == null || bindDn.isEmpty()) {
- throw new GuacamoleInvalidCredentialsException("Unable to determine"
- + " DN of user " + username, CredentialsInfo.USERNAME_PASSWORD);
- }
-
- // Attempt bind
- LdapNetworkConnection ldapConnection =
- ldapService.bindAs(bindDn.getName(), password);
- if (ldapConnection == null)
+
+ UserLDAPConfiguration config = getLDAPConfiguration(username, password);
+ if (config == null)
throw new GuacamoleInvalidCredentialsException("Invalid login.",
CredentialsInfo.USERNAME_PASSWORD);
try {
-
+
// Retrieve group membership of the user that just authenticated
Set effectiveGroups =
- userGroupService.getParentUserGroupIdentifiers(ldapConnection,
- bindDn);
+ userGroupService.getParentUserGroupIdentifiers(config, config.getBindDN());
// Return AuthenticatedUser if bind succeeds
LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
- authenticatedUser.init(credentials, getAttributeTokens(ldapConnection,
- bindDn), effectiveGroups, bindDn);
+ authenticatedUser.init(config, credentials,
+ getAttributeTokens(config), effectiveGroups);
return authenticatedUser;
}
- // Always disconnect
- finally {
- ldapConnection.close();
+ catch (GuacamoleException | RuntimeException | Error e) {
+ config.close();
+ throw e;
}
}
@@ -240,11 +314,8 @@ public class AuthenticationProviderService {
* guacamole.properties. If no attributes are specified or none are
* found on the LDAP user object, an empty map is returned.
*
- * @param ldapConnection
- * LDAP connection to use to read the attributes of the user.
- *
- * @param username
- * The username of the user whose attributes are to be queried.
+ * @param config
+ * The configuration of the LDAP server being queried.
*
* @return
* A map of parameter tokens generated from attributes on the user
@@ -255,11 +326,11 @@ public class AuthenticationProviderService {
* @throws GuacamoleException
* If an error occurs retrieving the user DN or the attributes.
*/
- private Map getAttributeTokens(LdapNetworkConnection ldapConnection,
- Dn userDn) throws GuacamoleException {
+ private Map getAttributeTokens(ConnectedLDAPConfiguration config)
+ throws GuacamoleException {
// Get attributes from configuration information
- List attrList = confService.getAttributes();
+ List attrList = config.getAttributes();
// If there are no attributes there is no reason to search LDAP
if (attrList.isEmpty())
@@ -272,7 +343,7 @@ public class AuthenticationProviderService {
try {
// Get LDAP attributes by querying LDAP
- Entry userEntry = ldapConnection.lookup(userDn, attrArray);
+ Entry userEntry = config.getLDAPConnection().lookup(config.getBindDN(), attrArray);
if (userEntry == null)
return Collections.emptyMap();
@@ -312,35 +383,26 @@ public class AuthenticationProviderService {
public LDAPUserContext getUserContext(AuthenticatedUser authenticatedUser)
throws GuacamoleException {
- // Bind using credentials associated with AuthenticatedUser
- Credentials credentials = authenticatedUser.getCredentials();
if (authenticatedUser instanceof LDAPAuthenticatedUser) {
- Dn bindDn = ((LDAPAuthenticatedUser) authenticatedUser).getBindDn();
- LdapNetworkConnection ldapConnection =
- ldapService.bindAs(bindDn.getName(), credentials.getPassword());
- if (ldapConnection == null) {
- logger.debug("LDAP bind succeeded for \"{}\" during "
- + "authentication but failed during data retrieval.",
- authenticatedUser.getIdentifier());
- throw new GuacamoleInvalidCredentialsException("Invalid login.",
- CredentialsInfo.USERNAME_PASSWORD);
- }
+ LDAPAuthenticatedUser ldapAuthenticatedUser = (LDAPAuthenticatedUser) authenticatedUser;
+ ConnectedLDAPConfiguration config = ldapAuthenticatedUser.getLDAPConfiguration();
try {
// Build user context by querying LDAP
LDAPUserContext userContext = userContextProvider.get();
- userContext.init(authenticatedUser, ldapConnection);
+ userContext.init(ldapAuthenticatedUser);
return userContext;
}
// Always disconnect
finally {
- ldapConnection.close();
+ config.close();
}
}
+
return null;
}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java
new file mode 100644
index 000000000..5617bb785
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap;
+
+import java.util.List;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.message.AliasDerefMode;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.ldap.conf.EncryptionMethod;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
+import org.apache.guacamole.auth.ldap.conf.MemberAttributeType;
+
+/**
+ * LDAPConfiguration implementation that is associated with an
+ * LdapNetworkConnection to the configured LDAP server.
+ */
+public class ConnectedLDAPConfiguration implements LDAPConfiguration, AutoCloseable {
+
+ /**
+ * The wrapped LDAPConfiguration.
+ */
+ private final LDAPConfiguration config;
+
+ /**
+ * The connection to the LDAP server represented by this configuration.
+ */
+ private final LdapNetworkConnection connection;
+
+ /**
+ * The LDAP DN that was used to bind with the LDAP server to produce
+ * {@link #connection}.
+ */
+ private final Dn bindDn;
+
+ /**
+ * Creates a new ConnectedLDAPConfiguration that associates the given
+ * LdapNetworkConnection with the given LDAPConfiguration. All functions
+ * inherited from the LDAPConfiguration interface are delegated to the
+ * given LDAPConfiguration. It is the responsibility of the caller to
+ * ensure the provided LdapNetworkConnection is closed after it is no
+ * longer needed.
+ *
+ * @param config
+ * The LDAPConfiguration to wrap.
+ *
+ * @param bindDn
+ * The LDAP DN that was used to bind with the LDAP server to produce
+ * the given LdapNetworkConnection.
+ *
+ * @param connection
+ * The connection to the LDAP server represented by the given
+ * configuration.
+ */
+ public ConnectedLDAPConfiguration(LDAPConfiguration config, Dn bindDn, LdapNetworkConnection connection) {
+ this.config = config;
+ this.bindDn = bindDn;
+ this.connection = connection;
+ }
+
+ /**
+ * Returns the LdapNetworkConnection for the connection to the LDAP server
+ * represented by this configuration. The lifecycle of this connection is
+ * managed externally. The connection is not guaranteed to still be
+ * connected.
+ *
+ * @return
+ * The LdapNetworkConnection for the connection to the LDAP server
+ * represented by this configuration.
+ */
+ public LdapNetworkConnection getLDAPConnection() {
+ return connection;
+ }
+
+ /**
+ * Returns the LDAP DN that was used to bind with the LDAP server to
+ * produce the LdapNetworkConnection associated with this
+ * ConnectedLDAPConfiguration.
+ *
+ * @return
+ * The LDAP DN that was used to bind with the LDAP server.
+ */
+ public Dn getBindDN() {
+ return bindDn;
+ }
+
+ @Override
+ public void close() {
+ connection.close();
+ }
+
+ @Override
+ public String appliesTo(String username) throws GuacamoleException {
+ return config.appliesTo(username);
+ }
+
+ @Override
+ public String getServerHostname() throws GuacamoleException {
+ return config.getServerHostname();
+ }
+
+ @Override
+ public int getServerPort() throws GuacamoleException {
+ return config.getServerPort();
+ }
+
+ @Override
+ public List getUsernameAttributes() throws GuacamoleException {
+ return config.getUsernameAttributes();
+ }
+
+ @Override
+ public Dn getUserBaseDN() throws GuacamoleException {
+ return config.getUserBaseDN();
+ }
+
+ @Override
+ public Dn getConfigurationBaseDN() throws GuacamoleException {
+ return config.getConfigurationBaseDN();
+ }
+
+ @Override
+ public List getGroupNameAttributes() throws GuacamoleException {
+ return config.getGroupNameAttributes();
+ }
+
+ @Override
+ public Dn getGroupBaseDN() throws GuacamoleException {
+ return config.getGroupBaseDN();
+ }
+
+ @Override
+ public String getSearchBindDN() throws GuacamoleException {
+ return config.getSearchBindDN();
+ }
+
+ @Override
+ public String getSearchBindPassword() throws GuacamoleException {
+ return config.getSearchBindPassword();
+ }
+
+ @Override
+ public EncryptionMethod getEncryptionMethod() throws GuacamoleException {
+ return config.getEncryptionMethod();
+ }
+
+ @Override
+ public int getMaxResults() throws GuacamoleException {
+ return config.getMaxResults();
+ }
+
+ @Override
+ public AliasDerefMode getDereferenceAliases() throws GuacamoleException {
+ return config.getDereferenceAliases();
+ }
+
+ @Override
+ public boolean getFollowReferrals() throws GuacamoleException {
+ return config.getFollowReferrals();
+ }
+
+ @Override
+ public int getMaxReferralHops() throws GuacamoleException {
+ return config.getMaxReferralHops();
+ }
+
+ @Override
+ public ExprNode getUserSearchFilter() throws GuacamoleException {
+ return config.getUserSearchFilter();
+ }
+
+ @Override
+ public ExprNode getGroupSearchFilter() throws GuacamoleException {
+ return config.getGroupSearchFilter();
+ }
+
+ @Override
+ public int getOperationTimeout() throws GuacamoleException {
+ return config.getOperationTimeout();
+ }
+
+ @Override
+ public int getNetworkTimeout() throws GuacamoleException {
+ return config.getNetworkTimeout();
+ }
+
+ @Override
+ public List getAttributes() throws GuacamoleException {
+ return config.getAttributes();
+ }
+
+ @Override
+ public String getMemberAttribute() throws GuacamoleException {
+ return config.getMemberAttribute();
+ }
+
+ @Override
+ public MemberAttributeType getMemberAttributeType() throws GuacamoleException {
+ return config.getMemberAttributeType();
+ }
+
+}
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 d93ae23a8..b8f5d30ad 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
@@ -19,7 +19,6 @@
package org.apache.guacamole.auth.ldap;
-import com.google.inject.Inject;
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;
@@ -34,8 +33,8 @@ import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.GuacamoleUnsupportedException;
-import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
import org.apache.guacamole.auth.ldap.conf.EncryptionMethod;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,12 +48,6 @@ public class LDAPConnectionService {
*/
private static final Logger logger = LoggerFactory.getLogger(LDAPConnectionService.class);
- /**
- * Service for retrieving LDAP server configuration information.
- */
- @Inject
- private ConfigurationService confService;
-
/**
* Creates a new instance of LdapNetworkConnection, configured as required
* to use the given encryption method to communicate with the LDAP server
@@ -74,6 +67,10 @@ public class LDAPConnectionService {
* The encryption method that should be used to communicate with the
* LDAP server.
*
+ * @param timeout
+ * The maximum number of milliseconds to wait for a response from the
+ * LDAP server.
+ *
* @return
* A new instance of LdapNetworkConnection which uses the given
* encryption method to communicate with the LDAP server at the given
@@ -84,11 +81,13 @@ public class LDAPConnectionService {
* bug).
*/
private LdapNetworkConnection createLDAPConnection(String host, int port,
- EncryptionMethod encryptionMethod) throws GuacamoleException {
+ EncryptionMethod encryptionMethod, int timeout)
+ throws GuacamoleException {
LdapConnectionConfig config = new LdapConnectionConfig();
config.setLdapHost(host);
config.setLdapPort(port);
+ config.setTimeout(timeout);
// Map encryption method to proper connection and socket factory
switch (encryptionMethod) {
@@ -130,6 +129,9 @@ public class LDAPConnectionService {
* requested, and will not be connected until it is used in an LDAP
* operation (such as a bind).
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @return
* A new LdapNetworkConnection instance which has already been
* configured to use the encryption method, hostname, and port
@@ -139,12 +141,13 @@ public class LDAPConnectionService {
* If an error occurs while parsing guacamole.properties, or if the
* requested encryption method is actually not implemented (a bug).
*/
- private LdapNetworkConnection createLDAPConnection()
+ private LdapNetworkConnection createLDAPConnection(LDAPConfiguration config)
throws GuacamoleException {
return createLDAPConnection(
- confService.getServerHostname(),
- confService.getServerPort(),
- confService.getEncryptionMethod());
+ config.getServerHostname(),
+ config.getServerPort(),
+ config.getEncryptionMethod(),
+ config.getNetworkTimeout());
}
/**
@@ -156,6 +159,9 @@ public class LDAPConnectionService {
* requested, and will not be connected until it is used in an LDAP
* operation (such as a bind).
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param url
* The LDAP URL containing the details which should be used to connect
* to the LDAP server.
@@ -170,8 +176,8 @@ public class LDAPConnectionService {
* method indicated by the URL is known but not actually implemented (a
* bug).
*/
- private LdapNetworkConnection createLDAPConnection(String url)
- throws GuacamoleException {
+ private LdapNetworkConnection createLDAPConnection(LDAPConfiguration config,
+ String url) throws GuacamoleException {
// Parse provided LDAP URL
LdapUrl ldapUrl;
@@ -197,7 +203,7 @@ public class LDAPConnectionService {
// Use STARTTLS for otherwise unencrypted ldap:// URLs if the main
// LDAP connection requires STARTTLS
- else if (confService.getEncryptionMethod() == EncryptionMethod.STARTTLS) {
+ else if (config.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);
@@ -210,7 +216,8 @@ public class LDAPConnectionService {
if (port < 1)
port = encryptionMethod.DEFAULT_PORT;
- return createLDAPConnection(host, port, encryptionMethod);
+ return createLDAPConnection(host, port, encryptionMethod,
+ config.getNetworkTimeout());
}
@@ -329,6 +336,9 @@ public class LDAPConnectionService {
* hostname, port, and encryption method of the LDAP server are determined
* from guacamole.properties.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param bindUser
* The DN or UPN of the user to bind as, or null to bind anonymously.
*
@@ -344,15 +354,18 @@ public class LDAPConnectionService {
* If an error occurs while parsing guacamole.properties, or if the
* configured encryption method is actually not implemented (a bug).
*/
- public LdapNetworkConnection bindAs(String bindUser, String password)
- throws GuacamoleException {
- return bindAs(createLDAPConnection(), bindUser, password);
+ public LdapNetworkConnection bindAs(LDAPConfiguration config,
+ String bindUser, String password) throws GuacamoleException {
+ return bindAs(createLDAPConnection(config), bindUser, password);
}
/**
* Binds to the LDAP server indicated by the given LDAP URL using the
* credentials that were used to bind an existing LdapNetworkConnection.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param url
* The LDAP URL containing the details which should be used to connect
* to the LDAP server.
@@ -370,16 +383,19 @@ public class LDAPConnectionService {
* method indicated by the URL is known but not actually implemented (a
* bug).
*/
- public LdapNetworkConnection bindAs(String url,
+ public LdapNetworkConnection bindAs(LDAPConfiguration config, String url,
LdapNetworkConnection useCredentialsFrom)
throws GuacamoleException {
- return bindAs(createLDAPConnection(url), useCredentialsFrom);
+ return bindAs(createLDAPConnection(config, url), useCredentialsFrom);
}
/**
* Generate a SearchRequest object using the given Base DN and filter
* and retrieving other properties from the LDAP configuration service.
- *
+ *
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param baseDn
* The LDAP Base DN at which to search the search.
*
@@ -392,19 +408,19 @@ public class LDAPConnectionService {
* @throws GuacamoleException
* If an error occurs retrieving any of the configuration values.
*/
- public SearchRequest getSearchRequest(Dn baseDn, ExprNode filter)
- throws GuacamoleException {
+ public SearchRequest getSearchRequest(LDAPConfiguration config, Dn baseDn,
+ ExprNode filter) throws GuacamoleException {
SearchRequest searchRequest = new SearchRequestImpl();
searchRequest.setBase(baseDn);
- searchRequest.setDerefAliases(confService.getDereferenceAliases());
+ searchRequest.setDerefAliases(config.getDereferenceAliases());
searchRequest.setScope(SearchScope.SUBTREE);
searchRequest.setFilter(filter);
- searchRequest.setSizeLimit(confService.getMaxResults());
- searchRequest.setTimeLimit(confService.getOperationTimeout());
+ searchRequest.setSizeLimit(config.getMaxResults());
+ searchRequest.setTimeLimit(config.getOperationTimeout());
searchRequest.setTypesOnly(false);
- if (confService.getFollowReferrals())
+ if (config.getFollowReferrals())
searchRequest.followReferrals();
return searchRequest;
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 e54b54f7c..02814ac9c 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
@@ -44,7 +44,7 @@ import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
-import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
import org.apache.guacamole.auth.ldap.conf.LDAPGuacamoleProperties;
import org.apache.guacamole.net.auth.Identifiable;
import org.slf4j.Logger;
@@ -70,12 +70,6 @@ public class ObjectQueryService {
@Inject
private LDAPConnectionService ldapService;
- /**
- * Service for retrieving LDAP server configuration information.
- */
- @Inject
- private ConfigurationService confService;
-
/**
* Returns the identifier of the object represented by the given LDAP
* entry. Multiple attributes may be declared as containing the identifier
@@ -184,6 +178,9 @@ public class ObjectQueryService {
* list of all results. Only objects beneath the given base DN are
* included in the search.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param ldapConnection
* The current connection to the LDAP server, associated with the
* current user.
@@ -212,12 +209,13 @@ public class ObjectQueryService {
* information required to execute the query cannot be read from
* guacamole.properties.
*/
- public List search(LdapNetworkConnection ldapConnection,
- Dn baseDN, ExprNode query, int searchHop,
- Collection attributes) throws GuacamoleException {
+ public List search(LDAPConfiguration config,
+ LdapNetworkConnection ldapConnection, Dn baseDN, ExprNode query,
+ int searchHop, Collection attributes)
+ throws GuacamoleException {
// Refuse to follow referrals if limit has been reached
- int maxHops = confService.getMaxReferralHops();
+ int maxHops = config.getMaxReferralHops();
if (searchHop >= maxHops) {
logger.debug("Refusing to follow further referrals as the maximum "
+ "number of referral hops ({}) has been reached. LDAP "
@@ -230,8 +228,7 @@ public class ObjectQueryService {
logger.debug("Searching \"{}\" for objects matching \"{}\".", baseDN, query);
// Search within subtree of given base DN
- SearchRequest request = ldapService.getSearchRequest(baseDN, query);
-
+ SearchRequest request = ldapService.getSearchRequest(config, baseDN, query);
if (attributes != null)
request.addAttributes(attributes.toArray(new String[0]));
@@ -257,10 +254,10 @@ public class ObjectQueryService {
// 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.bindAs(url, ldapConnection)) {
+ try (LdapNetworkConnection referralConnection = ldapService.bindAs(config, url, ldapConnection)) {
if (referralConnection != null) {
logger.debug("Following referral to \"{}\"...", url);
- entries.addAll(search(referralConnection, baseDN, query, searchHop + 1, attributes));
+ entries.addAll(search(config, referralConnection, baseDN, query, searchHop + 1, attributes));
}
else
logger.debug("Could not bind with LDAP "
@@ -304,6 +301,9 @@ public class ObjectQueryService {
* given connection, returning a list of all results. Only objects beneath
* the given base DN are included in the search.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param ldapConnection
* The current connection to the LDAP server, associated with the
* current user.
@@ -339,12 +339,12 @@ public class ObjectQueryService {
* information required to execute the query cannot be read from
* guacamole.properties.
*/
- public List search(LdapNetworkConnection ldapConnection, Dn baseDN,
- ExprNode filter, Collection filterAttributes, String filterValue,
- Collection attributes)
- throws GuacamoleException {
+ public List search(LDAPConfiguration config,
+ LdapNetworkConnection ldapConnection, Dn baseDN, ExprNode filter,
+ Collection filterAttributes, String filterValue,
+ Collection attributes) throws GuacamoleException {
ExprNode query = generateQuery(filter, filterAttributes, filterValue);
- return search(ldapConnection, baseDN, query, 0, attributes);
+ return search(config, ldapConnection, baseDN, query, 0, attributes);
}
/**
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/CaseInsensitivePatternDeserializer.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/CaseInsensitivePatternDeserializer.java
new file mode 100644
index 000000000..0525b1dbe
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/CaseInsensitivePatternDeserializer.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.conf;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
+import com.fasterxml.jackson.databind.type.LogicalType;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Custom JSON (or YAML) deserializer for Jackson that deserializes string
+ * values as Patterns with the case insensitive flag set by default. Jackson
+ * will actually handle deserialization of Patterns automatically, but does not
+ * provide for setting the default flags.
+ */
+public class CaseInsensitivePatternDeserializer extends StdScalarDeserializer {
+
+ /**
+ * Unique version identifier of this {@link Serializable} class.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new CaseInsensitivePatternDeserializer which deserializes
+ * string values to Pattern objects with the case insensitive flag set.
+ */
+ public CaseInsensitivePatternDeserializer() {
+ super(Pattern.class);
+ }
+
+ @Override
+ public LogicalType logicalType() {
+ return LogicalType.Textual;
+ }
+
+ @Override
+ public boolean isCachable() {
+ return true;
+ }
+
+ @Override
+ public Pattern deserialize(JsonParser parser, DeserializationContext context)
+ throws IOException, JsonProcessingException {
+
+ if (!parser.hasToken(JsonToken.VALUE_STRING))
+ throw new JsonParseException(parser, "Regular expressions may only be represented as strings.");
+
+ try {
+ return Pattern.compile(parser.getText(), Pattern.CASE_INSENSITIVE);
+ }
+ catch (PatternSyntaxException e) {
+ throw new JsonParseException(parser, "Invalid regular expression.", e);
+ }
+
+ }
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
index 2071dfa02..3b08d3332 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
@@ -19,21 +19,57 @@
package org.apache.guacamole.auth.ldap.conf;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
-import java.util.List;
-import org.apache.directory.api.ldap.model.filter.ExprNode;
-import org.apache.directory.api.ldap.model.filter.PresenceNode;
-import org.apache.directory.api.ldap.model.message.AliasDerefMode;
-import org.apache.directory.api.ldap.model.name.Dn;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * Service for retrieving configuration information regarding the LDAP server.
+ * Service for retrieving configuration information regarding LDAP servers.
*/
+@Singleton
public class ConfigurationService {
+ /**
+ * Logger for this class.
+ */
+ private static final Logger logger = LoggerFactory.getLogger(ConfigurationService.class);
+
+ /**
+ * ObjectMapper for deserializing YAML.
+ */
+ private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory())
+ .registerModule(new SimpleModule().addDeserializer(Pattern.class, new CaseInsensitivePatternDeserializer()));
+
+ /**
+ * The name of the file within GUACAMOLE_HOME that defines each available
+ * LDAP server (if not using guacamole.properties).
+ */
+ private static final String LDAP_SERVERS_YML = "ldap-servers.yml";
+
+ /**
+ * The timestamp that the {@link #LDAP_SERVERS_YML} was last modified when
+ * it was read, as would be returned by {@link File#lastModified()}.
+ */
+ private final AtomicLong lastModified = new AtomicLong(0);
+
+ /**
+ * The cached copy of the configuration read from {@link #LDAP_SERVERS_YML}.
+ */
+ private Collection cachedConfigurations = Collections.emptyList();
+
/**
* The Guacamole server environment.
*/
@@ -41,375 +77,56 @@ public class ConfigurationService {
private Environment environment;
/**
- * Returns the hostname of the LDAP server as configured with
- * guacamole.properties. By default, this will be "localhost".
+ * Returns the configuration information for all configured LDAP servers.
+ * If multiple servers are returned, each should be tried in order until a
+ * successful LDAP connection is established.
*
* @return
- * The hostname of the LDAP server, as configured with
- * guacamole.properties.
+ * The configurations of all LDAP servers.
*
* @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
+ * If the configuration information of the LDAP servers cannot be
+ * retrieved due to an error.
*/
- public String getServerHostname() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_HOSTNAME,
- "localhost"
- );
- }
+ public Collection extends LDAPConfiguration> getLDAPConfigurations() throws GuacamoleException {
- /**
- * Returns the port of the LDAP server configured with
- * guacamole.properties. The default value depends on which encryption
- * method is being used. For unencrypted LDAP and STARTTLS, this will be
- * 389. For LDAPS (LDAP over SSL) this will be 636.
- *
- * @return
- * The port of the LDAP server, as configured with
- * guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public int getServerPort() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_PORT,
- getEncryptionMethod().DEFAULT_PORT
- );
- }
+ // Read configuration from YAML, if available
+ File ldapServers = new File(environment.getGuacamoleHome(), LDAP_SERVERS_YML);
+ if (ldapServers.exists()) {
- /**
- * Returns all username attributes which should be used to query and bind
- * users using the LDAP directory. By default, this will be "uid" - a
- * common attribute used for this purpose.
- *
- * @return
- * The username attributes which should be used to query and bind users
- * using the LDAP directory.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public List getUsernameAttributes() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE,
- Collections.singletonList("uid")
- );
- }
+ long oldLastModified = lastModified.get();
+ long currentLastModified = ldapServers.lastModified();
- /**
- * Returns the base DN under which all Guacamole users will be stored
- * within the LDAP directory.
- *
- * @return
- * The base DN under which all Guacamole users will be stored within
- * the LDAP directory.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed, or if the user base DN
- * property is not specified.
- */
- public Dn getUserBaseDN() throws GuacamoleException {
- return environment.getRequiredProperty(
- LDAPGuacamoleProperties.LDAP_USER_BASE_DN
- );
- }
+ // Update cached copy of YAML if things have changed, ensuring only
+ // one concurrent request updates the cache at any given time
+ if (currentLastModified > oldLastModified && lastModified.compareAndSet(oldLastModified, currentLastModified)) {
+ try {
- /**
- * Returns the base DN under which all Guacamole configurations
- * (connections) will be stored within the LDAP directory. If Guacamole
- * configurations will not be stored within LDAP, null is returned.
- *
- * @return
- * The base DN under which all Guacamole configurations will be stored
- * within the LDAP directory, or null if no Guacamole configurations
- * will be stored within the LDAP directory.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public Dn getConfigurationBaseDN() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN
- );
- }
+ logger.debug("Reading updated LDAP configuration from \"{}\"...", ldapServers);
+ Collection configs = mapper.readValue(ldapServers, new TypeReference>() {});
- /**
- * Returns all attributes which should be used to determine the unique
- * identifier of each user group. By default, this will be "cn".
- *
- * @return
- * The attributes which should be used to determine the unique
- * identifier of each group.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public List getGroupNameAttributes() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_GROUP_NAME_ATTRIBUTE,
- Collections.singletonList("cn")
- );
- }
+ logger.debug("Reading LDAP configuration defaults from guacamole.properties...");
+ LDAPConfiguration defaultConfig = new EnvironmentLDAPConfiguration(environment);
+ configs.forEach((config) -> config.setDefaults(defaultConfig));
- /**
- * Returns the base DN under which all Guacamole role based access control
- * (RBAC) groups will be stored within the LDAP directory. If RBAC will not
- * be used, null is returned.
- *
- * @return
- * The base DN under which all Guacamole RBAC groups will be stored
- * within the LDAP directory, or null if RBAC will not be used.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public Dn getGroupBaseDN() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN
- );
- }
+ cachedConfigurations = configs;
- /**
- * Returns the login that should be used when searching for the DNs of users
- * attempting to authenticate. If no such search should be performed, null
- * is returned.
- *
- * @return
- * The DN that should be used when searching for the DNs of users
- * attempting to authenticate, or null if no such search should be
- * performed.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public String getSearchBindDN() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_SEARCH_BIND_DN
- );
- }
+ }
+ catch (IOException e) {
+ logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage());
+ }
+ }
+ else
+ logger.debug("Using cached LDAP configuration from \"{}\".", ldapServers);
- /**
- * Returns the password that should be used when binding to the LDAP server
- * using the DN returned by getSearchBindDN(). If no password should be
- * used, null is returned.
- *
- * @return
- * The password that should be used when binding to the LDAP server
- * using the DN returned by getSearchBindDN(), or null if no password
- * should be used.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public String getSearchBindPassword() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_SEARCH_BIND_PASSWORD
- );
- }
+ return cachedConfigurations;
- /**
- * Returns the encryption method that should be used when connecting to the
- * LDAP server. By default, no encryption is used.
- *
- * @return
- * The encryption method that should be used when connecting to the
- * LDAP server.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public EncryptionMethod getEncryptionMethod() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD,
- EncryptionMethod.NONE
- );
- }
+ }
- /**
- * Returns maximum number of results a LDAP query can return,
- * as configured with guacamole.properties.
- * By default, this will be 1000.
- *
- * @return
- * The maximum number of results a LDAP query can return,
- * as configured with guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public int getMaxResults() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_MAX_SEARCH_RESULTS,
- 1000
- );
- }
+ // Use guacamole.properties if not using YAML
+ logger.debug("Reading LDAP configuration from guacamole.properties...");
+ return Collections.singletonList(new EnvironmentLDAPConfiguration(environment));
- /**
- * Returns whether or not LDAP aliases will be dereferenced,
- * as configured with guacamole.properties. The default
- * behavior if not explicitly defined is to never
- * dereference them.
- *
- * @return
- * The behavior for handling dereferencing of aliases
- * as configured in guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public AliasDerefMode getDereferenceAliases() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES,
- AliasDerefMode.NEVER_DEREF_ALIASES
- );
- }
-
- /**
- * Returns the boolean value for whether the connection should
- * follow referrals or not. By default, it will not.
- *
- * @return
- * The boolean value of whether to follow referrals
- * as configured in guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public boolean getFollowReferrals() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_FOLLOW_REFERRALS,
- false
- );
- }
-
- /**
- * Returns the maximum number of referral hops to follow. By default
- * a maximum of 5 hops is allowed.
- *
- * @return
- * The maximum number of referral hops to follow
- * as configured in guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public int getMaxReferralHops() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_MAX_REFERRAL_HOPS,
- 5
- );
- }
-
- /**
- * Returns the search filter that should be used when querying the
- * LDAP server for Guacamole users. If no filter is specified,
- * a default of "(objectClass=user)" is returned.
- *
- * @return
- * The search filter that should be used when querying the
- * LDAP server for users that are valid in Guacamole, or
- * "(objectClass=user)" if not specified.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public ExprNode getUserSearchFilter() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER,
- new PresenceNode("objectClass")
- );
- }
-
- /**
- * Returns the search filter that should be used when querying the
- * LDAP server for Guacamole groups. If no filter is specified,
- * a default of "(objectClass=*)" is used.
- *
- * @return
- * The search filter that should be used when querying the
- * LDAP server for groups that are valid in Guacamole, or
- * "(objectClass=*)" if not specified.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public ExprNode getGroupSearchFilter() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER,
- new PresenceNode("objectClass")
- );
- }
-
- /**
- * Returns the maximum number of seconds to wait for LDAP operations.
- *
- * @return
- * The maximum number of seconds to wait for LDAP operations
- * as configured in guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public int getOperationTimeout() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_OPERATION_TIMEOUT,
- 30
- );
- }
-
- /**
- * Returns names for custom LDAP user attributes. By default no
- * attributes will be returned.
- *
- * @return
- * Custom LDAP user attributes as configured in guacamole.properties.
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public List getAttributes() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_USER_ATTRIBUTES,
- Collections.emptyList()
- );
- }
-
- /**
- * Returns the name of the LDAP attribute used to enumerate
- * members in a group, or "member" by default.
- *
- * @return
- * The name of the LDAP attribute to use to enumerate
- * members in a group.
- *
- * @throws GuacamoleException
- * If guacamole.properties connect be parsed.
- */
- public String getMemberAttribute() throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE,
- "member"
- );
- }
-
- /**
- * Returns whether the LDAP attribute used to enumerate members in a group
- * specifies UID or DN.
- *
- * @return
- * The type of data contained in the LDAP attribute used to enumerate
- * members in a group, as configured in guacamole.properties
- *
- * @throws GuacamoleException
- * If guacamole.properties cannot be parsed.
- */
- public MemberAttributeType getMemberAttributeType()
- throws GuacamoleException {
- return environment.getProperty(
- LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE,
- MemberAttributeType.DN
- );
}
}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java
new file mode 100644
index 000000000..28ab8ed02
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.conf;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.filter.PresenceNode;
+import org.apache.directory.api.ldap.model.message.AliasDerefMode;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+
+/**
+ * LDAPConfiguration implementation that returns the default values for all
+ * configuration parameters. For any configuration parameters that are
+ * required (such as {@link #getUserBaseDN()}), an exception is thrown.
+ */
+public class DefaultLDAPConfiguration implements LDAPConfiguration {
+
+ @Override
+ public String appliesTo(String username) {
+ return null;
+ }
+
+ @Override
+ public String getServerHostname() {
+ return "localhost";
+ }
+
+ @Override
+ public int getServerPort() {
+ return getEncryptionMethod().DEFAULT_PORT;
+ }
+
+ @Override
+ public List getUsernameAttributes() {
+ return Collections.singletonList("uid");
+ }
+
+ @Override
+ public Dn getUserBaseDN() throws GuacamoleException {
+ throw new GuacamoleServerException("All LDAP servers must have a defined user base DN.");
+ }
+
+ @Override
+ public Dn getConfigurationBaseDN() {
+ return null;
+ }
+
+ @Override
+ public List getGroupNameAttributes() {
+ return Collections.singletonList("cn");
+ }
+
+ @Override
+ public Dn getGroupBaseDN() {
+ return null;
+ }
+
+ @Override
+ public String getSearchBindDN() {
+ return null;
+ }
+
+ @Override
+ public String getSearchBindPassword() {
+ return null;
+ }
+
+ @Override
+ public EncryptionMethod getEncryptionMethod() {
+ return EncryptionMethod.NONE;
+ }
+
+ @Override
+ public int getMaxResults() {
+ return 1000;
+ }
+
+ @Override
+ public AliasDerefMode getDereferenceAliases() {
+ return AliasDerefMode.NEVER_DEREF_ALIASES;
+ }
+
+ @Override
+ public boolean getFollowReferrals() {
+ return false;
+ }
+
+ @Override
+ public int getMaxReferralHops() {
+ return 5;
+ }
+
+ @Override
+ public ExprNode getUserSearchFilter() {
+ return new PresenceNode("objectClass");
+ }
+
+ @Override
+ public ExprNode getGroupSearchFilter() {
+ return new PresenceNode("objectClass");
+ }
+
+ @Override
+ public int getOperationTimeout() {
+ return 30;
+ }
+
+ @Override
+ public int getNetworkTimeout() {
+ return 30000;
+ }
+
+ @Override
+ public List getAttributes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getMemberAttribute() {
+ return "member";
+ }
+
+ @Override
+ public MemberAttributeType getMemberAttributeType()
+ throws GuacamoleException {
+ return MemberAttributeType.DN;
+ }
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java
new file mode 100644
index 000000000..ae2d3cf3f
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.conf;
+
+import java.util.List;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.message.AliasDerefMode;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.Environment;
+
+/**
+ * LDAPConfiguration implementation that reads its configuration details from
+ * guacamole.properties.
+ */
+public class EnvironmentLDAPConfiguration implements LDAPConfiguration {
+
+ /**
+ * The Guacamole server environment.
+ */
+ private final Environment environment;
+
+ /**
+ * The default configuration options for all parameters.
+ */
+ private static final LDAPConfiguration DEFAULT = new DefaultLDAPConfiguration();
+
+ /**
+ * Creates a new EnvironmentLDAPConfiguration that reads its configuration
+ * details from guacamole.properties, as exposed by the given Environment.
+ *
+ * @param environment
+ * The Guacamole server environment.
+ */
+ public EnvironmentLDAPConfiguration(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public String appliesTo(String username) throws GuacamoleException {
+ return username;
+ }
+
+ @Override
+ public String getServerHostname() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_HOSTNAME,
+ DEFAULT.getServerHostname()
+ );
+ }
+
+ @Override
+ public int getServerPort() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_PORT,
+ getEncryptionMethod().DEFAULT_PORT
+ );
+ }
+
+ @Override
+ public List getUsernameAttributes() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE,
+ DEFAULT.getUsernameAttributes()
+ );
+ }
+
+ @Override
+ public Dn getUserBaseDN() throws GuacamoleException {
+ return environment.getRequiredProperty(
+ LDAPGuacamoleProperties.LDAP_USER_BASE_DN
+ );
+ }
+
+ @Override
+ public Dn getConfigurationBaseDN() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN,
+ DEFAULT.getConfigurationBaseDN()
+ );
+ }
+
+ @Override
+ public List getGroupNameAttributes() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_GROUP_NAME_ATTRIBUTE,
+ DEFAULT.getGroupNameAttributes()
+ );
+ }
+
+ @Override
+ public Dn getGroupBaseDN() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN,
+ DEFAULT.getGroupBaseDN()
+ );
+ }
+
+ @Override
+ public String getSearchBindDN() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_SEARCH_BIND_DN,
+ DEFAULT.getSearchBindDN()
+ );
+ }
+
+ @Override
+ public String getSearchBindPassword() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_SEARCH_BIND_PASSWORD,
+ DEFAULT.getSearchBindPassword()
+ );
+ }
+
+ @Override
+ public EncryptionMethod getEncryptionMethod() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD,
+ DEFAULT.getEncryptionMethod()
+ );
+ }
+
+ @Override
+ public int getMaxResults() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_MAX_SEARCH_RESULTS,
+ DEFAULT.getMaxResults()
+ );
+ }
+
+ @Override
+ public AliasDerefMode getDereferenceAliases() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES,
+ DEFAULT.getDereferenceAliases()
+ );
+ }
+
+ @Override
+ public boolean getFollowReferrals() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_FOLLOW_REFERRALS,
+ DEFAULT.getFollowReferrals()
+ );
+ }
+
+ @Override
+ public int getMaxReferralHops() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_MAX_REFERRAL_HOPS,
+ DEFAULT.getMaxReferralHops()
+ );
+ }
+
+ @Override
+ public ExprNode getUserSearchFilter() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER,
+ DEFAULT.getUserSearchFilter()
+ );
+ }
+
+ @Override
+ public ExprNode getGroupSearchFilter() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER,
+ DEFAULT.getGroupSearchFilter()
+ );
+ }
+
+ @Override
+ public int getOperationTimeout() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_OPERATION_TIMEOUT,
+ DEFAULT.getOperationTimeout()
+ );
+ }
+
+ @Override
+ public int getNetworkTimeout() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_NETWORK_TIMEOUT,
+ DEFAULT.getNetworkTimeout()
+ );
+ }
+
+ @Override
+ public List getAttributes() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_USER_ATTRIBUTES,
+ DEFAULT.getAttributes()
+ );
+ }
+
+ @Override
+ public String getMemberAttribute() throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE,
+ DEFAULT.getMemberAttribute()
+ );
+ }
+
+ @Override
+ public MemberAttributeType getMemberAttributeType()
+ throws GuacamoleException {
+ return environment.getProperty(
+ LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE,
+ DEFAULT.getMemberAttributeType()
+ );
+ }
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java
new file mode 100644
index 000000000..4440739a7
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.conf;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import static com.fasterxml.jackson.annotation.JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.message.AliasDerefMode;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.properties.GuacamoleProperty;
+
+/**
+ * LDAPConfiguration implementation that is annotated for deserialization by
+ * Jackson.
+ */
+public class JacksonLDAPConfiguration implements LDAPConfiguration {
+
+ /**
+ * The regular expressions that match all users that should be routed to
+ * the LDAP server represented by this configuration.
+ */
+ @JsonProperty("match-usernames")
+ @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ private List matchUsernames;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_HOSTNAME}. If
+ * not set within the YAML, this will be null.
+ */
+ @JsonProperty("hostname")
+ private String hostname;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_PORT}. If not
+ * set within the YAML, this will be null.
+ */
+ @JsonProperty("port")
+ private Integer port;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USERNAME_ATTRIBUTES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("username-attribute")
+ @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ private List usernameAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-base-dn")
+ private String userBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_CONFIG_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("config-base-dn")
+ private String configBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-base-dn")
+ private String groupBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_NAME_ATTRIBUTES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-name-attribute")
+ @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ private List groupNameAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("search-bind-dn")
+ private String searchBindDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_PASSWORD}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("search-bind-password")
+ private String searchBindPassword;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_ENCRYPTION_METHOD}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("encryption-method")
+ private String encryptionMethod;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_SEARCH_RESULTS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("max-search-results")
+ private Integer maxSearchResults;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_DEREFERENCE_ALIASES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("dereference-aliases")
+ private String dereferenceAliases;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_FOLLOW_REFERRALS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("follow-referrals")
+ private Boolean followReferrals;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_REFERRAL_HOPS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("max-referral-hops")
+ private Integer maxReferralHops;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_SEARCH_FILTER}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-search-filter")
+ private String userSearchFilter;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_SEARCH_FILTER}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-search-filter")
+ private String groupSearchFilter;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_OPERATION_TIMEOUT}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("operation-timeout")
+ private Integer operationTimeout;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_NETWORK_TIMEOUT}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("network-timeout")
+ private Integer networkTimeout;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_ATTRIBUTES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-attributes")
+ @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ private List userAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("member-attribute")
+ private String memberAttribute;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE_TYPE}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("member-attribute-type")
+ private String memberAttributeType;
+
+ /**
+ * The default configuration options for all parameters.
+ */
+ private LDAPConfiguration defaultConfig = new DefaultLDAPConfiguration();
+
+ /**
+ * Supplier of default values for LDAP configurations. Unlike
+ * {@link java.util.function.Supplier}, the {@link #get()} function of
+ * DefaultSupplier may throw a {@link GuacamoleException}.
+ *
+ * @param
+ * The type of value returned by this DefaultSupplier.
+ */
+ @FunctionalInterface
+ private interface DefaultSupplier {
+
+ /**
+ * Returns the value supplied by this DefaultSupplier. The value
+ * returned is not cached and may be non-deterministic.
+ *
+ * @return
+ * The value supplied by this DefaultSupplier.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while producing/retrieving the value.
+ */
+ T get() throws GuacamoleException;
+
+ }
+
+ /**
+ * Returns the given value, if non-null. If null, the given default value
+ * is returned.
+ *
+ * @param
+ * The type of value accepted and returned.
+ *
+ * @param value
+ * The possibly null value to return if non-null.
+ *
+ * @param defaultValue
+ * A function which supplies the value to return if the provided value
+ * is null.
+ *
+ * @return
+ * The provided value, if non-null, otherwise the provided default
+ * value.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while producing/retrieving the default value.
+ */
+ private T withDefault(T value, DefaultSupplier defaultValue)
+ throws GuacamoleException {
+ return value != null ? value : defaultValue.get();
+ }
+
+ /**
+ * Parses and returns the given value, if non-null. If null, the given
+ * default value is returned.
+ *
+ * @param
+ * The type of value accepted and returned.
+ *
+ * @param property
+ * The GuacamoleProperty implementation to use to parse the provided
+ * String value.
+ *
+ * @param value
+ * The possibly null value to return if non-null.
+ *
+ * @param defaultValue
+ * A function which supplies the value to return if the provided value
+ * is null.
+ *
+ * @return
+ * The provided value, if non-null, otherwise the provided default
+ * value.
+ *
+ * @throws GuacamoleException
+ * If an error occurs while producing/retrieving the default value.
+ */
+ private T withDefault(GuacamoleProperty property, String value,
+ DefaultSupplier defaultValue)
+ throws GuacamoleException {
+ return withDefault(property.parseValue(value), defaultValue);
+ }
+
+ /**
+ * Sets the LDAPConfiguration that should be used for the default values of
+ * any configuration options omitted from the YAML. If not set, an instance
+ * of {@link DefaultLDAPConfiguration} will be used.
+ *
+ * @param defaultConfig
+ * The LDAPConfiguration to use for the default values of any omitted
+ * configuration options.
+ */
+ public void setDefaults(LDAPConfiguration defaultConfig) {
+ this.defaultConfig = defaultConfig;
+ }
+
+ @Override
+ public String appliesTo(String username) throws GuacamoleException {
+
+ // Match any user by default
+ if (matchUsernames == null || matchUsernames.isEmpty())
+ return username;
+
+ for (Pattern pattern : matchUsernames) {
+ Matcher matcher = pattern.matcher(username);
+ if (matcher.matches())
+ return matcher.groupCount() >= 1 ? matcher.group(1) : username;
+ }
+
+ return null;
+
+ }
+
+ @Override
+ public String getServerHostname() throws GuacamoleException {
+ return withDefault(hostname, defaultConfig::getServerHostname);
+ }
+
+ @Override
+ public int getServerPort() throws GuacamoleException {
+ return withDefault(port, () -> getEncryptionMethod().DEFAULT_PORT);
+ }
+
+ @Override
+ public List getUsernameAttributes() throws GuacamoleException {
+ return withDefault(usernameAttributes, defaultConfig::getUsernameAttributes);
+ }
+
+ @Override
+ public Dn getUserBaseDN() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_USER_BASE_DN,
+ userBaseDn, defaultConfig::getUserBaseDN);
+ }
+
+ @Override
+ public Dn getConfigurationBaseDN() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN,
+ configBaseDn, defaultConfig::getConfigurationBaseDN);
+ }
+
+ @Override
+ public List getGroupNameAttributes() throws GuacamoleException {
+ return withDefault(groupNameAttributes, defaultConfig::getGroupNameAttributes);
+ }
+
+ @Override
+ public Dn getGroupBaseDN() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN,
+ groupBaseDn, defaultConfig::getGroupBaseDN);
+ }
+
+ @Override
+ public String getSearchBindDN() throws GuacamoleException {
+ return withDefault(searchBindDn, defaultConfig::getSearchBindDN);
+ }
+
+ @Override
+ public String getSearchBindPassword() throws GuacamoleException {
+ return withDefault(searchBindPassword, defaultConfig::getSearchBindDN);
+ }
+
+ @Override
+ public EncryptionMethod getEncryptionMethod() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD,
+ encryptionMethod, defaultConfig::getEncryptionMethod);
+ }
+
+ @Override
+ public int getMaxResults() throws GuacamoleException {
+ return withDefault(maxSearchResults, defaultConfig::getMaxResults);
+ }
+
+ @Override
+ public AliasDerefMode getDereferenceAliases() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES,
+ dereferenceAliases, defaultConfig::getDereferenceAliases);
+ }
+
+ @Override
+ public boolean getFollowReferrals() throws GuacamoleException {
+ return withDefault(followReferrals, defaultConfig::getFollowReferrals);
+ }
+
+ @Override
+ public int getMaxReferralHops() throws GuacamoleException {
+ return withDefault(maxReferralHops, defaultConfig::getMaxReferralHops);
+ }
+
+ @Override
+ public ExprNode getUserSearchFilter() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER,
+ userSearchFilter, defaultConfig::getUserSearchFilter);
+ }
+
+ @Override
+ public ExprNode getGroupSearchFilter() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER,
+ groupSearchFilter, defaultConfig::getGroupSearchFilter);
+ }
+
+ @Override
+ public int getOperationTimeout() throws GuacamoleException {
+ return withDefault(operationTimeout, defaultConfig::getOperationTimeout);
+ }
+
+ @Override
+ public int getNetworkTimeout() throws GuacamoleException {
+ return withDefault(networkTimeout, defaultConfig::getNetworkTimeout);
+ }
+
+ @Override
+ public List getAttributes() throws GuacamoleException {
+ return withDefault(userAttributes, defaultConfig::getAttributes);
+ }
+
+ @Override
+ public String getMemberAttribute() throws GuacamoleException {
+ return withDefault(memberAttribute, defaultConfig::getMemberAttribute);
+ }
+
+ @Override
+ public MemberAttributeType getMemberAttributeType() throws GuacamoleException {
+ return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE,
+ memberAttributeType, defaultConfig::getMemberAttributeType);
+ }
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java
new file mode 100644
index 000000000..77eb31511
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.conf;
+
+import java.util.List;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.message.AliasDerefMode;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.guacamole.GuacamoleException;
+
+/**
+ * Configuration information defining how a particular LDAP server should be
+ * queried.
+ */
+public interface LDAPConfiguration {
+
+ /**
+ * Tests whether this LDAPConfiguration applies to the user having the
+ * given username. If the configuration applies, the username that should
+ * be used to derive the user's DN is returned.
+ *
+ * @param username
+ * The username to test.
+ *
+ * @return
+ * The username that should be used to derive this user's DN, or null
+ * if the configuration does not apply.
+ *
+ * @throws GuacamoleException
+ * If an error prevents testing against this configuration.
+ */
+ String appliesTo(String username) throws GuacamoleException;
+
+ /**
+ * Returns the hostname or IP address of the LDAP server.
+ *
+ * @return
+ * The hostname or IP address of the LDAP server.
+ *
+ * @throws GuacamoleException
+ * If the hostname or IP address of the LDAP server cannot be
+ * retrieved.
+ */
+ String getServerHostname() throws GuacamoleException;
+
+ /**
+ * Returns the port of the LDAP server. The default value depends on which
+ * encryption method is being used. For unencrypted LDAP and STARTTLS, this
+ * will be 389. For LDAPS (LDAP over SSL) this will be 636.
+ *
+ * @return
+ * The port of the LDAP server.
+ *
+ * @throws GuacamoleException
+ * If the port of the LDAP server cannot be retrieved.
+ */
+ int getServerPort() throws GuacamoleException;
+
+ /**
+ * Returns all username attributes which should be used to query and bind
+ * users using the LDAP directory.
+ *
+ * @return
+ * The username attributes which should be used to query and bind users
+ * using the LDAP directory.
+ *
+ * @throws GuacamoleException
+ * If the username attributes cannot be retrieved.
+ */
+ List getUsernameAttributes() throws GuacamoleException;
+
+ /**
+ * Returns the base DN under which all Guacamole users will be stored
+ * within the LDAP directory.
+ *
+ * @return
+ * The base DN under which all Guacamole users will be stored within
+ * the LDAP directory.
+ *
+ * @throws GuacamoleException
+ * If the user base DN cannot be retrieved.
+ */
+ Dn getUserBaseDN() throws GuacamoleException;
+
+ /**
+ * Returns the base DN under which all Guacamole configurations
+ * (connections) will be stored within the LDAP directory. If Guacamole
+ * configurations will not be stored within LDAP, null is returned.
+ *
+ * @return
+ * The base DN under which all Guacamole configurations will be stored
+ * within the LDAP directory, or null if no Guacamole configurations
+ * will be stored within the LDAP directory.
+ *
+ * @throws GuacamoleException
+ * If the configuration base DN cannot be retrieved.
+ */
+ Dn getConfigurationBaseDN() throws GuacamoleException;
+
+ /**
+ * Returns all attributes which should be used to determine the unique
+ * identifier of each user group.
+ *
+ * @return
+ * The attributes which should be used to determine the unique
+ * identifier of each group.
+ *
+ * @throws GuacamoleException
+ * If the group name attributes cannot be retrieved.
+ */
+ List getGroupNameAttributes() throws GuacamoleException;
+
+ /**
+ * Returns the base DN under which all Guacamole role based access control
+ * (RBAC) groups will be stored within the LDAP directory. If RBAC will not
+ * be used, null is returned.
+ *
+ * @return
+ * The base DN under which all Guacamole RBAC groups will be stored
+ * within the LDAP directory, or null if RBAC will not be used.
+ *
+ * @throws GuacamoleException
+ * If the group base DN cannot be retrieved.
+ */
+ Dn getGroupBaseDN() throws GuacamoleException;
+
+ /**
+ * Returns the login that should be used when searching for the DNs of users
+ * attempting to authenticate. If no such search should be performed, null
+ * is returned.
+ *
+ * @return
+ * The DN that should be used when searching for the DNs of users
+ * attempting to authenticate, or null if no such search should be
+ * performed.
+ *
+ * @throws GuacamoleException
+ * If the search bind DN cannot be retrieved.
+ */
+ String getSearchBindDN() throws GuacamoleException;
+
+ /**
+ * Returns the password that should be used when binding to the LDAP server
+ * using the DN returned by getSearchBindDN(). If no password should be
+ * used, null is returned.
+ *
+ * @return
+ * The password that should be used when binding to the LDAP server
+ * using the DN returned by getSearchBindDN(), or null if no password
+ * should be used.
+ *
+ * @throws GuacamoleException
+ * If the search bind password cannot be retrieved.
+ */
+ String getSearchBindPassword() throws GuacamoleException;
+
+ /**
+ * Returns the encryption method that should be used when connecting to the
+ * LDAP server.
+ *
+ * @return
+ * The encryption method that should be used when connecting to the
+ * LDAP server.
+ *
+ * @throws GuacamoleException
+ * If the encryption method cannot be retrieved.
+ */
+ EncryptionMethod getEncryptionMethod() throws GuacamoleException;
+
+ /**
+ * Returns maximum number of results a LDAP query can return.
+ *
+ * @return
+ * The maximum number of results a LDAP query can return.
+ *
+ * @throws GuacamoleException
+ * If the maximum number of results cannot be retrieved.
+ */
+ int getMaxResults() throws GuacamoleException;
+
+ /**
+ * Returns whether or not LDAP aliases will be dereferenced.
+ *
+ * @return
+ * The LDAP alias dereferencing mode.
+ *
+ * @throws GuacamoleException
+ * If the LDAP alias dereferencing mode cannot be retrieved.
+ */
+ AliasDerefMode getDereferenceAliases() throws GuacamoleException;
+
+ /**
+ * Returns whether referrals should be automatically followed.
+ *
+ * @return
+ * Whether referrals should be followed.
+ *
+ * @throws GuacamoleException
+ * If the configuration information determining whether LDAP referrals
+ * should be followed cannot be retrieved.
+ */
+ boolean getFollowReferrals() throws GuacamoleException;
+
+ /**
+ * Returns the maximum number of referral hops to follow.
+ *
+ * @return
+ * The maximum number of referral hops to follow.
+ *
+ * @throws GuacamoleException
+ * If the maximum number of referral hops cannot be retrieved.
+ */
+ int getMaxReferralHops() throws GuacamoleException;
+
+ /**
+ * Returns the search filter that should be used when querying the
+ * LDAP server for Guacamole users.
+ *
+ * @return
+ * The search filter that should be used when querying the
+ * LDAP server for users that are valid in Guacamole.
+ *
+ * @throws GuacamoleException
+ * If the user search filter cannot be retrieved.
+ */
+ ExprNode getUserSearchFilter() throws GuacamoleException;
+
+ /**
+ * Returns the search filter that should be used when querying the
+ * LDAP server for Guacamole groups.
+ *
+ * @return
+ * The search filter that should be used when querying the
+ * LDAP server for groups that are valid in Guacamole.
+ *
+ * @throws GuacamoleException
+ * If the group search filter cannot be retrieved.
+ */
+ ExprNode getGroupSearchFilter() throws GuacamoleException;
+
+ /**
+ * Returns the maximum number of milliseconds to wait for a response when
+ * communicating with the LDAP server.
+ *
+ * @return
+ * The maximum number of milliseconds to wait for responses from the
+ * LDAP server.
+ *
+ * @throws GuacamoleException
+ * If the LDAP network timeout cannot be retrieved.
+ */
+ int getNetworkTimeout() throws GuacamoleException;
+
+ /**
+ * Returns the maximum number of seconds to wait for LDAP operations.
+ *
+ * @return
+ * The maximum number of seconds to wait for LDAP operations.
+ *
+ * @throws GuacamoleException
+ * If the LDAP operation timeout cannot be retrieved.
+ */
+ int getOperationTimeout() throws GuacamoleException;
+
+ /**
+ * Returns names of any LDAP user attributes that should be made available
+ * as parameter tokens.
+ *
+ * @return
+ * A list of all LDAP user attributes that should be made available as
+ * parameter tokens.
+ *
+ * @throws GuacamoleException
+ * If the names of the LDAP user attributes to be exposed as parameter
+ * tokens cannot be retrieved.
+ */
+ List getAttributes() throws GuacamoleException;
+
+ /**
+ * Returns the name of the LDAP attribute used to enumerate members in a
+ * group.
+ *
+ * @return
+ * The name of the LDAP attribute to use to enumerate
+ * members in a group.
+ *
+ * @throws GuacamoleException
+ * If the group member attribute cannot be retrieved.
+ */
+ String getMemberAttribute() throws GuacamoleException;
+
+ /**
+ * Returns whether the LDAP attribute used to enumerate members in a group
+ * specifies a UID or DN.
+ *
+ * @return
+ * The type of data contained in the LDAP attribute used to enumerate
+ * members in a group.
+ *
+ * @throws GuacamoleException
+ * If the type of attribute used to enumerate group members cannot be
+ * retrieved.
+ */
+ MemberAttributeType getMemberAttributeType() throws GuacamoleException;
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java
index 5bf5cfbd6..1db4f723c 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java
@@ -254,6 +254,17 @@ public class LDAPGuacamoleProperties {
};
+ /**
+ * Number of milliseconds to wait for responses from the LDAP server.
+ */
+ public static final IntegerGuacamoleProperty LDAP_NETWORK_TIMEOUT =
+ new IntegerGuacamoleProperty() {
+
+ @Override
+ public String getName() { return "ldap-network-timeout"; }
+
+ };
+
/**
* Custom attribute or attributes to query from Guacamole user's record in
* the LDAP directory.
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java
index 84bef7f92..9da1547ba 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/connection/ConnectionService.java
@@ -35,16 +35,13 @@ import org.apache.directory.api.ldap.model.filter.EqualityNode;
import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.api.ldap.model.filter.OrNode;
import org.apache.directory.api.ldap.model.name.Dn;
-import org.apache.directory.ldap.client.api.LdapConnectionConfig;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider;
-import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration;
import org.apache.guacamole.auth.ldap.ObjectQueryService;
import org.apache.guacamole.auth.ldap.group.UserGroupService;
import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser;
-import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.TokenInjectingConnection;
import org.apache.guacamole.net.auth.simple.SimpleConnection;
@@ -63,12 +60,6 @@ public class ConnectionService {
*/
private static final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
- /**
- * Service for retrieving LDAP server configuration information.
- */
- @Inject
- private ConfigurationService confService;
-
/**
* Service for executing LDAP queries.
*/
@@ -124,17 +115,12 @@ public class ConnectionService {
/**
- * Returns all Guacamole connections accessible to the user currently bound
- * under the given LDAP connection.
+ * Returns all Guacamole connections accessible to the given user.
*
* @param user
* The AuthenticatedUser object associated with the user who is
* currently authenticated with Guacamole.
*
- * @param ldapConnection
- * The current connection to the LDAP server, associated with the
- * current user.
- *
* @return
* All connections accessible to the user currently bound under the
* given LDAP connection, as a map of connection identifier to
@@ -143,34 +129,27 @@ public class ConnectionService {
* @throws GuacamoleException
* If an error occurs preventing retrieval of connections.
*/
- public Map getConnections(AuthenticatedUser user,
- LdapNetworkConnection ldapConnection) throws GuacamoleException {
+ public Map getConnections(LDAPAuthenticatedUser user)
+ throws GuacamoleException {
+
+ ConnectedLDAPConfiguration ldapConfig = user.getLDAPConfiguration();
// Do not return any connections if base DN is not specified
- Dn configurationBaseDN = confService.getConfigurationBaseDN();
+ Dn configurationBaseDN = ldapConfig.getConfigurationBaseDN();
if (configurationBaseDN == null)
return Collections.emptyMap();
try {
- // Pull the current user DN from the LDAP connection
- LdapConnectionConfig ldapConnectionConfig = ldapConnection.getConfig();
- Dn userDN = new Dn(ldapConnectionConfig.getName());
-
- // getConnections() will only be called after a connection has been
- // authenticated (via non-anonymous bind), thus userDN cannot
- // possibly be null
- assert(userDN != null);
-
// Get the search filter for finding connections accessible by the
// current user
- ExprNode connectionSearchFilter = getConnectionSearchFilter(userDN, ldapConnection);
+ ExprNode connectionSearchFilter = getConnectionSearchFilter(user);
// Find all Guacamole connections for the given user by
// looking for direct membership in the guacConfigGroup
// and possibly any groups the user is a member of that are
// referred to in the seeAlso attribute of the guacConfigGroup.
- List results = queryService.search(ldapConnection,
+ List results = queryService.search(ldapConfig, ldapConfig.getLDAPConnection(),
configurationBaseDN, connectionSearchFilter, 0, GUAC_CONFIG_LDAP_ATTRIBUTES);
// Return a map of all readable connections
@@ -261,8 +240,7 @@ public class ConnectionService {
// Inject LDAP-specific tokens only if LDAP handled user
// authentication
if (user instanceof LDAPAuthenticatedUser)
- connection = new TokenInjectingConnection(connection,
- ((LDAPAuthenticatedUser) user).getTokens());
+ connection = new TokenInjectingConnection(connection, user.getTokens());
return connection;
@@ -277,14 +255,11 @@ public class ConnectionService {
/**
* Returns an LDAP search filter which queries all connections accessible
- * by the user having the given DN.
+ * by the given user.
*
- * @param userDN
- * DN of the user to search for associated guacConfigGroup connections.
- *
- * @param ldapConnection
- * LDAP connection to use if additional information must be queried to
- * produce the filter, such as groups driving RBAC.
+ * @param user
+ * The AuthenticatedUser object associated with the user who is
+ * currently authenticated with Guacamole.
*
* @return
* An LDAP search filter which queries all guacConfigGroup objects
@@ -296,10 +271,12 @@ public class ConnectionService {
* @throws GuacamoleException
* If an error occurs retrieving the group base DN.
*/
- private ExprNode getConnectionSearchFilter(Dn userDN,
- LdapNetworkConnection ldapConnection)
+ private ExprNode getConnectionSearchFilter(LDAPAuthenticatedUser user)
throws LdapException, GuacamoleException {
+ ConnectedLDAPConfiguration config = user.getLDAPConfiguration();
+ Dn userDN = config.getBindDN();
+
AndNode searchFilter = new AndNode();
// Add the prefix to the search filter, prefix filter searches for guacConfigGroups with the userDN as the member attribute value
@@ -307,12 +284,12 @@ public class ConnectionService {
// Apply group filters
OrNode groupFilter = new OrNode();
- groupFilter.addNode(new EqualityNode(confService.getMemberAttribute(),
+ groupFilter.addNode(new EqualityNode(config.getMemberAttribute(),
userDN.toString()));
// Additionally filter by group membership if the current user is a
// member of any user groups
- List userGroups = userGroupService.getParentUserGroupEntries(ldapConnection, userDN);
+ List userGroups = userGroupService.getParentUserGroupEntries(config, userDN);
if (!userGroups.isEmpty()) {
userGroups.forEach(entry ->
groupFilter.addNode(new EqualityNode(LDAP_ATTRIBUTE_NAME_GROUPS,entry.getDn().toString()))
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java
index 67ffbc3fb..b38fc9198 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java
@@ -33,11 +33,12 @@ import org.apache.directory.api.ldap.model.filter.EqualityNode;
import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.api.ldap.model.filter.NotNode;
import org.apache.directory.api.ldap.model.name.Dn;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
-import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
import org.apache.guacamole.auth.ldap.conf.MemberAttributeType;
import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration;
import org.apache.guacamole.auth.ldap.ObjectQueryService;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
+import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser;
import org.apache.guacamole.net.auth.UserGroup;
import org.apache.guacamole.net.auth.simple.SimpleUserGroup;
import org.slf4j.Logger;
@@ -54,12 +55,6 @@ public class UserGroupService {
*/
private static final Logger logger = LoggerFactory.getLogger(UserGroupService.class);
- /**
- * Service for retrieving LDAP server configuration information.
- */
- @Inject
- private ConfigurationService confService;
-
/**
* Service for executing LDAP queries.
*/
@@ -73,22 +68,25 @@ public class UserGroupService {
* defined (may always return zero results), it should only be explicitly
* excluded if it is expected to have been defined.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @return
* The base search filter which should be used to retrieve user groups.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
- private ExprNode getGroupSearchFilter() throws GuacamoleException {
+ private ExprNode getGroupSearchFilter(LDAPConfiguration config) throws GuacamoleException {
// Use filter defined by "ldap-group-search-filter" as basis for all
// retrieval of user groups
- ExprNode groupFilter = confService.getGroupSearchFilter();
+ ExprNode groupFilter = config.getGroupSearchFilter();
// Explicitly exclude guacConfigGroup object class only if it should
// be assumed to be defined (query may fail due to no such object
// class existing otherwise)
- if (confService.getConfigurationBaseDN() != null) {
+ if (config.getConfigurationBaseDN() != null) {
groupFilter = new AndNode(
groupFilter,
new NotNode(new EqualityNode("objectClass", "guacConfigGroup"))
@@ -100,12 +98,11 @@ public class UserGroupService {
}
/**
- * Returns all Guacamole user groups accessible to the user currently bound
- * under the given LDAP connection.
+ * Returns all Guacamole user groups accessible to the given user.
*
- * @param ldapConnection
- * The current connection to the LDAP server, associated with the
- * current user.
+ * @param user
+ * The AuthenticatedUser object associated with the user who is
+ * currently authenticated with Guacamole.
*
* @return
* All user groups accessible to the user currently bound under the
@@ -115,25 +112,28 @@ public class UserGroupService {
* @throws GuacamoleException
* If an error occurs preventing retrieval of user groups.
*/
- public Map getUserGroups(LdapNetworkConnection ldapConnection)
+ public Map getUserGroups(LDAPAuthenticatedUser user)
throws GuacamoleException {
+ ConnectedLDAPConfiguration config = user.getLDAPConfiguration();
+
// Do not return any user groups if base DN is not specified
- Dn groupBaseDN = confService.getGroupBaseDN();
+ Dn groupBaseDN = config.getGroupBaseDN();
if (groupBaseDN == null)
return Collections.emptyMap();
// Gather all attributes relevant for a group
- String memberAttribute = confService.getMemberAttribute();
- Collection groupAttributes = new HashSet<>(confService.getGroupNameAttributes());
+ String memberAttribute = config.getMemberAttribute();
+ Collection groupAttributes = new HashSet<>(config.getGroupNameAttributes());
groupAttributes.add(memberAttribute);
// Retrieve all visible user groups which are not guacConfigGroups
- Collection attributes = confService.getGroupNameAttributes();
+ Collection attributes = config.getGroupNameAttributes();
List results = queryService.search(
- ldapConnection,
+ config,
+ config.getLDAPConnection(),
groupBaseDN,
- getGroupSearchFilter(),
+ getGroupSearchFilter(config),
attributes,
null,
groupAttributes
@@ -167,9 +167,8 @@ public class UserGroupService {
* user is a member of. Only user groups which are readable by the current
* user will be retrieved.
*
- * @param ldapConnection
- * The current connection to the LDAP server, associated with the
- * current user.
+ * @param config
+ * The configuration of the LDAP server being queried.
*
* @param userDN
* The DN of the user whose group membership should be retrieved.
@@ -181,24 +180,25 @@ public class UserGroupService {
* @throws GuacamoleException
* If an error occurs preventing retrieval of user groups.
*/
- public List getParentUserGroupEntries(LdapNetworkConnection ldapConnection,
- Dn userDN) throws GuacamoleException {
+ public List getParentUserGroupEntries(ConnectedLDAPConfiguration config, Dn userDN)
+ throws GuacamoleException {
// Do not return any user groups if base DN is not specified
- Dn groupBaseDN = confService.getGroupBaseDN();
+ Dn groupBaseDN = config.getGroupBaseDN();
if (groupBaseDN == null)
return Collections.emptyList();
// memberAttribute specified in properties could contain DN or username
- MemberAttributeType memberAttributeType = confService.getMemberAttributeType();
+ MemberAttributeType memberAttributeType = config.getMemberAttributeType();
String userIDorDN = userDN.toString();
- Collection userAttributes = confService.getUsernameAttributes();
+ Collection userAttributes = config.getUsernameAttributes();
if (memberAttributeType == MemberAttributeType.UID) {
// Retrieve user objects with userDN
List userEntries = queryService.search(
- ldapConnection,
+ config,
+ config.getLDAPConnection(),
userDN,
- confService.getUserSearchFilter(),
+ config.getUserSearchFilter(),
0,
userAttributes);
// ... there can surely only be one
@@ -222,16 +222,17 @@ public class UserGroupService {
}
// Gather all attributes relevant for a group
- String memberAttribute = confService.getMemberAttribute();
- Collection groupAttributes = new HashSet<>(confService.getGroupNameAttributes());
+ String memberAttribute = config.getMemberAttribute();
+ Collection groupAttributes = new HashSet<>(config.getGroupNameAttributes());
groupAttributes.add(memberAttribute);
// Get all groups the user is a member of starting at the groupBaseDN,
// excluding guacConfigGroups
return queryService.search(
- ldapConnection,
+ config,
+ config.getLDAPConnection(),
groupBaseDN,
- getGroupSearchFilter(),
+ getGroupSearchFilter(config),
Collections.singleton(memberAttribute),
userIDorDN,
groupAttributes
@@ -244,9 +245,8 @@ public class UserGroupService {
* member of. Only identifiers of user groups which are readable by the
* current user will be retrieved.
*
- * @param ldapConnection
- * The current connection to the LDAP server, associated with the
- * current user.
+ * @param config
+ * The configuration of the LDAP server being queried.
*
* @param userDN
* The DN of the user whose group membership should be retrieved.
@@ -258,11 +258,11 @@ public class UserGroupService {
* @throws GuacamoleException
* If an error occurs preventing retrieval of user groups.
*/
- public Set getParentUserGroupIdentifiers(LdapNetworkConnection ldapConnection,
- Dn userDN) throws GuacamoleException {
+ public Set getParentUserGroupIdentifiers(ConnectedLDAPConfiguration config, Dn userDN)
+ throws GuacamoleException {
- Collection attributes = confService.getGroupNameAttributes();
- List userGroups = getParentUserGroupEntries(ldapConnection, userDN);
+ Collection attributes = config.getGroupNameAttributes();
+ List userGroups = getParentUserGroupEntries(config, userDN);
Set identifiers = new HashSet<>(userGroups.size());
userGroups.forEach(entry -> {
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java
index 44296432f..2abe4aef0 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPAuthenticatedUser.java
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration;
import org.apache.guacamole.net.auth.AbstractAuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
@@ -63,9 +64,19 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser {
*/
private Dn bindDn;
+ /**
+ * The configuration of the LDAP server that should be used for all queries
+ * related to this AuthenticatedUser.
+ */
+ private ConnectedLDAPConfiguration config;
+
/**
* Initializes this AuthenticatedUser with the given credentials,
- * connection parameter tokens. and set of effective user groups.
+ * connection parameter tokens, and set of effective user groups.
+ *
+ * @param config
+ * The configuration of the LDAP server that should be used for all
+ * queries related to this AuthenticatedUser.
*
* @param credentials
* The credentials provided when this user was authenticated.
@@ -77,17 +88,15 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser {
* @param effectiveGroups
* The unique identifiers of all user groups which affect the
* permissions available to this user.
- *
- * @param bindDn
- * The LDAP DN used to bind this user.
*/
- public void init(Credentials credentials, Map tokens,
- Set effectiveGroups, Dn bindDn) {
+ public void init(UserLDAPConfiguration config, Credentials credentials,
+ Map tokens, Set effectiveGroups) {
+ this.config = config;
this.credentials = credentials;
this.tokens = Collections.unmodifiableMap(tokens);
this.effectiveGroups = effectiveGroups;
- this.bindDn = bindDn;
- setIdentifier(credentials.getUsername());
+ this.bindDn = config.getBindDN();
+ setIdentifier(config.getGuacamoleUsername());
}
/**
@@ -103,7 +112,7 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser {
public Map getTokens() {
return tokens;
}
-
+
/**
* Returns the LDAP DN used to bind this user.
*
@@ -114,6 +123,18 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser {
return bindDn;
}
+ /**
+ * Returns the configuration of the LDAP server that should be used for all
+ * queries related to this AuthenticatedUser.
+ *
+ * @return
+ * The configuration of the LDAP server related to this
+ * AuthenticatedUser.
+ */
+ public ConnectedLDAPConfiguration getLDAPConfiguration() {
+ return config;
+ }
+
@Override
public AuthenticationProvider getAuthenticationProvider() {
return authProvider;
@@ -129,4 +150,9 @@ public class LDAPAuthenticatedUser extends AbstractAuthenticatedUser {
return effectiveGroups;
}
+ @Override
+ public void invalidate() {
+ config.close();
+ }
+
}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java
index b5c789e1e..79b1af718 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/LDAPUserContext.java
@@ -21,13 +21,11 @@ package org.apache.guacamole.auth.ldap.user;
import com.google.inject.Inject;
import java.util.Collections;
-import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.guacamole.auth.ldap.connection.ConnectionService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider;
import org.apache.guacamole.auth.ldap.group.UserGroupService;
import org.apache.guacamole.net.auth.AbstractUserContext;
-import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.ConnectionGroup;
@@ -101,38 +99,32 @@ public class LDAPUserContext extends AbstractUserContext {
private ConnectionGroup rootGroup;
/**
- * Initializes this UserContext using the provided AuthenticatedUser and
- * LdapNetworkConnection.
+ * Initializes this UserContext using the provided AuthenticatedUser.
*
* @param user
* The AuthenticatedUser representing the user that authenticated. This
- * user may have been authenticated by a different authentication
- * provider (not LDAP).
- *
- * @param ldapConnection
- * The connection to the LDAP server to use when querying accessible
- * Guacamole users and connections.
+ * user will always have been authenticated via LDAP, as LDAP data is
+ * not provided to non-LDAP users.
*
* @throws GuacamoleException
* If associated data stored within the LDAP directory cannot be
* queried due to an error.
*/
- public void init(AuthenticatedUser user, LdapNetworkConnection ldapConnection)
- throws GuacamoleException {
+ public void init(LDAPAuthenticatedUser user) throws GuacamoleException {
// Query all accessible users
userDirectory = new SimpleDirectory<>(
- userService.getUsers(ldapConnection)
+ userService.getUsers(user)
);
// Query all accessible user groups
userGroupDirectory = new SimpleDirectory<>(
- userGroupService.getUserGroups(ldapConnection)
+ userGroupService.getUserGroups(user)
);
// Query all accessible connections
connectionDirectory = new SimpleDirectory<>(
- connectionService.getConnections(user, ldapConnection)
+ connectionService.getConnections(user)
);
// Root group contains only connections
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserLDAPConfiguration.java
new file mode 100644
index 000000000..3d2cd8221
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserLDAPConfiguration.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.guacamole.auth.ldap.user;
+
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
+import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
+
+/**
+ * LDAPConfiguration implementation that represents the configuration and
+ * network connection of an LDAP that has been bound on behalf of a Guacamole
+ * user.
+ */
+public class UserLDAPConfiguration extends ConnectedLDAPConfiguration {
+
+ /**
+ * The username of the associated Guacamole user.
+ */
+ private final String username;
+
+ /**
+ * Creates a new UserLDAPConfiguration that associates the given
+ * LDAPConfiguration of an LDAP server with the active network connection to
+ * that server, as well as the username of the Guacamole user on behalf of
+ * whom that connection was established. All functions inherited from the
+ * LDAPConfiguration interface are delegated to the given LDAPConfiguration.
+ * It is the responsibility of the caller to ensure the provided
+ * LdapNetworkConnection is closed after it is no longer needed.
+ *
+ * @param config
+ * The LDAPConfiguration to wrap.
+ *
+ * @param username
+ * The username of the associated Guacamole user.
+ *
+ * @param bindDn
+ * The LDAP DN that was used to bind with the LDAP server to produce
+ * the given LdapNetworkConnection.
+ *
+ * @param connection
+ * The connection to the LDAP server represented by the given
+ * configuration.
+ */
+ public UserLDAPConfiguration(LDAPConfiguration config,
+ String username, Dn bindDn, LdapNetworkConnection connection) {
+ super(config, bindDn, connection);
+ this.username = username;
+ }
+
+ /**
+ * Returns the username of the Guacamole user on behalf of whom the
+ * associated LDAP network connection was established.
+ *
+ * @return
+ * The username of the associated Guacamole user.
+ */
+ public String getGuacamoleUsername() {
+ return username;
+ }
+
+}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
index 1ffc270be..fa9fe1522 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/user/UserService.java
@@ -32,11 +32,12 @@ import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueEx
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
-import org.apache.guacamole.auth.ldap.conf.ConfigurationService;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
+import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration;
import org.apache.guacamole.auth.ldap.conf.LDAPGuacamoleProperties;
import org.apache.guacamole.auth.ldap.ObjectQueryService;
+import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.simple.SimpleUser;
import org.slf4j.Logger;
@@ -53,12 +54,6 @@ public class UserService {
*/
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
- /**
- * Service for retrieving LDAP server configuration information.
- */
- @Inject
- private ConfigurationService confService;
-
/**
* Service for executing LDAP queries.
*/
@@ -66,12 +61,11 @@ public class UserService {
private ObjectQueryService queryService;
/**
- * Returns all Guacamole users accessible to the user currently bound under
- * the given LDAP connection.
+ * Returns all Guacamole users accessible to the given user.
*
- * @param ldapConnection
- * The current connection to the LDAP server, associated with the
- * current user.
+ * @param user
+ * The AuthenticatedUser object associated with the user who is
+ * currently authenticated with Guacamole.
*
* @return
* All users accessible to the user currently bound under the given
@@ -81,19 +75,24 @@ public class UserService {
* @throws GuacamoleException
* If an error occurs preventing retrieval of users.
*/
- public Map getUsers(LdapNetworkConnection ldapConnection)
+ public Map getUsers(LDAPAuthenticatedUser user)
throws GuacamoleException {
+ ConnectedLDAPConfiguration config = user.getLDAPConfiguration();
+
// Retrieve all visible user objects
- Collection usernameAttrs = confService.getUsernameAttributes();
+ Collection usernameAttrs = config.getUsernameAttributes();
Collection attributes = new HashSet<>(usernameAttrs);
- attributes.addAll(confService.getAttributes());
- List results = queryService.search(ldapConnection,
- confService.getUserBaseDN(),
- confService.getUserSearchFilter(),
- usernameAttrs,
- null,
- attributes);
+ attributes.addAll(config.getAttributes());
+ List results = queryService.search(
+ config,
+ config.getLDAPConnection(),
+ config.getUserBaseDN(),
+ config.getUserSearchFilter(),
+ usernameAttrs,
+ null,
+ attributes
+ );
// Convert retrieved users to map of identifier to Guacamole user object
return queryService.asMap(results, entry -> {
@@ -124,6 +123,9 @@ public class UserService {
* is not enforced across the username attribute, it is possible that this
* will return multiple DNs.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param ldapConnection
* The connection to the LDAP server to use when querying user DNs.
*
@@ -139,14 +141,14 @@ public class UserService {
* If an error occurs while querying the user DNs, or if the username
* attribute property cannot be parsed within guacamole.properties.
*/
- public List getUserDNs(LdapNetworkConnection ldapConnection,
+ public List getUserDNs(LDAPConfiguration config, LdapNetworkConnection ldapConnection,
String username) throws GuacamoleException {
// Retrieve user objects having a matching username
- List results = queryService.search(ldapConnection,
- confService.getUserBaseDN(),
- confService.getUserSearchFilter(),
- confService.getUsernameAttributes(),
+ List results = queryService.search(config, ldapConnection,
+ config.getUserBaseDN(),
+ config.getUserSearchFilter(),
+ config.getUsernameAttributes(),
username,
Collections.singletonList("dn"));
@@ -164,6 +166,9 @@ public class UserService {
* or queried from the LDAP server, depending on how LDAP authentication
* has been configured.
*
+ * @param config
+ * The configuration of the LDAP server being queried.
+ *
* @param username
* The username of the user whose corresponding DN should be returned.
*
@@ -174,11 +179,11 @@ public class UserService {
* If required properties are missing, and thus the user DN cannot be
* determined.
*/
- public Dn deriveUserDN(String username)
+ public Dn deriveUserDN(LDAPConfiguration config, String username)
throws GuacamoleException {
// Pull username attributes from properties
- List usernameAttributes = confService.getUsernameAttributes();
+ List usernameAttributes = config.getUsernameAttributes();
// We need exactly one base DN to derive the user DN
if (usernameAttributes.size() != 1) {
@@ -193,7 +198,7 @@ public class UserService {
// Derive user DN from base DN
try {
return new Dn(new Rdn(usernameAttributes.get(0), username),
- confService.getUserBaseDN());
+ config.getUserBaseDN());
}
catch (LdapInvalidAttributeValueException | LdapInvalidDnException e) {
throw new GuacamoleServerException("Error trying to derive user DN.", e);
diff --git a/pom.xml b/pom.xml
index 0480d5bf1..f133e2c18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -377,6 +377,11 @@
jackson-databind
${jackson.version}
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${jackson.version}
+
com.fasterxml.jackson.module
jackson-module-jaxb-annotations