mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-27 23:23:07 +00:00 
			
		
		
		
	GUAC-586: Implement listing of visible users within LDAP (by completely rewriting the LDAP auth provider).
This commit is contained in:
		| @@ -102,6 +102,18 @@ | ||||
|             <version>4.3</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Guice --> | ||||
|         <dependency> | ||||
|             <groupId>com.google.inject</groupId> | ||||
|             <artifactId>guice</artifactId> | ||||
|             <version>3.0</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.google.inject.extensions</groupId> | ||||
|             <artifactId>guice-multibindings</artifactId> | ||||
|             <version>3.0</version> | ||||
|         </dependency> | ||||
|  | ||||
|     </dependencies> | ||||
|  | ||||
| </project> | ||||
|   | ||||
| @@ -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<String, GuacamoleConfiguration> 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<String, GuacamoleConfiguration> configs = new TreeMap<String, GuacamoleConfiguration>(); | ||||
|             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; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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<AuthenticatedUser> authenticatedUserProvider; | ||||
|  | ||||
|     /** | ||||
|      * Provider for UserContext objects. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Provider<UserContext> 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); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
| @@ -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<String, Connection> 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<String, Connection> connections = new HashMap<String, Connection>(); | ||||
|             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); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<User> userDirectory; | ||||
|  | ||||
|     /** | ||||
|      * Directory containing all Connection objects accessible to the user | ||||
|      * associated with this UserContext. | ||||
|      */ | ||||
|     private Directory<Connection> connectionDirectory; | ||||
|  | ||||
|     /** | ||||
|      * Directory containing all ConnectionGroup objects accessible to the user | ||||
|      * associated with this UserContext. | ||||
|      */ | ||||
|     private Directory<ConnectionGroup> 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<User>( | ||||
|             userService.getUsers(ldapConnection) | ||||
|         ); | ||||
|  | ||||
|         // Query all accessible connections | ||||
|         connectionDirectory = new SimpleDirectory<Connection>( | ||||
|             connectionService.getConnections(ldapConnection) | ||||
|         ); | ||||
|  | ||||
|         // Root group contains only connections | ||||
|         rootGroup = new SimpleConnectionGroup( | ||||
|             ROOT_CONNECTION_GROUP, ROOT_CONNECTION_GROUP, | ||||
|             connectionDirectory.getIdentifiers(), | ||||
|             Collections.<String>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<User> getUserDirectory() throws GuacamoleException { | ||||
|         return userDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<Connection> getConnectionDirectory() | ||||
|             throws GuacamoleException { | ||||
|         return connectionDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<ConnectionGroup> getConnectionGroupDirectory() | ||||
|             throws GuacamoleException { | ||||
|         return connectionGroupDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ConnectionGroup getRootConnectionGroup() throws GuacamoleException { | ||||
|         return rootGroup; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<ActiveConnection> getActiveConnectionDirectory() | ||||
|             throws GuacamoleException { | ||||
|         return new SimpleDirectory<ActiveConnection>(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<Form> getUserAttributes() { | ||||
|         return Collections.<Form>emptyList(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<Form> getConnectionAttributes() { | ||||
|         return Collections.<Form>emptyList(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<Form> getConnectionGroupAttributes() { | ||||
|         return Collections.<Form>emptyList(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<String, User> 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<String, User> users = new HashMap<String, User>(); | ||||
|             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); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user