diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml index f12905ec1..d5175be8e 100644 --- a/extensions/guacamole-auth-ldap/pom.xml +++ b/extensions/guacamole-auth-ldap/pom.xml @@ -102,6 +102,18 @@ 4.3 + + + com.google.inject + guice + 3.0 + + + com.google.inject.extensions + guice-multibindings + 3.0 + + diff --git a/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/LDAPAuthenticationProvider.java b/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/LDAPAuthenticationProvider.java index 9a25b94a2..945bcd9bd 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/LDAPAuthenticationProvider.java +++ b/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/LDAPAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,15 @@ package net.sourceforge.guacamole.net.auth.ldap; -import com.novell.ldap.LDAPAttribute; -import com.novell.ldap.LDAPConnection; -import com.novell.ldap.LDAPEntry; -import com.novell.ldap.LDAPException; -import com.novell.ldap.LDAPSearchResults; -import java.io.UnsupportedEncodingException; -import java.util.Enumeration; -import java.util.Map; -import java.util.TreeMap; +import org.glyptodon.guacamole.auth.ldap.AuthenticationProviderService; +import org.glyptodon.guacamole.auth.ldap.LDAPAuthenticationProviderModule; +import com.google.inject.Guice; +import com.google.inject.Injector; import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.AuthenticatedUser; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; import org.glyptodon.guacamole.net.auth.Credentials; -import net.sourceforge.guacamole.net.auth.ldap.properties.LDAPGuacamoleProperties; -import org.glyptodon.guacamole.GuacamoleServerException; -import org.glyptodon.guacamole.environment.Environment; -import org.glyptodon.guacamole.environment.LocalEnvironment; -import org.glyptodon.guacamole.net.auth.simple.SimpleAuthenticationProvider; -import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.glyptodon.guacamole.net.auth.UserContext; /** * Allows users to be authenticated against an LDAP server. Each user may have @@ -50,17 +40,13 @@ import org.slf4j.LoggerFactory; * * @author Michael Jumper */ -public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider { +public class LDAPAuthenticationProvider implements AuthenticationProvider { /** - * Logger for this class. + * Injector which will manage the object graph of this authentication + * provider. */ - private Logger logger = LoggerFactory.getLogger(LDAPAuthenticationProvider.class); - - /** - * Guacamole server environment. - */ - private final Environment environment; + private final Injector injector; /** * Creates a new LDAPAuthenticationProvider that authenticates users @@ -71,7 +57,12 @@ public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider { * a property. */ public LDAPAuthenticationProvider() throws GuacamoleException { - environment = new LocalEnvironment(); + + // Set up Guice injector. + injector = Guice.createInjector( + new LDAPAuthenticationProviderModule(this) + ); + } @Override @@ -79,219 +70,33 @@ public class LDAPAuthenticationProvider extends SimpleAuthenticationProvider { return "ldap"; } - // Courtesy of OWASP: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java - private static String escapeLDAPSearchFilter(String filter) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < filter.length(); i++) { - char curChar = filter.charAt(i); - switch (curChar) { - case '\\': - sb.append("\\5c"); - break; - case '*': - sb.append("\\2a"); - break; - case '(': - sb.append("\\28"); - break; - case ')': - sb.append("\\29"); - break; - case '\u0000': - sb.append("\\00"); - break; - default: - sb.append(curChar); - } - } - return sb.toString(); + @Override + public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { + + AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class); + return authProviderService.authenticateUser(credentials); + } - // Courtesy of OWASP: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java - private static String escapeDN(String name) { - StringBuilder sb = new StringBuilder(); - if ((name.length() > 0) && ((name.charAt(0) == ' ') || (name.charAt(0) == '#'))) { - sb.append('\\'); // add the leading backslash if needed - } - for (int i = 0; i < name.length(); i++) { - char curChar = name.charAt(i); - switch (curChar) { - case '\\': - sb.append("\\\\"); - break; - case ',': - sb.append("\\,"); - break; - case '+': - sb.append("\\+"); - break; - case '"': - sb.append("\\\""); - break; - case '<': - sb.append("\\<"); - break; - case '>': - sb.append("\\>"); - break; - case ';': - sb.append("\\;"); - break; - default: - sb.append(curChar); - } - } - if ((name.length() > 1) && (name.charAt(name.length() - 1) == ' ')) { - sb.insert(sb.length() - 1, '\\'); // add the trailing backslash if needed - } - return sb.toString(); - } + @Override + public AuthenticatedUser updateAuthenticatedUser(AuthenticatedUser authenticatedUser, + Credentials credentials) throws GuacamoleException { + return authenticatedUser; + } @Override - public Map getAuthorizedConfigurations(Credentials credentials) throws GuacamoleException { + public UserContext getUserContext(AuthenticatedUser authenticatedUser) + throws GuacamoleException { - // Require username - if (credentials.getUsername() == null) { - logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider."); - return null; - } + AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class); + return authProviderService.getUserContext(authenticatedUser); - // Require password, and do not allow anonymous binding - if (credentials.getPassword() == null - || credentials.getPassword().length() == 0) { - logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider."); - return null; - } - - // Connect to LDAP server - LDAPConnection ldapConnection; - try { - - ldapConnection = new LDAPConnection(); - ldapConnection.connect( - environment.getRequiredProperty(LDAPGuacamoleProperties.LDAP_HOSTNAME), - environment.getRequiredProperty(LDAPGuacamoleProperties.LDAP_PORT) - ); - - } - catch (LDAPException e) { - throw new GuacamoleServerException("Unable to connect to LDAP server.", e); - } - - // Get username attribute - String username_attribute = environment.getRequiredProperty( - LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE - ); - - // Get user base DN - String user_base_dn = environment.getRequiredProperty( - LDAPGuacamoleProperties.LDAP_USER_BASE_DN - ); - - // Construct user DN - String user_dn = - escapeDN(username_attribute) + "=" + escapeDN(credentials.getUsername()) - + "," + user_base_dn; - - try { - - // Bind as user - try { - ldapConnection.bind( - LDAPConnection.LDAP_V3, - user_dn, - credentials.getPassword().getBytes("UTF-8") - ); - } - catch (UnsupportedEncodingException e) { - throw new GuacamoleException(e); - } - - } - catch (LDAPException e) { - logger.debug("LDAP bind failed.", e); - return null; - } - - // Get config base DN - String config_base_dn = environment.getRequiredProperty( - LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN - ); - - // Pull all connections - try { - - // Find all guac configs for this user - LDAPSearchResults results = ldapConnection.search( - config_base_dn, - LDAPConnection.SCOPE_SUB, - "(&(objectClass=guacConfigGroup)(member=" + escapeLDAPSearchFilter(user_dn) + "))", - null, - false - ); - - // Add all configs - Map configs = new TreeMap(); - while (results.hasMore()) { - - LDAPEntry entry = results.next(); - - // New empty configuration - GuacamoleConfiguration config = new GuacamoleConfiguration(); - - // Get CN - LDAPAttribute cn = entry.getAttribute("cn"); - if (cn == null) - throw new GuacamoleException("guacConfigGroup without cn"); - - // Get protocol - LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); - if (protocol == null) - throw new GuacamoleException("guacConfigGroup without guacConfigProtocol"); - - // Set protocol - config.setProtocol(protocol.getStringValue()); - - // Get parameters, if any - LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); - if (parameterAttribute != null) { - - // For each parameter - Enumeration parameters = parameterAttribute.getStringValues(); - while (parameters.hasMoreElements()) { - - String parameter = (String) parameters.nextElement(); - - // Parse parameter - int equals = parameter.indexOf('='); - if (equals != -1) { - - // Parse name - String name = parameter.substring(0, equals); - String value = parameter.substring(equals+1); - - config.setParameter(name, value); - - } - - } - - } - - // Store config by CN - configs.put(cn.getStringValue(), config); - - } - - // Disconnect - ldapConnection.disconnect(); - return configs; - - } - catch (LDAPException e) { - throw new GuacamoleServerException("Error while querying for connections.", e); - } + } + @Override + public UserContext updateUserContext(UserContext context, + AuthenticatedUser authenticatedUser) throws GuacamoleException { + return context; } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/AuthenticationProviderService.java new file mode 100644 index 000000000..8702c7ea8 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/AuthenticationProviderService.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPException; +import java.io.UnsupportedEncodingException; +import org.glyptodon.guacamole.auth.ldap.user.AuthenticatedUser; +import org.glyptodon.guacamole.auth.ldap.user.UserContext; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.net.auth.Credentials; +import org.glyptodon.guacamole.net.auth.credentials.CredentialsInfo; +import org.glyptodon.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service providing convenience functions for the LDAP AuthenticationProvider + * implementation. + * + * @author Michael Jumper + */ +public class AuthenticationProviderService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); + + /** + * Service for escaping parts of LDAP queries. + */ + @Inject + private EscapingService escapingService; + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Provider for AuthenticatedUser objects. + */ + @Inject + private Provider authenticatedUserProvider; + + /** + * Provider for UserContext objects. + */ + @Inject + private Provider userContextProvider; + + /** + * Binds to the LDAP server using the provided Guacamole credentials. The + * DN of the user is derived using the LDAP configuration properties + * provided in guacamole.properties, as is the server hostname and port + * information. + * + * @param credentials + * The credentials to use to bind to the LDAP server. + * + * @return + * A bound LDAP connection, or null if the connection could not be + * bound. + * + * @throws GuacamoleException + * If an error occurs while binding to the LDAP server. + */ + private LDAPConnection bindAs(Credentials credentials) + throws GuacamoleException { + + LDAPConnection ldapConnection; + + // Require username + if (credentials.getUsername() == null) { + logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider."); + return null; + } + + // Require password, and do not allow anonymous binding + if (credentials.getPassword() == null + || credentials.getPassword().length() == 0) { + logger.debug("Anonymous bind is not currently allowed by the LDAP authentication provider."); + return null; + } + + // Connect to LDAP server + try { + ldapConnection = new LDAPConnection(); + ldapConnection.connect( + confService.getServerHostname(), + confService.getServerPort() + ); + } + catch (LDAPException e) { + logger.error("Unable to connect to LDAP server: {}", e.getMessage()); + logger.debug("Failed to connect to LDAP server.", e); + return null; + } + + // Bind using provided credentials + try { + + // Construct user DN + String userDN = + escapingService.escapeDN(confService.getUsernameAttribute()) + + "=" + escapingService.escapeDN(credentials.getUsername()) + + "," + confService.getUserBaseDN(); + + // Bind as user + try { + ldapConnection.bind(LDAPConnection.LDAP_V3, userDN, + credentials.getPassword().getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + logger.error("Unexpected lack of support for UTF-8: {}", e.getMessage()); + logger.debug("Support for UTF-8 (as required by Java spec) not found.", e); + return null; + } + + // Disconnect if an error occurs during bind + catch (LDAPException e) { + ldapConnection.disconnect(); + throw e; + } + + } + catch (LDAPException e) { + logger.debug("LDAP bind failed.", e); + return null; + } + + return ldapConnection; + + } + + public AuthenticatedUser authenticateUser(Credentials credentials) + throws GuacamoleException { + + // Attempt bind + LDAPConnection ldapConnection = bindAs(credentials); + if (ldapConnection == null) + throw new GuacamoleInsufficientCredentialsException("Permission denied.", CredentialsInfo.USERNAME_PASSWORD); + + try { + + // Return AuthenticatedUser if bind succeeds + AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(credentials); + return authenticatedUser; + + } + + // Always disconnect + finally { + + // Attempt disconnect + try { + ldapConnection.disconnect(); + } + + // Warn if disconnect unexpectedly fails + catch (LDAPException e) { + logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage()); + logger.debug("LDAP disconnect failed.", e); + } + + } + + } + + /** + * Returns a UserContext object initialized with data accessible to the + * given AuthenticatedUser. + * + * @param authenticatedUser + * The AuthenticatedUser to retrieve data for. + * + * @return + * A UserContext object initialized with data accessible to the given + * AuthenticatedUser. + * + * @throws GuacamoleException + * If the UserContext cannot be created due to an error. + */ + public UserContext getUserContext(org.glyptodon.guacamole.net.auth.AuthenticatedUser authenticatedUser) + throws GuacamoleException { + + // Bind using credentials associated with AuthenticatedUser + Credentials credentials = authenticatedUser.getCredentials(); + LDAPConnection ldapConnection = bindAs(credentials); + if (ldapConnection == null) + return null; + + try { + + // Build user context by querying LDAP + UserContext userContext = userContextProvider.get(); + userContext.init(authenticatedUser, ldapConnection); + return userContext; + + } + + // Always disconnect + finally { + + // Attempt disconnect + try { + ldapConnection.disconnect(); + } + + // Warn if disconnect unexpectedly fails + catch (LDAPException e) { + logger.warn("Unable to disconnect from LDAP server: {}", e.getMessage()); + logger.debug("LDAP disconnect failed.", e); + } + + } + + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/ConfigurationService.java new file mode 100644 index 000000000..692300499 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/ConfigurationService.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap; + +import com.google.inject.Inject; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.environment.Environment; + +/** + * Service for retrieving configuration information regarding the LDAP server. + * + * @author Michael Jumper + */ +public class ConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * Returns the hostname of the LDAP server as configured with + * guacamole.properties. By default, this will be "localhost". + * + * @return + * The hostname of the LDAP server, as configured with + * guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getServerHostname() throws GuacamoleException { + return environment.getProperty( + LDAPGuacamoleProperties.LDAP_HOSTNAME, + "localhost" + ); + } + + /** + * Returns the port of the LDAP server configured with + * guacamole.properties. By default, this will be 389 - the standard LDAP + * port. + * + * @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, + 389 + ); + } + + /** + * Returns the username attribute 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 attribute which should be used to query and bind users + * using the LDAP directory. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getUsernameAttribute() throws GuacamoleException { + return environment.getProperty( + LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE, + "uid" + ); + } + + /** + * 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 String getUserBaseDN() throws GuacamoleException { + return environment.getRequiredProperty( + LDAPGuacamoleProperties.LDAP_USER_BASE_DN + ); + } + + /** + * Returns the base DN under which all Guacamole configurations + * (connections) will be stored within the LDAP directory. + * + * @return + * The base DN under which all Guacamole configurations will be stored + * within the LDAP directory. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed, or if the configuration + * base DN property is not specified. + */ + public String getConfigurationBaseDN() throws GuacamoleException { + return environment.getRequiredProperty( + LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN + ); + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/EscapingService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/EscapingService.java new file mode 100644 index 000000000..925ba24cf --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/EscapingService.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap; + +/** + * Service for escaping LDAP filters, distinguished names (DN's), etc. + * + * @author Michael Jumper + */ +public class EscapingService { + + /** + * Escapes the given string for use within an LDAP search filter. This + * implementation is provided courtesy of OWASP: + * + * https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java + * + * @param filter + * The string to escape such that it has no special meaning within an + * LDAP search filter. + * + * @return + * The escaped string, safe for use within an LDAP search filter. + */ + public String escapeLDAPSearchFilter(String filter) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < filter.length(); i++) { + char curChar = filter.charAt(i); + switch (curChar) { + case '\\': + sb.append("\\5c"); + break; + case '*': + sb.append("\\2a"); + break; + case '(': + sb.append("\\28"); + break; + case ')': + sb.append("\\29"); + break; + case '\u0000': + sb.append("\\00"); + break; + default: + sb.append(curChar); + } + } + return sb.toString(); + } + + /** + * Escapes the given string such that it is safe for use within an LDAP + * distinguished name (DN). This implementation is provided courtesy of + * OWASP: + * + * https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java + * + * @param name + * The string to escape such that it has no special meaning within an + * LDAP DN. + * + * @return + * The escaped string, safe for use within an LDAP DN. + */ + public String escapeDN(String name) { + StringBuilder sb = new StringBuilder(); + if ((name.length() > 0) && ((name.charAt(0) == ' ') || (name.charAt(0) == '#'))) { + sb.append('\\'); // add the leading backslash if needed + } + for (int i = 0; i < name.length(); i++) { + char curChar = name.charAt(i); + switch (curChar) { + case '\\': + sb.append("\\\\"); + break; + case ',': + sb.append("\\,"); + break; + case '+': + sb.append("\\+"); + break; + case '"': + sb.append("\\\""); + break; + case '<': + sb.append("\\<"); + break; + case '>': + sb.append("\\>"); + break; + case ';': + sb.append("\\;"); + break; + default: + sb.append(curChar); + } + } + if ((name.length() > 1) && (name.charAt(name.length() - 1) == ' ')) { + sb.insert(sb.length() - 1, '\\'); // add the trailing backslash if needed + } + return sb.toString(); + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java new file mode 100644 index 000000000..4dbc1eddc --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPAuthenticationProviderModule.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap; + +import com.google.inject.AbstractModule; +import org.glyptodon.guacamole.auth.ldap.connection.ConnectionService; +import org.glyptodon.guacamole.auth.ldap.user.UserService; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.environment.Environment; +import org.glyptodon.guacamole.environment.LocalEnvironment; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; + +/** + * Guice module which configures LDAP-specific injections. + * + * @author Michael Jumper + */ +public class LDAPAuthenticationProviderModule extends AbstractModule { + + /** + * Guacamole server environment. + */ + private final Environment environment; + + /** + * A reference to the LDAPAuthenticationProvider on behalf of which this + * module has configured injection. + */ + private final AuthenticationProvider authProvider; + + /** + * Creates a new LDAP authentication provider module which configures + * injection for the LDAPAuthenticationProvider. + * + * @param authProvider + * The AuthenticationProvider for which injection is being configured. + * + * @throws GuacamoleException + * If an error occurs while retrieving the Guacamole server + * environment. + */ + public LDAPAuthenticationProviderModule(AuthenticationProvider authProvider) + throws GuacamoleException { + + // Get local environment + this.environment = new LocalEnvironment(); + + // Store associated auth provider + this.authProvider = authProvider; + + } + + @Override + protected void configure() { + + // Bind core implementations of guacamole-ext classes + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(Environment.class).toInstance(environment); + + // Bind LDAP-specific services + bind(ConfigurationService.class); + bind(ConnectionService.class); + bind(EscapingService.class); + bind(UserService.class); + + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/properties/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPGuacamoleProperties.java similarity index 98% rename from extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/properties/LDAPGuacamoleProperties.java rename to extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPGuacamoleProperties.java index e97c01a1e..8fabf816e 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/net/sourceforge/guacamole/net/auth/ldap/properties/LDAPGuacamoleProperties.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/LDAPGuacamoleProperties.java @@ -20,7 +20,7 @@ * THE SOFTWARE. */ -package net.sourceforge.guacamole.net.auth.ldap.properties; +package org.glyptodon.guacamole.auth.ldap; import org.glyptodon.guacamole.properties.IntegerGuacamoleProperty; import org.glyptodon.guacamole.properties.StringGuacamoleProperty; diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java new file mode 100644 index 000000000..71a5afc52 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/connection/ConnectionService.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap.connection; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPAttribute; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPEntry; +import com.novell.ldap.LDAPException; +import com.novell.ldap.LDAPSearchResults; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import org.glyptodon.guacamole.auth.ldap.ConfigurationService; +import org.glyptodon.guacamole.auth.ldap.EscapingService; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.simple.SimpleConnection; +import org.glyptodon.guacamole.protocol.GuacamoleConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for querying the connections available to a particular Guacamole + * user according to an LDAP directory. + * + * @author Michael Jumper + */ +public class ConnectionService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(ConnectionService.class); + + /** + * Service for escaping parts of LDAP queries. + */ + @Inject + private EscapingService escapingService; + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Returns all Guacamole connections accessible to the user currently bound + * under the given LDAP connection. + * + * @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 + * corresponding connection object. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of connections. + */ + public Map getConnections(LDAPConnection ldapConnection) + throws GuacamoleException { + + try { + + // Pull the current user DN from the LDAP connection + String userDN = ldapConnection.getAuthenticationDN(); + + // Find all Guacamole connections for the given user + LDAPSearchResults results = ldapConnection.search( + confService.getConfigurationBaseDN(), + LDAPConnection.SCOPE_SUB, + "(&(objectClass=guacConfigGroup)(member=" + escapingService.escapeLDAPSearchFilter(userDN) + "))", + null, + false + ); + + // Produce connections for each readable configuration + Map connections = new HashMap(); + while (results.hasMore()) { + + LDAPEntry entry = results.next(); + + // Get common name (CN) + LDAPAttribute cn = entry.getAttribute("cn"); + if (cn == null) { + logger.warn("guacConfigGroup is missing a cn."); + continue; + } + + // Get associated protocol + LDAPAttribute protocol = entry.getAttribute("guacConfigProtocol"); + if (protocol == null) { + logger.warn("guacConfigGroup \"{}\" is missing the " + + "required \"guacConfigProtocol\" attribute.", + cn.getStringValue()); + continue; + } + + // Set protocol + GuacamoleConfiguration config = new GuacamoleConfiguration(); + config.setProtocol(protocol.getStringValue()); + + // Get parameters, if any + LDAPAttribute parameterAttribute = entry.getAttribute("guacConfigParameter"); + if (parameterAttribute != null) { + + // For each parameter + Enumeration parameters = parameterAttribute.getStringValues(); + while (parameters.hasMoreElements()) { + + String parameter = (String) parameters.nextElement(); + + // Parse parameter + int equals = parameter.indexOf('='); + if (equals != -1) { + + // Parse name + String name = parameter.substring(0, equals); + String value = parameter.substring(equals+1); + + config.setParameter(name, value); + + } + + } + + } + + // Store connection using cn for both identifier and name + String name = cn.getStringValue(); + connections.put(name, new SimpleConnection(name, name, config)); + + } + + // Return map of all connections + return connections; + + } + catch (LDAPException e) { + throw new GuacamoleServerException("Error while querying for connections.", e); + } + + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/AuthenticatedUser.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/AuthenticatedUser.java new file mode 100644 index 000000000..e594193ec --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/AuthenticatedUser.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap.user; + +import com.google.inject.Inject; +import org.glyptodon.guacamole.net.auth.AbstractAuthenticatedUser; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; +import org.glyptodon.guacamole.net.auth.Credentials; + +/** + * An LDAP-specific implementation of AuthenticatedUser, associating a + * particular set of credentials with the LDAP authentication provider. + * + * @author Michael Jumper + */ +public class AuthenticatedUser extends AbstractAuthenticatedUser { + + /** + * Reference to the authentication provider associated with this + * authenticated user. + */ + @Inject + private AuthenticationProvider authProvider; + + /** + * The credentials provided when this user was authenticated. + */ + private Credentials credentials; + + /** + * Initializes this AuthenticatedUser using the given credentials. + * + * @param credentials + * The credentials provided when this user was authenticated. + */ + public void init(Credentials credentials) { + this.credentials = credentials; + setIdentifier(credentials.getUsername()); + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + @Override + public Credentials getCredentials() { + return credentials; + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserContext.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserContext.java new file mode 100644 index 000000000..930e8b42d --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserContext.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap.user; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPConnection; +import java.util.Collection; +import java.util.Collections; +import org.glyptodon.guacamole.auth.ldap.connection.ConnectionService; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.form.Form; +import org.glyptodon.guacamole.net.auth.ActiveConnection; +import org.glyptodon.guacamole.net.auth.AuthenticatedUser; +import org.glyptodon.guacamole.net.auth.AuthenticationProvider; +import org.glyptodon.guacamole.net.auth.Connection; +import org.glyptodon.guacamole.net.auth.ConnectionGroup; +import org.glyptodon.guacamole.net.auth.Directory; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.simple.SimpleConnectionGroup; +import org.glyptodon.guacamole.net.auth.simple.SimpleConnectionGroupDirectory; +import org.glyptodon.guacamole.net.auth.simple.SimpleDirectory; +import org.glyptodon.guacamole.net.auth.simple.SimpleUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An LDAP-specific implementation of UserContext which queries all Guacamole + * connections and users from the LDAP directory. + * + * @author Michael Jumper + */ +public class UserContext implements org.glyptodon.guacamole.net.auth.UserContext { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(UserContext.class); + + /** + * The identifier reserved for the root connection group. + */ + private static final String ROOT_CONNECTION_GROUP = "ROOT"; + + /** + * Service for retrieving Guacamole connections from the LDAP server. + */ + @Inject + private ConnectionService connectionService; + + /** + * Service for retrieving Guacamole users from the LDAP server. + */ + @Inject + private UserService userService; + + /** + * Reference to the AuthenticationProvider associated with this + * UserContext. + */ + @Inject + private AuthenticationProvider authProvider; + + /** + * Reference to a User object representing the user whose access level + * dictates the users and connections visible through this UserContext. + */ + private User self; + + /** + * Directory containing all User objects accessible to the user associated + * with this UserContext. + */ + private Directory userDirectory; + + /** + * Directory containing all Connection objects accessible to the user + * associated with this UserContext. + */ + private Directory connectionDirectory; + + /** + * Directory containing all ConnectionGroup objects accessible to the user + * associated with this UserContext. + */ + private Directory connectionGroupDirectory; + + /** + * Reference to the root connection group. + */ + private ConnectionGroup rootGroup; + + /** + * Initializes this UserContext using the provided AuthenticatedUser and + * LDAPConnection. + * + * @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. + * + * @throws GuacamoleException + * If associated data stored within the LDAP directory cannot be + * queried due to an error. + */ + public void init(AuthenticatedUser user, LDAPConnection ldapConnection) + throws GuacamoleException { + + // Query all accessible users + userDirectory = new SimpleDirectory( + userService.getUsers(ldapConnection) + ); + + // Query all accessible connections + connectionDirectory = new SimpleDirectory( + connectionService.getConnections(ldapConnection) + ); + + // Root group contains only connections + rootGroup = new SimpleConnectionGroup( + ROOT_CONNECTION_GROUP, ROOT_CONNECTION_GROUP, + connectionDirectory.getIdentifiers(), + Collections.emptyList() + ); + + // Expose only the root group in the connection group directory + connectionGroupDirectory = new SimpleConnectionGroupDirectory(Collections.singleton(rootGroup)); + + // Init self with basic permissions + self = new SimpleUser( + user.getIdentifier(), + userDirectory.getIdentifiers(), + connectionDirectory.getIdentifiers(), + connectionGroupDirectory.getIdentifiers() + ); + + } + + @Override + public User self() { + return self; + } + + @Override + public AuthenticationProvider getAuthenticationProvider() { + return authProvider; + } + + @Override + public Directory getUserDirectory() throws GuacamoleException { + return userDirectory; + } + + @Override + public Directory getConnectionDirectory() + throws GuacamoleException { + return connectionDirectory; + } + + @Override + public Directory getConnectionGroupDirectory() + throws GuacamoleException { + return connectionGroupDirectory; + } + + @Override + public ConnectionGroup getRootConnectionGroup() throws GuacamoleException { + return rootGroup; + } + + @Override + public Directory getActiveConnectionDirectory() + throws GuacamoleException { + return new SimpleDirectory(); + } + + @Override + public Collection
getUserAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection getConnectionAttributes() { + return Collections.emptyList(); + } + + @Override + public Collection getConnectionGroupAttributes() { + return Collections.emptyList(); + } + +} diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserService.java b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserService.java new file mode 100644 index 000000000..9b72c0f44 --- /dev/null +++ b/extensions/guacamole-auth-ldap/src/main/java/org/glyptodon/guacamole/auth/ldap/user/UserService.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.glyptodon.guacamole.auth.ldap.user; + +import com.google.inject.Inject; +import com.novell.ldap.LDAPAttribute; +import com.novell.ldap.LDAPConnection; +import com.novell.ldap.LDAPEntry; +import com.novell.ldap.LDAPException; +import com.novell.ldap.LDAPSearchResults; +import java.util.HashMap; +import java.util.Map; +import org.glyptodon.guacamole.auth.ldap.ConfigurationService; +import org.glyptodon.guacamole.auth.ldap.EscapingService; +import org.glyptodon.guacamole.GuacamoleException; +import org.glyptodon.guacamole.GuacamoleServerException; +import org.glyptodon.guacamole.net.auth.User; +import org.glyptodon.guacamole.net.auth.simple.SimpleUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Service for queries the users visible to a particular Guacamole user + * according to an LDAP directory. + * + * @author Michael Jumper + */ +public class UserService { + + /** + * Logger for this class. + */ + private final Logger logger = LoggerFactory.getLogger(UserService.class); + + /** + * Service for escaping parts of LDAP queries. + */ + @Inject + private EscapingService escapingService; + + /** + * Service for retrieving LDAP server configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Returns all Guacamole users accessible to the user currently bound under + * the given LDAP connection. + * + * @param ldapConnection + * The current connection to the LDAP server, associated with the + * current user. + * + * @return + * All users accessible to the user currently bound under the given + * LDAP connection, as a map of connection identifier to corresponding + * user object. + * + * @throws GuacamoleException + * If an error occurs preventing retrieval of users. + */ + public Map getUsers(LDAPConnection ldapConnection) + throws GuacamoleException { + + try { + + // Get username attribute + String usernameAttribute = confService.getUsernameAttribute(); + + // Find all Guacamole users underneath base DN + LDAPSearchResults results = ldapConnection.search( + confService.getUserBaseDN(), + LDAPConnection.SCOPE_ONE, + "(&(objectClass=*)(" + escapingService.escapeLDAPSearchFilter(usernameAttribute) + "=*))", + null, + false + ); + + // Read all visible users + Map users = new HashMap(); + while (results.hasMore()) { + + LDAPEntry entry = results.next(); + + // Get common name (CN) + LDAPAttribute username = entry.getAttribute(usernameAttribute); + if (username == null) { + logger.warn("Queried user is missing the username attribute \"{}\".", usernameAttribute); + continue; + } + + // Store connection using cn for both identifier and name + String identifier = username.getStringValue(); + users.put(identifier, new SimpleUser(identifier)); + + } + + // Return map of all connections + return users; + + } + catch (LDAPException e) { + throw new GuacamoleServerException("Error while querying users.", e); + } + + } + +} diff --git a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUser.java b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUser.java index 96171c381..046458acb 100644 --- a/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUser.java +++ b/guacamole-ext/src/main/java/org/glyptodon/guacamole/net/auth/simple/SimpleUser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,7 +28,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import org.glyptodon.guacamole.GuacamoleException; -import org.glyptodon.guacamole.GuacamoleSecurityException; import org.glyptodon.guacamole.net.auth.AbstractUser; import org.glyptodon.guacamole.net.auth.permission.ObjectPermission; import org.glyptodon.guacamole.net.auth.permission.ObjectPermissionSet; @@ -41,6 +40,12 @@ import org.glyptodon.guacamole.net.auth.permission.SystemPermissionSet; */ public class SimpleUser extends AbstractUser { + /** + * All connection permissions granted to this user. + */ + private final Set userPermissions = + new HashSet(); + /** * All connection permissions granted to this user. */ @@ -59,6 +64,19 @@ public class SimpleUser extends AbstractUser { public SimpleUser() { } + /** + * Creates a new SimpleUser having the given username and no permissions. + * + * @param username + * The username to assign to this SimpleUser. + */ + public SimpleUser(String username) { + + // Set username + setIdentifier(username); + + } + /** * Adds a new READ permission to the given set of permissions for each of * the given identifiers. @@ -89,6 +107,7 @@ public class SimpleUser extends AbstractUser { * * @param username * The username to assign to this SimpleUser. + * * @param connectionIdentifiers * The identifiers of all connections this user has READ access to. * @@ -100,8 +119,7 @@ public class SimpleUser extends AbstractUser { Collection connectionIdentifiers, Collection connectionGroupIdentifiers) { - // Set username - setIdentifier(username); + this(username); // Add permissions addReadPermissions(connectionPermissions, connectionIdentifiers); @@ -109,6 +127,37 @@ public class SimpleUser extends AbstractUser { } + /** + * Creates a new SimpleUser having the given username and READ access to + * the users, connections, and groups having the given identifiers. + * + * @param username + * The username to assign to this SimpleUser. + * + * @param userIdentifiers + * The identifiers of all users this user has READ access to. + * + * @param connectionIdentifiers + * The identifiers of all connections this user has READ access to. + * + * @param connectionGroupIdentifiers + * The identifiers of all connection groups this user has READ access + * to. + */ + public SimpleUser(String username, + Collection userIdentifiers, + Collection connectionIdentifiers, + Collection connectionGroupIdentifiers) { + + this(username); + + // Add permissions + addReadPermissions(userPermissions, userIdentifiers); + addReadPermissions(connectionPermissions, connectionIdentifiers); + addReadPermissions(connectionGroupPermissions, connectionGroupIdentifiers); + + } + @Override public Map getAttributes() { return Collections.emptyMap(); @@ -140,7 +189,7 @@ public class SimpleUser extends AbstractUser { @Override public ObjectPermissionSet getUserPermissions() throws GuacamoleException { - return new SimpleObjectPermissionSet(); + return new SimpleObjectPermissionSet(userPermissions); } @Override