Add .gitignore and .ratignore files for various directories
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		
							
								
								
									
										2
									
								
								extensions/guacamole-auth-ldap/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								extensions/guacamole-auth-ldap/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| target/ | ||||
| *~ | ||||
							
								
								
									
										0
									
								
								extensions/guacamole-auth-ldap/.ratignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								extensions/guacamole-auth-ldap/.ratignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # | ||||
| # Licensed to the Apache Software Foundation (ASF) under one | ||||
| # or more contributor license agreements.  See the NOTICE file | ||||
| # distributed with this work for additional information | ||||
| # regarding copyright ownership.  The ASF licenses this file | ||||
| # to you under the Apache License, Version 2.0 (the | ||||
| # "License"); you may not use this file except in compliance | ||||
| # with the License.  You may obtain a copy of the License at | ||||
| # | ||||
| #   http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, | ||||
| # software distributed under the License is distributed on an | ||||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
| # KIND, either express or implied.  See the License for the | ||||
| # specific language governing permissions and limitations | ||||
| # under the License. | ||||
| # | ||||
|  | ||||
| dn: cn=Example Config,dc=guac-dev,dc=org | ||||
| objectClass: guacConfigGroup | ||||
| objectClass: groupOfNames | ||||
| cn: Example Config | ||||
| guacConfigProtocol: vnc | ||||
| guacConfigParameter: hostname=localhost | ||||
| guacConfigParameter: port=5900 | ||||
| guacConfigParameter: password=secret | ||||
| member: cn=user1,dc=example,dc=com | ||||
| member: cn=user2,dc=example,dc=com | ||||
| seeAlso: cn=admins,ou=groups,dc=example,dc=com | ||||
							
								
								
									
										111
									
								
								extensions/guacamole-auth-ldap/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								extensions/guacamole-auth-ldap/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
|     Licensed to the Apache Software Foundation (ASF) under one | ||||
|     or more contributor license agreements.  See the NOTICE file | ||||
|     distributed with this work for additional information | ||||
|     regarding copyright ownership.  The ASF licenses this file | ||||
|     to you under the Apache License, Version 2.0 (the | ||||
|     "License"); you may not use this file except in compliance | ||||
|     with the License.  You may obtain a copy of the License at | ||||
|  | ||||
|       http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|     Unless required by applicable law or agreed to in writing, | ||||
|     software distributed under the License is distributed on an | ||||
|     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|     KIND, either express or implied.  See the License for the | ||||
|     specific language governing permissions and limitations | ||||
|     under the License. | ||||
| --> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 | ||||
|                         http://maven.apache.org/maven-v4_0_0.xsd"> | ||||
|  | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <groupId>org.apache.guacamole</groupId> | ||||
|     <artifactId>guacamole-auth-ldap</artifactId> | ||||
|     <packaging>jar</packaging> | ||||
|     <version>1.6.0</version> | ||||
|     <name>guacamole-auth-ldap</name> | ||||
|     <url>http://guacamole.apache.org/</url> | ||||
|  | ||||
|     <parent> | ||||
|         <groupId>org.apache.guacamole</groupId> | ||||
|         <artifactId>extensions</artifactId> | ||||
|         <version>1.6.0</version> | ||||
|         <relativePath>../</relativePath> | ||||
|     </parent> | ||||
|  | ||||
|     <dependencies> | ||||
|  | ||||
|         <!-- Guacamole Extension API --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.guacamole</groupId> | ||||
|             <artifactId>guacamole-ext</artifactId> | ||||
|             <version>1.6.0</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Apache Directory LDAP API --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.directory.api</groupId> | ||||
|             <artifactId>api-all</artifactId> | ||||
|             <version>2.1.7</version> | ||||
|             <exclusions> | ||||
|  | ||||
|                 <!-- | ||||
|                     Replace vulnerable version of Apache MINA until upstream | ||||
|                     releases a version with fixed dependencies. | ||||
|                 --> | ||||
|                 <exclusion> | ||||
|                     <groupId>org.apache.mina</groupId> | ||||
|                     <artifactId>mina-core</artifactId> | ||||
|                 </exclusion> | ||||
|  | ||||
|                 <!-- | ||||
|                     Replace slightly older commons-lang3 (3.15.0) with latest | ||||
|                     compatible version (3.16.0) so that we don't need two copies | ||||
|                     of the same license information. | ||||
|                 --> | ||||
|                 <exclusion> | ||||
|                     <groupId>org.apache.commons</groupId> | ||||
|                     <artifactId>commons-lang3</artifactId> | ||||
|                 </exclusion> | ||||
|  | ||||
|             </exclusions> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Guice --> | ||||
|         <dependency> | ||||
|             <groupId>com.google.inject</groupId> | ||||
|             <artifactId>guice</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Jackson and YAML support --> | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.jackson.core</groupId> | ||||
|             <artifactId>jackson-databind</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>com.fasterxml.jackson.dataformat</groupId> | ||||
|             <artifactId>jackson-dataformat-yaml</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Apache Commons Lang (see exclusions for api-all) --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.commons</groupId> | ||||
|             <artifactId>commons-lang3</artifactId> | ||||
|             <version>3.17.0</version> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Apache MINA (see exclusions for api-all) --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.mina</groupId> | ||||
|             <artifactId>mina-core</artifactId> | ||||
|             <version>2.2.4</version> | ||||
|         </dependency> | ||||
|  | ||||
|     </dependencies> | ||||
|  | ||||
| </project> | ||||
							
								
								
									
										44
									
								
								extensions/guacamole-auth-ldap/schema/guacConfigGroup.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								extensions/guacamole-auth-ldap/schema/guacConfigGroup.ldif
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| # | ||||
| # Licensed to the Apache Software Foundation (ASF) under one | ||||
| # or more contributor license agreements.  See the NOTICE file | ||||
| # distributed with this work for additional information | ||||
| # regarding copyright ownership.  The ASF licenses this file | ||||
| # to you under the Apache License, Version 2.0 (the | ||||
| # "License"); you may not use this file except in compliance | ||||
| # with the License.  You may obtain a copy of the License at | ||||
| # | ||||
| #   http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, | ||||
| # software distributed under the License is distributed on an | ||||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
| # KIND, either express or implied.  See the License for the | ||||
| # specific language governing permissions and limitations | ||||
| # under the License. | ||||
| # | ||||
|  | ||||
| dn: cn=guacConfigGroup,cn=schema,cn=config | ||||
| objectClass: olcSchemaConfig | ||||
| cn: guacConfigGroup | ||||
|  | ||||
| olcAttributeTypes: ( 1.3.6.1.4.1.38971.1.1.1 NAME 'guacConfigProtocol' | ||||
|   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
| olcAttributeTypes: ( 1.3.6.1.4.1.38971.1.1.2 NAME 'guacConfigParameter' | ||||
|   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
| olcAttributeTypes: ( 1.3.6.1.4.1.38971.1.1.3 NAME 'guacConfigProxyHostname' | ||||
|   SINGLE-VALUE | ||||
|   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
| olcAttributeTypes: ( 1.3.6.1.4.1.38971.1.1.4 NAME 'guacConfigProxyPort' | ||||
|   SINGLE-VALUE | ||||
|   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) | ||||
| olcAttributeTypes: ( 1.3.6.1.4.1.38971.1.1.5 NAME 'guacConfigProxyEncryption' | ||||
|   SINGLE-VALUE | ||||
|   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
| olcObjectClasses: ( 1.3.6.1.4.1.38971.1.2.1 NAME 'guacConfigGroup' | ||||
|   DESC 'Guacamole configuration group' | ||||
|   SUP groupOfNames | ||||
|   MUST guacConfigProtocol | ||||
|   MAY ( guacConfigParameter $ | ||||
|   guacConfigProxyHostname $ | ||||
|   guacConfigProxyPort $ | ||||
|   guacConfigProxyEncryption ) ) | ||||
							
								
								
									
										46
									
								
								extensions/guacamole-auth-ldap/schema/guacConfigGroup.schema
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								extensions/guacamole-auth-ldap/schema/guacConfigGroup.schema
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| # | ||||
| # Licensed to the Apache Software Foundation (ASF) under one | ||||
| # or more contributor license agreements.  See the NOTICE file | ||||
| # distributed with this work for additional information | ||||
| # regarding copyright ownership.  The ASF licenses this file | ||||
| # to you under the Apache License, Version 2.0 (the | ||||
| # "License"); you may not use this file except in compliance | ||||
| # with the License.  You may obtain a copy of the License at | ||||
| # | ||||
| #   http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, | ||||
| # software distributed under the License is distributed on an | ||||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
| # KIND, either express or implied.  See the License for the | ||||
| # specific language governing permissions and limitations | ||||
| # under the License. | ||||
| # | ||||
|  | ||||
| attributetype ( 1.3.6.1.4.1.38971.1.1.1 NAME 'guacConfigProtocol' | ||||
|     SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
|  | ||||
| attributetype ( 1.3.6.1.4.1.38971.1.1.2 NAME 'guacConfigParameter' | ||||
|     SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
|  | ||||
| attributetype ( 1.3.6.1.4.1.38971.1.1.3 NAME 'guacConfigProxyHostname' | ||||
|     SINGLE-VALUE | ||||
|     SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
|  | ||||
| attributetype ( 1.3.6.1.4.1.38971.1.1.4 NAME 'guacConfigProxyPort' | ||||
|     SINGLE-VALUE | ||||
|     SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) | ||||
|  | ||||
| attributetype ( 1.3.6.1.4.1.38971.1.1.5 NAME 'guacConfigProxyEncryption' | ||||
|     SINGLE-VALUE | ||||
|     SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) | ||||
|  | ||||
| objectClass ( 1.3.6.1.4.1.38971.1.2.1 NAME 'guacConfigGroup' | ||||
|     DESC 'Guacamole configuration group' | ||||
|     SUP groupOfNames | ||||
|     MUST guacConfigProtocol | ||||
|     MAY ( guacConfigParameter $ | ||||
|           guacConfigProxyHostname $ | ||||
|           guacConfigProxyPort $ | ||||
|           guacConfigProxyEncryption ) ) | ||||
|  | ||||
							
								
								
									
										64
									
								
								extensions/guacamole-auth-ldap/src/main/assembly/dist.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								extensions/guacamole-auth-ldap/src/main/assembly/dist.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
|     Licensed to the Apache Software Foundation (ASF) under one | ||||
|     or more contributor license agreements.  See the NOTICE file | ||||
|     distributed with this work for additional information | ||||
|     regarding copyright ownership.  The ASF licenses this file | ||||
|     to you under the Apache License, Version 2.0 (the | ||||
|     "License"); you may not use this file except in compliance | ||||
|     with the License.  You may obtain a copy of the License at | ||||
|  | ||||
|       http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|     Unless required by applicable law or agreed to in writing, | ||||
|     software distributed under the License is distributed on an | ||||
|     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|     KIND, either express or implied.  See the License for the | ||||
|     specific language governing permissions and limitations | ||||
|     under the License. | ||||
| --> | ||||
| <assembly | ||||
|     xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> | ||||
|      | ||||
|     <id>dist</id> | ||||
|     <baseDirectory>${project.artifactId}-${project.version}</baseDirectory> | ||||
|  | ||||
|     <!-- Output tar.gz --> | ||||
|     <formats> | ||||
|         <format>tar.gz</format> | ||||
|     </formats> | ||||
|  | ||||
|     <!-- Include docs, schema, and extension .jar --> | ||||
|     <fileSets> | ||||
|  | ||||
|         <!-- Include docs --> | ||||
|         <fileSet> | ||||
|             <directory>doc</directory> | ||||
|         </fileSet> | ||||
|  | ||||
|         <!-- Include schema --> | ||||
|         <fileSet> | ||||
|             <outputDirectory>schema</outputDirectory> | ||||
|             <directory>schema</directory> | ||||
|         </fileSet> | ||||
|  | ||||
|         <!-- Include licenses --> | ||||
|         <fileSet> | ||||
|             <outputDirectory></outputDirectory> | ||||
|             <directory>target/licenses</directory> | ||||
|         </fileSet> | ||||
|  | ||||
|         <!-- Include extension .jar --> | ||||
|         <fileSet> | ||||
|             <directory>target</directory> | ||||
|             <outputDirectory></outputDirectory> | ||||
|             <includes> | ||||
|                 <include>*.jar</include> | ||||
|             </includes> | ||||
|         </fileSet> | ||||
|  | ||||
|     </fileSets> | ||||
|  | ||||
| </assembly> | ||||
| @@ -0,0 +1,486 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
| import org.apache.guacamole.auth.ldap.user.UserLDAPConfiguration; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| import org.apache.directory.api.ldap.model.entry.Attribute; | ||||
| import org.apache.directory.api.ldap.model.entry.Entry; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapException; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.ldap.conf.ConfigurationService; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.group.UserGroupService; | ||||
| import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; | ||||
| import org.apache.guacamole.auth.ldap.user.LDAPUserContext; | ||||
| import org.apache.guacamole.auth.ldap.user.UserService; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| import org.apache.guacamole.token.TokenName; | ||||
| import org.apache.guacamole.net.auth.credentials.CredentialsInfo; | ||||
| import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service providing convenience functions for the LDAP AuthenticationProvider | ||||
|  * implementation. | ||||
|  */ | ||||
| public class AuthenticationProviderService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); | ||||
|      | ||||
|     /** | ||||
|      * The prefix that will be used when generating tokens. | ||||
|      */ | ||||
|     public static final String LDAP_TOKEN_PREFIX = "LDAP_"; | ||||
|  | ||||
|     /** | ||||
|      * Regular expression that extracts the Windows / Active Directory domain | ||||
|      * from a username in either of the following formats: down-level logon | ||||
|      * ("DOMAIN\\username") or UPN ("username@domain"). If the username is in | ||||
|      * "DOMAIN\\username" format, the domain will be stored in the first | ||||
|      * capturing group. If the username is in UPN format, the domain will be | ||||
|      * stored in the second capturing group. | ||||
|      */ | ||||
|     private static final Pattern LDAP_USERNAME_DOMAIN = Pattern.compile("^(.+)\\\\.*$|^.*@(.+)$"); | ||||
|  | ||||
|     /** | ||||
|      * The index of the capturing group in {@link #LDAP_USERNAME_DOMAIN} that | ||||
|      * captures the domain of a username in down-level logon format | ||||
|      * ("DOMAIN\\username"). | ||||
|      */ | ||||
|     private static final int LDAP_USERNAME_DOMAIN_DOWN_LEVEL_GROUP = 1; | ||||
|  | ||||
|     /** | ||||
|      * The index of the capturing group in {@link #LDAP_USERNAME_DOMAIN} that | ||||
|      * captures the domain of a username in UPN format ("username@domain"). | ||||
|      */ | ||||
|     private static final int LDAP_USERNAME_DOMAIN_UPN_GROUP = 2; | ||||
|  | ||||
|     /** | ||||
|      * The name of parameter token that will contain the domain extracted from | ||||
|      * the LDAP user's username, if applicable. | ||||
|      */ | ||||
|     public static final String LDAP_DOMAIN_TOKEN = LDAP_TOKEN_PREFIX + "DOMAIN"; | ||||
|  | ||||
|     /** | ||||
|      * Service for creating and managing connections to LDAP servers. | ||||
|      */ | ||||
|     @Inject | ||||
|     private LDAPConnectionService ldapService; | ||||
|  | ||||
|     /** | ||||
|      * Service for retrieving LDAP server configuration information. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ConfigurationService confService; | ||||
|  | ||||
|     /** | ||||
|      * Service for retrieving users and their corresponding LDAP DNs. | ||||
|      */ | ||||
|     @Inject | ||||
|     private UserService userService; | ||||
|  | ||||
|     /** | ||||
|      * Service for retrieving user groups. | ||||
|      */ | ||||
|     @Inject | ||||
|     private UserGroupService userGroupService; | ||||
|  | ||||
|     /** | ||||
|      * Provider for AuthenticatedUser objects. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Provider<LDAPAuthenticatedUser> authenticatedUserProvider; | ||||
|  | ||||
|     /** | ||||
|      * Provider for UserContext objects. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Provider<LDAPUserContext> userContextProvider; | ||||
|  | ||||
|     /** | ||||
|      * Determines the DN which corresponds to the user having the given | ||||
|      * username. The DN will either be derived directly from the user base DN, | ||||
|      * or queried from the LDAP server, depending on how LDAP authentication | ||||
|      * has been configured. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param username | ||||
|      *     The username of the user whose corresponding DN should be returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The DN which corresponds to the user having the given username. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If required properties are missing, and thus the user DN cannot be | ||||
|      *     determined. | ||||
|      */ | ||||
|     private Dn getUserBindDN(LDAPConfiguration config, String username) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // If a search DN is provided, search the LDAP directory for the DN | ||||
|         // corresponding to the given username | ||||
|         String searchBindLogon = config.getSearchBindDN(); | ||||
|         if (searchBindLogon != null) { | ||||
|  | ||||
|             // Create an LDAP connection using the search account | ||||
|             LdapNetworkConnection searchConnection = ldapService.bindAs(config, | ||||
|                 searchBindLogon, config.getSearchBindPassword()); | ||||
|  | ||||
|             // Warn of failure to find | ||||
|             if (searchConnection == null) { | ||||
|                 logger.error("Unable to bind using search DN \"{}\"", | ||||
|                         searchBindLogon); | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             try { | ||||
|  | ||||
|                 // Retrieve all DNs associated with the given username | ||||
|                 List<Dn> userDNs = userService.getUserDNs(config, searchConnection, username); | ||||
|                 if (userDNs.isEmpty()) | ||||
|                     return null; | ||||
|  | ||||
|                 // Warn if multiple DNs exist for the same user | ||||
|                 if (userDNs.size() != 1) { | ||||
|                     logger.warn("Multiple DNs possible for user \"{}\": {}", username, userDNs); | ||||
|                     return null; | ||||
|                 } | ||||
|  | ||||
|                 // Return the single possible DN | ||||
|                 return userDNs.get(0); | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Always disconnect | ||||
|             finally { | ||||
|                 searchConnection.close(); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Otherwise, derive user DN from base DN | ||||
|         return userService.deriveUserDN(config, username); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a new UserLDAPConfiguration that is connected to an LDAP server | ||||
|      * associated with the Guacamole user having the given username and bound | ||||
|      * using the provided password. All LDAP servers associated with the given | ||||
|      * user are tried until the connection and authentication attempt succeeds. | ||||
|      * If no LDAP servers are available, or no LDAP servers are associated with | ||||
|      * the given user, null is returned. The Guacamole username will be | ||||
|      * internally translated to a fully-qualified LDAP DN according to the | ||||
|      * configuration of the LDAP server that is ultimately chosen. | ||||
|      * | ||||
|      * @param username | ||||
|      *      The username of the Guacamole user to bind as. | ||||
|      * | ||||
|      * @param password | ||||
|      *      The password of the user to bind as. | ||||
|      * | ||||
|      * @return | ||||
|      *      A new UserLDAPConfiguration which is bound to an LDAP server using | ||||
|      *      the provided credentials, or null if no LDAP servers are available | ||||
|      *      for the given user or connecting/authenticating has failed. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *      If configuration information for the user's LDAP server(s) cannot | ||||
|      *      be retrieved. | ||||
|      */ | ||||
|     private UserLDAPConfiguration getLDAPConfiguration(String username, | ||||
|             String password) throws GuacamoleException { | ||||
|  | ||||
|         // Get all LDAP server configurations | ||||
|         Collection<? extends LDAPConfiguration> configs = confService.getLDAPConfigurations(); | ||||
|         if (configs.isEmpty()) { | ||||
|             logger.info("Skipping LDAP authentication as no LDAP servers are configured."); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Try each possible LDAP configuration until the TCP connection and | ||||
|         // authentication are successful | ||||
|         for (LDAPConfiguration config : configs) { | ||||
|  | ||||
|             // Attempt connection only if username matches | ||||
|             String translatedUsername = config.appliesTo(username); | ||||
|             if (translatedUsername == null) { | ||||
|                 logger.debug("LDAP server \"{}\" does not match username \"{}\".", config.getServerHostname(), username); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             logger.debug("LDAP server \"{}\" matched username \"{}\" as \"{}\".", | ||||
|                     config.getServerHostname(), username, translatedUsername); | ||||
|  | ||||
|             // Derive DN of user within this LDAP server | ||||
|             Dn bindDn = getUserBindDN(config, translatedUsername); | ||||
|             if (bindDn == null || bindDn.isEmpty()) { | ||||
|                 logger.info("Unable to determine DN of user \"{}\" using LDAP " | ||||
|                         + "server \"{}\". Proceeding with next server...", | ||||
|                         username, config.getServerHostname()); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Attempt bind (authentication) | ||||
|             LdapNetworkConnection ldapConnection = ldapService.bindAs(config, bindDn.getName(), password); | ||||
|             if (ldapConnection == null) { | ||||
|                 logger.info("Unable to bind as user \"{}\" against LDAP " | ||||
|                         + "server \"{}\". Proceeding with next server...", | ||||
|                         username, config.getServerHostname()); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Connection and bind were successful | ||||
|             logger.info("User \"{}\" was successfully authenticated by LDAP server \"{}\".", username, config.getServerHostname()); | ||||
|             return new UserLDAPConfiguration(config, translatedUsername, bindDn, ldapConnection); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // No LDAP connection/authentication attempt succeeded | ||||
|         logger.info("User \"{}\" did not successfully authenticate against any LDAP server.", username); | ||||
|         return null; | ||||
|  | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Returns an AuthenticatedUser representing the user authenticated by the | ||||
|      * given credentials. Also adds custom LDAP attributes to the | ||||
|      * AuthenticatedUser. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *     The credentials to use for authentication. | ||||
|      * | ||||
|      * @return | ||||
|      *     An AuthenticatedUser representing the user authenticated by the | ||||
|      *     given credentials. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while authenticating the user, or if access is | ||||
|      *     denied. | ||||
|      */ | ||||
|     public LDAPAuthenticatedUser authenticateUser(Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|          | ||||
|         String username = credentials.getUsername(); | ||||
|         String password = credentials.getPassword(); | ||||
|          | ||||
|         // Username and password are required | ||||
|         if (username == null | ||||
|                 || username.isEmpty() | ||||
|                 || password == null | ||||
|                 || password.isEmpty()) { | ||||
|             throw new GuacamoleInvalidCredentialsException( | ||||
|                     "Anonymous bind is not currently allowed by the LDAP" | ||||
|                     + " authentication provider.", CredentialsInfo.USERNAME_PASSWORD); | ||||
|         } | ||||
|  | ||||
|         UserLDAPConfiguration config = getLDAPConfiguration(username, password); | ||||
|         if (config == null) | ||||
|             throw new GuacamoleInvalidCredentialsException("Invalid login.", | ||||
|                     CredentialsInfo.USERNAME_PASSWORD); | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             // Retrieve group membership of the user that just authenticated | ||||
|             Set<String> effectiveGroups = | ||||
|                     userGroupService.getParentUserGroupIdentifiers(config, config.getBindDN()); | ||||
|  | ||||
|             // Return AuthenticatedUser if bind succeeds | ||||
|             LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); | ||||
|  | ||||
|             authenticatedUser.init(config, credentials, | ||||
|                     getUserTokens(config, credentials), effectiveGroups); | ||||
|  | ||||
|             return authenticatedUser; | ||||
|         } | ||||
|  | ||||
|         catch (GuacamoleException | RuntimeException | Error e) { | ||||
|             config.close(); | ||||
|             throw e; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the Windows / Active Directory domain included in the username | ||||
|      * of the provided user credentials. If the username does not contain a | ||||
|      * domain, null is returned. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *     The credentials used for authentication. | ||||
|      * | ||||
|      * @return | ||||
|      *     The domain name within the username of the provided credentials, or | ||||
|      *     null if no domain is present. | ||||
|      */ | ||||
|     private String getUserDomain(Credentials credentials) { | ||||
|  | ||||
|         // Extract domain from username (not necessarily present) | ||||
|         String username = credentials.getUsername(); | ||||
|         Matcher matcher = LDAP_USERNAME_DOMAIN.matcher(username); | ||||
|         if (!matcher.find()) | ||||
|             return null; | ||||
|  | ||||
|         String dlDomain = matcher.group(LDAP_USERNAME_DOMAIN_DOWN_LEVEL_GROUP); | ||||
|         String upnDomain = matcher.group(LDAP_USERNAME_DOMAIN_UPN_GROUP); | ||||
|  | ||||
|         // The two domain formats are mutually exclusive. If any format is | ||||
|         // present, the other must be absent unless there is a bug in the | ||||
|         // way the domains are parsed | ||||
|         assert(dlDomain == null || upnDomain == null); | ||||
|  | ||||
|         // Use whichever domain format is present | ||||
|         return dlDomain != null ? dlDomain : upnDomain; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns parameter tokens generated based on details specific to the user | ||||
|      * currently bound under the given LDAP connection. Both LDAP attributes on | ||||
|      * the user's associated LDAP object and the credentials submitted by the user | ||||
|      * to Guacamole are considered. If any tokens are to be derived from LDAP | ||||
|      * attributes, those attributes must be explicitly listed in | ||||
|      * guacamole.properties. If no tokens are applicable, an empty map is returned. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *     The credentials to use for authentication. | ||||
|      * | ||||
|      * @return | ||||
|      *     A map of parameter tokens. These tokens are generated based on | ||||
|      *     the attributes of the user currently bound under the given LDAP connection | ||||
|      *     and the user's credentials. The map's keys are the canonicalized attribute | ||||
|      *     names with an "LDAP_" prefix, and the values are the corresponding attribute | ||||
|      *     values. If the domain name is extracted from the user's credentials, it is added | ||||
|      *     to the map with the key "LDAP_DOMAIN". If no applicable tokens are found, | ||||
|      *     the method returns an empty map. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs retrieving the user DN or the attributes. | ||||
|      */ | ||||
|     private Map<String, String> getUserTokens(ConnectedLDAPConfiguration config, Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Get attributes from configuration information | ||||
|         Collection<String> attrList = config.getAttributes(); | ||||
|  | ||||
|         // If there are no attributes there is no reason to search LDAP | ||||
|         if (attrList.isEmpty()) | ||||
|             return Collections.<String, String>emptyMap(); | ||||
|  | ||||
|         // Build LDAP query parameters | ||||
|         String[] attrArray = attrList.toArray(new String[attrList.size()]); | ||||
|  | ||||
|         Map<String, String> tokens = new HashMap<>(); | ||||
|         try { | ||||
|  | ||||
|             // Get LDAP attributes by querying LDAP | ||||
|             Entry userEntry = config.getLDAPConnection().lookup(config.getBindDN(), attrArray); | ||||
|             if (userEntry == null) | ||||
|                 return Collections.<String, String>emptyMap(); | ||||
|  | ||||
|             Collection<Attribute> attributes = userEntry.getAttributes(); | ||||
|             if (attributes == null) | ||||
|                 return Collections.<String, String>emptyMap(); | ||||
|  | ||||
|             // Convert each retrieved attribute into a corresponding token | ||||
|             for (Attribute attr : attributes) { | ||||
|                 tokens.put(TokenName.canonicalize(attr.getId(), | ||||
|                         LDAP_TOKEN_PREFIX), attr.getString()); | ||||
|             } | ||||
|         } | ||||
|         catch (LdapException e) { | ||||
|             throw new GuacamoleServerException("Could not query LDAP user attributes.", e); | ||||
|         } | ||||
|  | ||||
|         // Extract the domain (ie: Windows / Active Directory domain) from the | ||||
|         // user's credentials | ||||
|         String domainName = getUserDomain(credentials); | ||||
|         if (domainName != null) | ||||
|             tokens.put(LDAP_DOMAIN_TOKEN, domainName); | ||||
|  | ||||
|         return tokens; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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 LDAPUserContext getUserContext(AuthenticatedUser authenticatedUser) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         if (authenticatedUser instanceof LDAPAuthenticatedUser) { | ||||
|  | ||||
|             LDAPAuthenticatedUser ldapAuthenticatedUser = (LDAPAuthenticatedUser) authenticatedUser; | ||||
|             ConnectedLDAPConfiguration config = ldapAuthenticatedUser.getLDAPConfiguration(); | ||||
|  | ||||
|             try { | ||||
|  | ||||
|                 // Build user context by querying LDAP | ||||
|                 LDAPUserContext userContext = userContextProvider.get(); | ||||
|                 userContext.init(ldapAuthenticatedUser); | ||||
|                 return userContext; | ||||
|  | ||||
|             } | ||||
|  | ||||
|             // Always disconnect | ||||
|             finally { | ||||
|                 config.close(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,227 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.conf.EncryptionMethod; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPSSLProtocol; | ||||
| import org.apache.guacamole.auth.ldap.conf.MemberAttributeType; | ||||
|  | ||||
| /** | ||||
|  * LDAPConfiguration implementation that is associated with an | ||||
|  * LdapNetworkConnection to the configured LDAP server. | ||||
|  */ | ||||
| public class ConnectedLDAPConfiguration implements LDAPConfiguration, AutoCloseable { | ||||
|  | ||||
|     /** | ||||
|      * The wrapped LDAPConfiguration. | ||||
|      */ | ||||
|     private final LDAPConfiguration config; | ||||
|  | ||||
|     /** | ||||
|      * The connection to the LDAP server represented by this configuration. | ||||
|      */ | ||||
|     private final LdapNetworkConnection connection; | ||||
|  | ||||
|     /** | ||||
|      * The LDAP DN that was used to bind with the LDAP server to produce | ||||
|      * {@link #connection}. | ||||
|      */ | ||||
|     private final Dn bindDn; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new ConnectedLDAPConfiguration that associates the given | ||||
|      * LdapNetworkConnection with the given LDAPConfiguration. All functions | ||||
|      * inherited from the LDAPConfiguration interface are delegated to the | ||||
|      * given LDAPConfiguration. It is the responsibility of the caller to | ||||
|      * ensure the provided LdapNetworkConnection is closed after it is no | ||||
|      * longer needed. | ||||
|      * | ||||
|      * @param config | ||||
|      *      The LDAPConfiguration to wrap. | ||||
|      * | ||||
|      * @param bindDn | ||||
|      *      The LDAP DN that was used to bind with the LDAP server to produce | ||||
|      *      the given LdapNetworkConnection. | ||||
|      * | ||||
|      * @param connection | ||||
|      *      The connection to the LDAP server represented by the given | ||||
|      *      configuration. | ||||
|      */ | ||||
|     public ConnectedLDAPConfiguration(LDAPConfiguration config, Dn bindDn, LdapNetworkConnection connection) { | ||||
|         this.config = config; | ||||
|         this.bindDn = bindDn; | ||||
|         this.connection = connection; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the LdapNetworkConnection for the connection to the LDAP server | ||||
|      * represented by this configuration. The lifecycle of this connection is | ||||
|      * managed externally. The connection is not guaranteed to still be | ||||
|      * connected. | ||||
|      * | ||||
|      * @return | ||||
|      *     The LdapNetworkConnection for the connection to the LDAP server | ||||
|      *     represented by this configuration. | ||||
|      */ | ||||
|     public LdapNetworkConnection getLDAPConnection() { | ||||
|         return connection; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the LDAP DN that was used to bind with the LDAP server to | ||||
|      * produce the LdapNetworkConnection associated with this | ||||
|      * ConnectedLDAPConfiguration. | ||||
|      * | ||||
|      * @return | ||||
|      *     The LDAP DN that was used to bind with the LDAP server. | ||||
|      */ | ||||
|     public Dn getBindDN() { | ||||
|         return bindDn; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void close() { | ||||
|         connection.close(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String appliesTo(String username) throws GuacamoleException { | ||||
|         return config.appliesTo(username); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getServerHostname() throws GuacamoleException { | ||||
|         return config.getServerHostname(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getServerPort() throws GuacamoleException { | ||||
|         return config.getServerPort(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getUsernameAttributes() throws GuacamoleException { | ||||
|         return config.getUsernameAttributes(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getUserBaseDN() throws GuacamoleException { | ||||
|         return config.getUserBaseDN(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getConfigurationBaseDN() throws GuacamoleException { | ||||
|         return config.getConfigurationBaseDN(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getGroupNameAttributes() throws GuacamoleException { | ||||
|         return config.getGroupNameAttributes(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getGroupBaseDN() throws GuacamoleException { | ||||
|         return config.getGroupBaseDN(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindDN() throws GuacamoleException { | ||||
|         return config.getSearchBindDN(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindPassword() throws GuacamoleException { | ||||
|         return config.getSearchBindPassword(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public EncryptionMethod getEncryptionMethod() throws GuacamoleException { | ||||
|         return config.getEncryptionMethod(); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public LDAPSSLProtocol getSslProtocol() throws GuacamoleException { | ||||
|         return config.getSslProtocol(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxResults() throws GuacamoleException { | ||||
|         return config.getMaxResults(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AliasDerefMode getDereferenceAliases() throws GuacamoleException { | ||||
|         return config.getDereferenceAliases(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean getFollowReferrals() throws GuacamoleException { | ||||
|         return config.getFollowReferrals(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxReferralHops() throws GuacamoleException { | ||||
|         return config.getMaxReferralHops(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getUserSearchFilter() throws GuacamoleException { | ||||
|         return config.getUserSearchFilter(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getGroupSearchFilter() throws GuacamoleException { | ||||
|         return config.getGroupSearchFilter(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getOperationTimeout() throws GuacamoleException { | ||||
|         return config.getOperationTimeout(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getNetworkTimeout() throws GuacamoleException { | ||||
|         return config.getNetworkTimeout(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getAttributes() throws GuacamoleException { | ||||
|         return config.getAttributes(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMemberAttribute() throws GuacamoleException { | ||||
|         return config.getMemberAttribute(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MemberAttributeType getMemberAttributeType() throws GuacamoleException { | ||||
|         return config.getMemberAttributeType(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,105 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
|  | ||||
| import com.google.inject.Guice; | ||||
| import com.google.inject.Injector; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| import org.apache.guacamole.net.auth.TokenInjectingUserContext; | ||||
| import org.apache.guacamole.net.auth.UserContext; | ||||
|  | ||||
| /** | ||||
|  * Allows users to be authenticated against an LDAP server. Each user may have | ||||
|  * any number of authorized configurations. Authorized configurations may be | ||||
|  * shared. | ||||
|  */ | ||||
| public class LDAPAuthenticationProvider extends AbstractAuthenticationProvider { | ||||
|  | ||||
|     /** | ||||
|      * The identifier reserved for the root connection group. | ||||
|      */ | ||||
|     public static final String ROOT_CONNECTION_GROUP = "ROOT"; | ||||
|  | ||||
|     /** | ||||
|      * Injector which will manage the object graph of this authentication | ||||
|      * provider. | ||||
|      */ | ||||
|     private final Injector injector; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new LDAPAuthenticationProvider that authenticates users | ||||
|      * against an LDAP directory. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If a required property is missing, or an error occurs while parsing | ||||
|      *     a property. | ||||
|      */ | ||||
|     public LDAPAuthenticationProvider() throws GuacamoleException { | ||||
|  | ||||
|         // Set up Guice injector. | ||||
|         injector = Guice.createInjector( | ||||
|             new LDAPAuthenticationProviderModule(this) | ||||
|         ); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getIdentifier() { | ||||
|         return "ldap"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AuthenticatedUser authenticateUser(Credentials credentials) throws GuacamoleException { | ||||
|  | ||||
|         AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class); | ||||
|         return authProviderService.authenticateUser(credentials); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserContext getUserContext(AuthenticatedUser authenticatedUser) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class); | ||||
|         return authProviderService.getUserContext(authenticatedUser); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserContext decorate(UserContext context, | ||||
|             AuthenticatedUser authenticatedUser, Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Only decorate if the user authenticated against LDAP | ||||
|         if (!(authenticatedUser instanceof LDAPAuthenticatedUser)) | ||||
|             return context; | ||||
|  | ||||
|         // Apply LDAP-specific tokens to all connections / connection groups | ||||
|         return new TokenInjectingUserContext(context, | ||||
|                 ((LDAPAuthenticatedUser) authenticatedUser).getTokens()); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
| import com.google.inject.AbstractModule; | ||||
| import org.apache.guacamole.auth.ldap.conf.ConfigurationService; | ||||
| import org.apache.guacamole.auth.ldap.connection.ConnectionService; | ||||
| import org.apache.guacamole.auth.ldap.user.UserService; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.group.UserGroupService; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| import org.apache.guacamole.environment.LocalEnvironment; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
|  | ||||
| /** | ||||
|  * Guice module which configures LDAP-specific injections. | ||||
|  */ | ||||
| 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 = LocalEnvironment.getInstance(); | ||||
|  | ||||
|         // 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(LDAPConnectionService.class); | ||||
|         bind(ObjectQueryService.class); | ||||
|         bind(UserGroupService.class); | ||||
|         bind(UserService.class); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,478 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
| import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapException; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.message.SearchRequest; | ||||
| import org.apache.directory.api.ldap.model.message.SearchRequestImpl; | ||||
| import org.apache.directory.api.ldap.model.message.SearchScope; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.api.ldap.model.url.LdapUrl; | ||||
| import org.apache.directory.ldap.client.api.LdapConnectionConfig; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.GuacamoleUnsupportedException; | ||||
| import org.apache.guacamole.auth.ldap.conf.EncryptionMethod; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPSSLProtocol; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for creating and managing connections to LDAP servers. | ||||
|  */ | ||||
| public class LDAPConnectionService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(LDAPConnectionService.class); | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of LdapNetworkConnection, configured as required | ||||
|      * to use the given encryption method to communicate with the LDAP server | ||||
|      * at the given hostname and port, with the specified encryption method, | ||||
|      * SSL protocol version, and timeout. The returned LdapNetworkConnection is | ||||
|      * configured for use but is not yet connected nor bound to the LDAP | ||||
|      * server. It will not be bound until a bind operation is explicitly | ||||
|      * requested, and will not be connected until it is used in an LDAP | ||||
|      * operation (such as a bind). | ||||
|      * | ||||
|      * @param host | ||||
|      *     The hostname or IP address of the LDAP server. | ||||
|      * | ||||
|      * @param port | ||||
|      *     The TCP port that the LDAP server is listening on. | ||||
|      * | ||||
|      * @param encryptionMethod | ||||
|      *     The encryption method that should be used to communicate with the | ||||
|      *     LDAP server. | ||||
|      *  | ||||
|      * @param sslProtocol | ||||
|      *     The SSL protocol version to use to make a secure LDAP configuration, | ||||
|      *     if SSL or STARTTLS is used. | ||||
|      * | ||||
|      * @param timeout | ||||
|      *     The maximum number of milliseconds to wait for a response from the | ||||
|      *     LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new instance of LdapNetworkConnection which uses the given | ||||
|      *     encryption method to communicate with the LDAP server at the given | ||||
|      *     hostname and port. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the requested encryption method is actually not implemented (a | ||||
|      *     bug). | ||||
|      */ | ||||
|     private LdapNetworkConnection createLDAPConnection(String host, int port, | ||||
|             EncryptionMethod encryptionMethod, LDAPSSLProtocol sslProtocol, | ||||
|             int timeout) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         LdapConnectionConfig config = new LdapConnectionConfig(); | ||||
|         config.setLdapHost(host); | ||||
|         config.setLdapPort(port); | ||||
|         config.setTimeout(timeout); | ||||
|  | ||||
|         // Map encryption method to proper connection and socket factory | ||||
|         switch (encryptionMethod) { | ||||
|  | ||||
|             // Unencrypted LDAP connection | ||||
|             case NONE: | ||||
|                 logger.debug("Connection to LDAP server without encryption."); | ||||
|                 break; | ||||
|  | ||||
|             // LDAP over SSL (LDAPS) | ||||
|             case SSL: | ||||
|                 logger.debug("Connecting to LDAP server using SSL/TLS."); | ||||
|                 config.setUseSsl(true); | ||||
|                 config.setSslProtocol(sslProtocol.toString()); | ||||
|                 break; | ||||
|  | ||||
|             // LDAP + STARTTLS | ||||
|             case STARTTLS: | ||||
|                 logger.debug("Connecting to LDAP server using STARTTLS."); | ||||
|                 config.setUseTls(true); | ||||
|                 config.setSslProtocol(sslProtocol.toString()); | ||||
|                 break; | ||||
|  | ||||
|             // The encryption method, though known, is not actually | ||||
|             // implemented. If encountered, this would be a bug. | ||||
|             default: | ||||
|                 throw new GuacamoleUnsupportedException("Unimplemented encryption method: " + encryptionMethod); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         return new LdapNetworkConnection(config); | ||||
|  | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Creates a new instance of LdapNetworkConnection, configured as required | ||||
|      * to use the given encryption method to communicate with the LDAP server | ||||
|      * at the given hostname and port with the encryption method and timeout | ||||
|      * specified, as well. The returned LdapNetworkConnection is configured | ||||
|      * for use but is not yet connected nor bound to the LDAP server. It will | ||||
|      * not be bound until a bind operation is explicitly requested, and will | ||||
|      * not be connected until it is used in an LDAP operation (such as a bind). | ||||
|      * | ||||
|      * @param host | ||||
|      *     The hostname or IP address of the LDAP server. | ||||
|      * | ||||
|      * @param port | ||||
|      *     The TCP port that the LDAP server is listening on. | ||||
|      * | ||||
|      * @param encryptionMethod | ||||
|      *     The encryption method that should be used to communicate with the | ||||
|      *     LDAP server. | ||||
|      * | ||||
|      * @param timeout | ||||
|      *     The maximum number of milliseconds to wait for a response from the | ||||
|      *     LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new instance of LdapNetworkConnection which uses the given | ||||
|      *     encryption method to communicate with the LDAP server at the given | ||||
|      *     hostname and port. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the requested encryption method is actually not implemented (a | ||||
|      *     bug). | ||||
|      */ | ||||
|     private LdapNetworkConnection createLDAPConnection(String host, int port, | ||||
|             EncryptionMethod encryptionMethod, int timeout) | ||||
|             throws GuacamoleException { | ||||
|         return createLDAPConnection(host, port, encryptionMethod, | ||||
|                 LDAPSSLProtocol.TLSv1_3, timeout); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of LdapNetworkConnection, configured as required | ||||
|      * to use whichever encryption method, hostname, and port are requested | ||||
|      * within guacamole.properties. The returned LdapNetworkConnection is | ||||
|      * configured for use but is not yet connected nor bound to the LDAP | ||||
|      * server. It will not be bound until a bind operation is explicitly | ||||
|      * requested, and will not be connected until it is used in an LDAP | ||||
|      * operation (such as a bind). | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new LdapNetworkConnection instance which has already been | ||||
|      *     configured to use the encryption method, hostname, and port | ||||
|      *     requested within guacamole.properties. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while parsing guacamole.properties, or if the | ||||
|      *     requested encryption method is actually not implemented (a bug). | ||||
|      */ | ||||
|     private LdapNetworkConnection createLDAPConnection(LDAPConfiguration config) | ||||
|             throws GuacamoleException { | ||||
|         return createLDAPConnection( | ||||
|                 config.getServerHostname(), | ||||
|                 config.getServerPort(), | ||||
|                 config.getEncryptionMethod(), | ||||
|                 config.getSslProtocol(), | ||||
|                 config.getNetworkTimeout()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of LdapNetworkConnection, configured as required | ||||
|      * to use whichever encryption method, hostname, and port are specified | ||||
|      * within the given LDAP URL. The returned LdapNetworkConnection is | ||||
|      * configured for use but is not yet connected nor bound to the LDAP | ||||
|      * server. It will not be bound until a bind operation is explicitly | ||||
|      * requested, and will not be connected until it is used in an LDAP | ||||
|      * operation (such as a bind). | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param url | ||||
|      *     The LDAP URL containing the details which should be used to connect | ||||
|      *     to the LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new LdapNetworkConnection instance which has already been | ||||
|      *     configured to use the encryption method, hostname, and port | ||||
|      *     specified within the given LDAP URL. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the given URL is not a valid LDAP URL, or if the encryption | ||||
|      *     method indicated by the URL is known but not actually implemented (a | ||||
|      *     bug). | ||||
|      */ | ||||
|     private LdapNetworkConnection createLDAPConnection(LDAPConfiguration config, | ||||
|             String url) throws GuacamoleException { | ||||
|  | ||||
|         // Parse provided LDAP URL | ||||
|         LdapUrl ldapUrl; | ||||
|         try { | ||||
|             ldapUrl = new LdapUrl(url); | ||||
|         } | ||||
|         catch (LdapException e) { | ||||
|             logger.debug("Cannot connect to LDAP URL \"{}\": URL is invalid.", url, e); | ||||
|             throw new GuacamoleServerException("Invalid LDAP URL.", e); | ||||
|         } | ||||
|  | ||||
|         // Retrieve hostname from URL, bailing out if no hostname is present | ||||
|         String host = ldapUrl.getHost(); | ||||
|         if (host == null || host.isEmpty()) { | ||||
|             logger.debug("Cannot connect to LDAP URL \"{}\": no hostname is present.", url); | ||||
|             throw new GuacamoleServerException("LDAP URL contains no hostname."); | ||||
|         } | ||||
|  | ||||
|         // Parse encryption method from URL scheme | ||||
|         EncryptionMethod encryptionMethod = EncryptionMethod.NONE; | ||||
|         if (LdapUrl.LDAPS_SCHEME.equals(ldapUrl.getScheme())) | ||||
|             encryptionMethod = EncryptionMethod.SSL; | ||||
|  | ||||
|         // Use STARTTLS for otherwise unencrypted ldap:// URLs if the main | ||||
|         // LDAP connection requires STARTTLS | ||||
|         else if (config.getEncryptionMethod() == EncryptionMethod.STARTTLS) { | ||||
|             logger.debug("Using STARTTLS for LDAP URL \"{}\" as the main LDAP " | ||||
|                     + "connection described in guacamole.properties is " | ||||
|                     + "configured to use STARTTLS.", url); | ||||
|             encryptionMethod = EncryptionMethod.STARTTLS; | ||||
|         } | ||||
|  | ||||
|         // If no post is specified within the URL, use the default port | ||||
|         // dictated by the encryption method | ||||
|         int port = ldapUrl.getPort(); | ||||
|         if (port < 1) | ||||
|             port = encryptionMethod.DEFAULT_PORT; | ||||
|  | ||||
|         return createLDAPConnection(host, port, encryptionMethod, | ||||
|                 config.getSslProtocol(), config.getNetworkTimeout()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds to the LDAP server indicated by the given LdapNetworkConnection | ||||
|      * using the given credentials. If the LdapNetworkConnection is not yet | ||||
|      * connected, an LDAP connection is first established. The provided | ||||
|      * credentials will be stored within the LdapConnectionConfig of the given | ||||
|      * LdapNetworkConnection. If the bind operation fails, the given | ||||
|      * LdapNetworkConnection is automatically closed. | ||||
|      * | ||||
|      * @param ldapConnection | ||||
|      *     The LdapNetworkConnection describing the connection to the LDAP | ||||
|      *     server. This LdapNetworkConnection is modified as a result of this | ||||
|      *     call and will be automatically closed if this call fails. | ||||
|      * | ||||
|      * @param userDN | ||||
|      *     The DN of the user to bind as, or null to bind anonymously. | ||||
|      * | ||||
|      * @param password | ||||
|      *     The password to use when binding as the specified user, or null to | ||||
|      *     attempt to bind without a password. | ||||
|      * | ||||
|      * @return | ||||
|      *     A bound LDAP connection, or null if the connection could not be | ||||
|      *     bound. | ||||
|      */ | ||||
|     private LdapNetworkConnection bindAs(LdapNetworkConnection ldapConnection, | ||||
|             String bindUser, String password) { | ||||
|  | ||||
|         // Add credentials to existing config | ||||
|         LdapConnectionConfig config = ldapConnection.getConfig(); | ||||
|         config.setName(bindUser); | ||||
|         config.setCredentials(password); | ||||
|  | ||||
|         try { | ||||
|             // Connect and bind using provided credentials | ||||
|             ldapConnection.bind(); | ||||
|         } | ||||
|  | ||||
|         // Disconnect if an authentication error occurs, but log that failure | ||||
|         // only at the debug level (such failures are expected) | ||||
|         catch (LdapAuthenticationException e) { | ||||
|             ldapConnection.close(); | ||||
|             logger.debug("Bind attempt with LDAP server as user \"{}\" failed.", | ||||
|                     bindUser, e); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Disconnect for all other bind failures, as well, logging those at | ||||
|         // the error level | ||||
|         catch (LdapException e) { | ||||
|             ldapConnection.close(); | ||||
|             logger.error("Binding with the LDAP server at \"{}\" as user " | ||||
|                     + "\"{}\" failed: {}", config.getLdapHost(), bindUser, | ||||
|                     e.getMessage()); | ||||
|             logger.debug("Unable to bind to LDAP server.", e); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return ldapConnection; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds to the LDAP server indicated by a given LdapNetworkConnection | ||||
|      * using the credentials that were used to bind another | ||||
|      * LdapNetworkConnection. If the LdapNetworkConnection about to be bound is | ||||
|      * not yet connected, an LDAP connection is first established. The | ||||
|      * credentials from the other LdapNetworkConnection will be stored within | ||||
|      * the LdapConnectionConfig of the given LdapNetworkConnection. If the bind | ||||
|      * operation fails, the given LdapNetworkConnection is automatically | ||||
|      * closed. | ||||
|      * | ||||
|      * @param ldapConnection | ||||
|      *     The LdapNetworkConnection describing the connection to the LDAP | ||||
|      *     server. This LdapNetworkConnection is modified as a result of this | ||||
|      *     call and will be automatically closed if this call fails. | ||||
|      * | ||||
|      * @param useCredentialsFrom | ||||
|      *     A bound LdapNetworkConnection whose bind credentials should be | ||||
|      *     copied for use within this bind operation. | ||||
|      * | ||||
|      * @return | ||||
|      *     A bound LDAP connection, or null if the connection could not be | ||||
|      *     bound. | ||||
|      */ | ||||
|     private LdapNetworkConnection bindAs(LdapNetworkConnection ldapConnection, | ||||
|             LdapNetworkConnection useCredentialsFrom) { | ||||
|  | ||||
|         // Copy bind username and password from original config | ||||
|         LdapConnectionConfig ldapConfig = useCredentialsFrom.getConfig(); | ||||
|         String username = ldapConfig.getName(); | ||||
|         String password = ldapConfig.getCredentials(); | ||||
|  | ||||
|         // Parse bind username as an LDAP DN | ||||
|         Dn userDN; | ||||
|         try { | ||||
|             userDN = new Dn(username); | ||||
|         } | ||||
|         catch (LdapInvalidDnException e) { | ||||
|             logger.error("Credentials of existing connection cannot be used. " | ||||
|                     + "The username used (\"{}\") is not a valid DN.", username); | ||||
|             logger.debug("Cannot bind using invalid DN.", e); | ||||
|             ldapConnection.close(); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Bind using username/password from existing connection | ||||
|         return bindAs(ldapConnection, userDN.getName(), password); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds to the LDAP server using the provided user DN and password. The | ||||
|      * hostname, port, and encryption method of the LDAP server are determined | ||||
|      * from guacamole.properties. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param bindUser | ||||
|      *     The DN or UPN of the user to bind as, or null to bind anonymously. | ||||
|      * | ||||
|      * @param password | ||||
|      *     The password to use when binding as the specified user, or null to | ||||
|      *     attempt to bind without a password. | ||||
|      * | ||||
|      * @return | ||||
|      *     A bound LDAP connection, or null if the connection could not be | ||||
|      *     bound. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while parsing guacamole.properties, or if the | ||||
|      *     configured encryption method is actually not implemented (a bug). | ||||
|      */ | ||||
|     public LdapNetworkConnection bindAs(LDAPConfiguration config, | ||||
|             String bindUser, String password) throws GuacamoleException { | ||||
|         return bindAs(createLDAPConnection(config), bindUser, password); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Binds to the LDAP server indicated by the given LDAP URL using the | ||||
|      * credentials that were used to bind an existing LdapNetworkConnection. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param url | ||||
|      *     The LDAP URL containing the details which should be used to connect | ||||
|      *     to the LDAP server. | ||||
|      * | ||||
|      * @param useCredentialsFrom | ||||
|      *     A bound LdapNetworkConnection whose bind credentials should be | ||||
|      *     copied for use within this bind operation. | ||||
|      * | ||||
|      * @return | ||||
|      *     A bound LDAP connection, or null if the connection could not be | ||||
|      *     bound. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the given URL is not a valid LDAP URL, or if the encryption | ||||
|      *     method indicated by the URL is known but not actually implemented (a | ||||
|      *     bug). | ||||
|      */ | ||||
|     public LdapNetworkConnection bindAs(LDAPConfiguration config, String url, | ||||
|             LdapNetworkConnection useCredentialsFrom) | ||||
|             throws GuacamoleException { | ||||
|         return bindAs(createLDAPConnection(config, url), useCredentialsFrom); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generate a SearchRequest object using the given Base DN and filter | ||||
|      * and retrieving other properties from the LDAP configuration service. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param baseDn | ||||
|      *     The LDAP Base DN at which to search the search. | ||||
|      *  | ||||
|      * @param filter | ||||
|      *     A string representation of a LDAP filter to use for the search. | ||||
|      *  | ||||
|      * @return | ||||
|      *     The properly-configured SearchRequest object. | ||||
|      *  | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs retrieving any of the configuration values. | ||||
|      */ | ||||
|     public SearchRequest getSearchRequest(LDAPConfiguration config, Dn baseDn, | ||||
|             ExprNode filter) throws GuacamoleException { | ||||
|          | ||||
|         SearchRequest searchRequest = new SearchRequestImpl(); | ||||
|         searchRequest.setBase(baseDn); | ||||
|         searchRequest.setDerefAliases(config.getDereferenceAliases()); | ||||
|         searchRequest.setScope(SearchScope.SUBTREE); | ||||
|         searchRequest.setFilter(filter); | ||||
|         searchRequest.setSizeLimit(config.getMaxResults()); | ||||
|         searchRequest.setTimeLimit(config.getOperationTimeout()); | ||||
|         searchRequest.setTypesOnly(false); | ||||
|          | ||||
|         if (config.getFollowReferrals()) | ||||
|             searchRequest.followReferrals(); | ||||
|          | ||||
|         return searchRequest; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,397 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.Function; | ||||
| import org.apache.directory.api.ldap.model.cursor.CursorException; | ||||
| import org.apache.directory.api.ldap.model.cursor.SearchCursor; | ||||
| import org.apache.directory.api.ldap.model.entry.Attribute; | ||||
| import org.apache.directory.api.ldap.model.entry.Entry; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapException; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; | ||||
| import org.apache.directory.api.ldap.model.filter.AndNode; | ||||
| import org.apache.directory.api.ldap.model.filter.EqualityNode; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.filter.OrNode; | ||||
| import org.apache.directory.api.ldap.model.filter.PresenceNode; | ||||
| import org.apache.directory.api.ldap.model.message.SearchRequest; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPGuacamoleProperties; | ||||
| import org.apache.guacamole.net.auth.Identifiable; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for executing queries against an LDAP directory intended to retrieve | ||||
|  * Guacamole-related objects. Referrals are automatically handled. Convenience | ||||
|  * functions are provided for generating the LDAP queries typically required | ||||
|  * for retrieving Guacamole objects, as well as for converting the results of a | ||||
|  * query into a {@link Map} of Guacamole objects. | ||||
|  */ | ||||
| public class ObjectQueryService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ObjectQueryService.class); | ||||
|      | ||||
|     /** | ||||
|      * Service for connecting to LDAP directory. | ||||
|      */ | ||||
|     @Inject | ||||
|     private LDAPConnectionService ldapService; | ||||
|  | ||||
|     /** | ||||
|      * Returns the identifier of the object represented by the given LDAP | ||||
|      * entry. Multiple attributes may be declared as containing the identifier | ||||
|      * of the object when present on an LDAP entry. If multiple such attributes | ||||
|      * are present on the same LDAP entry, the value of the attribute with | ||||
|      * highest priority is used. If multiple copies of the same attribute are | ||||
|      * present on the same LDAPentry, the first value of that attribute is | ||||
|      * used. | ||||
|      * | ||||
|      * @param entry | ||||
|      *     The entry representing the Guacamole object whose unique identifier | ||||
|      *     should be determined. | ||||
|      * | ||||
|      * @param attributes | ||||
|      *     A collection of all attributes which may be used to specify the | ||||
|      *     unique identifier of the Guacamole object represented by an LDAP | ||||
|      *     entry, in order of decreasing priority. | ||||
|      * | ||||
|      * @return | ||||
|      *     The identifier of the object represented by the given LDAP entry, or | ||||
|      *     null if no attributes declared as containing the identifier of the | ||||
|      *     object are present on the entry. | ||||
|      *  | ||||
|      * @throws LdapInvalidAttributeValueException | ||||
|      *     If an error occurs retrieving the value of the identifier attribute. | ||||
|      */ | ||||
|     public String getIdentifier(Entry entry, Collection<String> attributes)  | ||||
|             throws LdapInvalidAttributeValueException { | ||||
|  | ||||
|         // Retrieve the first value of the highest priority identifier attribute | ||||
|         for (String identifierAttribute : attributes) { | ||||
|             Attribute identifier = entry.get(identifierAttribute); | ||||
|             if (identifier != null) | ||||
|                 return identifier.getString(); | ||||
|         } | ||||
|  | ||||
|         // No identifier attribute is present on the entry | ||||
|         return null; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generates a properly-escaped LDAP query which finds all objects which | ||||
|      * match the given LDAP filter and which have at least one of the given | ||||
|      * attributes set to the specified value. | ||||
|      * | ||||
|      * @param filter | ||||
|      *     The LDAP filter to apply to reduce the results of the query in | ||||
|      *     addition to testing the values of the given attributes. | ||||
|      * | ||||
|      * @param attributes | ||||
|      *     A collection of all attributes to test for equivalence to the given | ||||
|      *     value, in order of decreasing priority. | ||||
|      * | ||||
|      * @param attributeValue | ||||
|      *     The value that the resulting LDAP query should search for within the | ||||
|      *     attributes of objects within the LDAP directory. If null, the | ||||
|      *     resulting LDAP query will search for the presence of at least one of | ||||
|      *     the given attributes on each object, regardless of the value of | ||||
|      *     those attributes. | ||||
|      * | ||||
|      * @return | ||||
|      *     An LDAP query which will search for arbitrary LDAP objects having at | ||||
|      *     least one of the given attributes set to the specified value. | ||||
|      */ | ||||
|     public ExprNode generateQuery(ExprNode filter, | ||||
|             Collection<String> attributes, String attributeValue) { | ||||
|  | ||||
|         // Build LDAP query for objects having at least one attribute and with | ||||
|         // the given search filter | ||||
|         AndNode searchFilter = new AndNode(); | ||||
|         searchFilter.addNode(filter); | ||||
|  | ||||
|         // If no attributes provided, we're done. | ||||
|         if (attributes.size() < 1) | ||||
|             return searchFilter; | ||||
|  | ||||
|         // Include all attributes within OR clause | ||||
|         OrNode attributeFilter = new OrNode(); | ||||
|  | ||||
|         // If value is defined, check each attribute for that value. | ||||
|         if (attributeValue != null) { | ||||
|             attributes.forEach(attribute -> | ||||
|                 attributeFilter.addNode(new EqualityNode(attribute, | ||||
|                         attributeValue)) | ||||
|             ); | ||||
|         } | ||||
|          | ||||
|         // If no value is defined, just check for presence of attribute. | ||||
|         else { | ||||
|             attributes.forEach(attribute -> | ||||
|                 attributeFilter.addNode(new PresenceNode(attribute)) | ||||
|             );             | ||||
|         } | ||||
|  | ||||
|         searchFilter.addNode(attributeFilter); | ||||
|  | ||||
|         logger.trace("Sending LDAP filter: \"{}\"", searchFilter.toString()); | ||||
|          | ||||
|         return searchFilter; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Executes an arbitrary LDAP query using the given connection, returning a | ||||
|      * list of all results. Only objects beneath the given base DN are | ||||
|      * included in the search. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param ldapConnection | ||||
|      *     The current connection to the LDAP server, associated with the | ||||
|      *     current user. | ||||
|      * | ||||
|      * @param baseDN | ||||
|      *     The base DN to search using the given LDAP query. | ||||
|      * | ||||
|      * @param query | ||||
|      *     The LDAP query to execute. | ||||
|      *  | ||||
|      * @param searchHop | ||||
|      *     The current level of referral depth for this search, used for | ||||
|      *     limiting the maximum depth to which referrals can go. | ||||
|      *  | ||||
|      * @param attributes | ||||
|      *     A collection of the names of attributes that should be retrieved | ||||
|      *     from LDAP entries returned by the search, or null if all available | ||||
|      *     attributes should be returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     A list of all results accessible to the user currently bound under | ||||
|      *     the given LDAP connection. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs executing the query, or if configuration | ||||
|      *     information required to execute the query cannot be read from | ||||
|      *     guacamole.properties. | ||||
|      */ | ||||
|     public List<Entry> search(LDAPConfiguration config, | ||||
|             LdapNetworkConnection ldapConnection, Dn baseDN, ExprNode query, | ||||
|             int searchHop, Collection<String> attributes) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Refuse to follow referrals if limit has been reached | ||||
|         int maxHops = config.getMaxReferralHops(); | ||||
|         if (searchHop >= maxHops) { | ||||
|             logger.debug("Refusing to follow further referrals as the maximum " | ||||
|                     + "number of referral hops ({}) has been reached. LDAP " | ||||
|                     + "search results may be incomplete. If further referrals " | ||||
|                     + "should be followed, consider setting the \"{}\" " | ||||
|                     + "property to a larger value.", maxHops, LDAPGuacamoleProperties.LDAP_MAX_REFERRAL_HOPS.getName()); | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|  | ||||
|         logger.debug("Searching \"{}\" for objects matching \"{}\".", baseDN, query); | ||||
|  | ||||
|         // Search within subtree of given base DN | ||||
|         SearchRequest request = ldapService.getSearchRequest(config, baseDN, query); | ||||
|         if (attributes != null) | ||||
|             request.addAttributes(attributes.toArray(new String[0])); | ||||
|  | ||||
|         // Produce list of all entries in the search result, automatically | ||||
|         // following referrals if configured to do so | ||||
|         List<Entry> entries = new ArrayList<>(); | ||||
|         try (SearchCursor results = ldapConnection.search(request)) { | ||||
|  | ||||
|             while (results.next()) { | ||||
|  | ||||
|                 // Add entry directly if no referral is involved | ||||
|                 if (results.isEntry()) | ||||
|                     entries.add(results.getEntry()); | ||||
|  | ||||
|                 // If a referral must be followed to obtain further results, | ||||
|                 // retrieval of those results depends on whether such referral | ||||
|                 // following is enabled | ||||
|                 else if (results.isReferral()) { | ||||
|  | ||||
|                     // Follow received referrals only if configured to do so | ||||
|                     if (request.isFollowReferrals()) { | ||||
|                         for (String url : results.getReferral().getLdapUrls()) { | ||||
|  | ||||
|                             // Connect to referred LDAP server to retrieve further results, ensuring the network | ||||
|                             // connection is always closed when it will no longer be used | ||||
|                             try (LdapNetworkConnection referralConnection = ldapService.bindAs(config, url, ldapConnection)) { | ||||
|                                 if (referralConnection != null) { | ||||
|                                     logger.debug("Following referral to \"{}\"...", url); | ||||
|                                     entries.addAll(search(config, referralConnection, baseDN, query, searchHop + 1, attributes)); | ||||
|                                 } | ||||
|                                 else | ||||
|                                     logger.debug("Could not bind with LDAP " | ||||
|                                             + "server indicated by referral " | ||||
|                                             + "URL \"{}\".", url); | ||||
|                             } | ||||
|                             catch (GuacamoleException e) { | ||||
|                                 logger.warn("Referral to \"{}\" could not be followed: {}", url, e.getMessage()); | ||||
|                                 logger.debug("Failed to follow LDAP referral.", e); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     // Log if referrals may be applicable but they aren't being | ||||
|                     // followed | ||||
|                     else | ||||
|                         logger.debug("Referrals to one or more other LDAP " | ||||
|                                 + "servers were received but are being " | ||||
|                                 + "ignored because following of referrals is " | ||||
|                                 + "not enabled. If referrals must be " | ||||
|                                 + "followed, consider setting the \"{}\" " | ||||
|                                 + "property to \"true\".", LDAPGuacamoleProperties.LDAP_FOLLOW_REFERRALS.getName()); | ||||
|  | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|  | ||||
|             return entries; | ||||
|  | ||||
|         } | ||||
|         catch (CursorException | IOException | LdapException e) { | ||||
|             throw new GuacamoleServerException("Unable to query list of " | ||||
|                     + "objects from LDAP directory: " + e.getMessage(), e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Executes the query which would be returned by generateQuery() using the | ||||
|      * given connection, returning a list of all results. Only objects beneath | ||||
|      * the given base DN are included in the search. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param ldapConnection | ||||
|      *     The current connection to the LDAP server, associated with the | ||||
|      *     current user. | ||||
|      * | ||||
|      * @param baseDN | ||||
|      *     The base DN to search using the given LDAP query. | ||||
|      * | ||||
|      * @param filter | ||||
|      *     The LDAP filter to apply to reduce the results of the query in | ||||
|      *     addition to testing the values of the given attributes. | ||||
|      * | ||||
|      * @param filterAttributes | ||||
|      *     A collection of all attributes to test for equivalence to the given | ||||
|      *     value, in order of decreasing priority. | ||||
|      * | ||||
|      * @param filterValue | ||||
|      *     The value that should be searched search for within the attributes | ||||
|      *     of objects within the LDAP directory. If null, the search will test | ||||
|      *     only for the presence of at least one of the given attributes on | ||||
|      *     each object, regardless of the value of those attributes. | ||||
|      *  | ||||
|      * @param attributes | ||||
|      *     A collection of the names of attributes that should be retrieved | ||||
|      *     from LDAP entries returned by the search, or null if all available | ||||
|      *     attributes should be returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     A list of all results accessible to the user currently bound under | ||||
|      *     the given LDAP connection. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs executing the query, or if configuration | ||||
|      *     information required to execute the query cannot be read from | ||||
|      *     guacamole.properties. | ||||
|      */ | ||||
|     public List<Entry> search(LDAPConfiguration config, | ||||
|             LdapNetworkConnection ldapConnection, Dn baseDN, ExprNode filter, | ||||
|             Collection<String> filterAttributes, String filterValue, | ||||
|             Collection<String> attributes) throws GuacamoleException { | ||||
|         ExprNode query = generateQuery(filter, filterAttributes, filterValue); | ||||
|         return search(config, ldapConnection, baseDN, query, 0, attributes); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Converts a given list of LDAP entries to a {@link Map} of Guacamole | ||||
|      * objects stored by their identifiers. | ||||
|      * | ||||
|      * @param <ObjectType> | ||||
|      *     The type of object to store within the {@link Map}. | ||||
|      * | ||||
|      * @param entries | ||||
|      *     A list of LDAP entries to convert to Guacamole objects. | ||||
|      * | ||||
|      * @param mapper | ||||
|      *     A mapping function which converts a given LDAP entry to its | ||||
|      *     corresponding Guacamole object. If the LDAP entry cannot be | ||||
|      *     converted, null should be returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     A new {@link Map} containing Guacamole object versions of each of | ||||
|      *     the given LDAP entries, where each object is stored within the | ||||
|      *     {@link Map} under its corresponding identifier. | ||||
|      */ | ||||
|     public <ObjectType extends Identifiable> Map<String, ObjectType> | ||||
|         asMap(List<Entry> entries, Function<Entry, ObjectType> mapper) { | ||||
|  | ||||
|         // Convert each entry to the corresponding Guacamole API object | ||||
|         Map<String, ObjectType> objects = new HashMap<>(entries.size()); | ||||
|         for (Entry entry : entries) { | ||||
|  | ||||
|             ObjectType object = mapper.apply(entry); | ||||
|             if (object == null) { | ||||
|                 logger.debug("Ignoring object \"{}\".", entry.getDn().toString()); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Attempt to add object to map, warning if the object appears | ||||
|             // to be a duplicate | ||||
|             String identifier = object.getIdentifier(); | ||||
|             if (objects.putIfAbsent(identifier, object) != null) | ||||
|                 logger.warn("Multiple objects ambiguously map to the " | ||||
|                         + "same identifier (\"{}\"). Ignoring \"{}\" as " | ||||
|                         + "a duplicate.", identifier, entry.getDn().toString()); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         return objects; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import com.fasterxml.jackson.core.JsonParseException; | ||||
| import com.fasterxml.jackson.core.JsonParser; | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
| import com.fasterxml.jackson.core.JsonToken; | ||||
| import com.fasterxml.jackson.databind.DeserializationContext; | ||||
| import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; | ||||
| import com.fasterxml.jackson.databind.type.LogicalType; | ||||
| import java.io.IOException; | ||||
| import java.io.Serializable; | ||||
| import java.util.regex.Pattern; | ||||
| import java.util.regex.PatternSyntaxException; | ||||
|  | ||||
| /** | ||||
|  * Custom JSON (or YAML) deserializer for Jackson that deserializes string | ||||
|  * values as Patterns with the case insensitive flag set by default. Jackson | ||||
|  * will actually handle deserialization of Patterns automatically, but does not | ||||
|  * provide for setting the default flags. | ||||
|  */ | ||||
| public class CaseInsensitivePatternDeserializer extends StdScalarDeserializer<Pattern> { | ||||
|  | ||||
|     /** | ||||
|      * Unique version identifier of this {@link Serializable} class. | ||||
|      */ | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new CaseInsensitivePatternDeserializer which deserializes | ||||
|      * string values to Pattern objects with the case insensitive flag set. | ||||
|      */ | ||||
|     public CaseInsensitivePatternDeserializer() { | ||||
|         super(Pattern.class); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public LogicalType logicalType() { | ||||
|         return LogicalType.Textual; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isCachable() { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Pattern deserialize(JsonParser parser, DeserializationContext context) | ||||
|             throws IOException, JsonProcessingException { | ||||
|  | ||||
|         if (!parser.hasToken(JsonToken.VALUE_STRING)) | ||||
|             throw new JsonParseException(parser, "Regular expressions may only be represented as strings."); | ||||
|  | ||||
|         try { | ||||
|             return Pattern.compile(parser.getText(), Pattern.CASE_INSENSITIVE); | ||||
|         } | ||||
|         catch (PatternSyntaxException e) { | ||||
|             throw new JsonParseException(parser, "Invalid regular expression.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,152 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import com.fasterxml.jackson.core.type.TypeReference; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.fasterxml.jackson.databind.module.SimpleModule; | ||||
| import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Singleton; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.concurrent.atomic.AtomicLong; | ||||
| import java.util.regex.Pattern; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for retrieving configuration information regarding LDAP servers. | ||||
|  */ | ||||
| @Singleton | ||||
| public class ConfigurationService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ConfigurationService.class); | ||||
|      | ||||
|     /** | ||||
|      * ObjectMapper for deserializing YAML. | ||||
|      */ | ||||
|     private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()) | ||||
|             .registerModule(new SimpleModule().addDeserializer(Pattern.class, new CaseInsensitivePatternDeserializer())); | ||||
|  | ||||
|     /** | ||||
|      * The name of the file within GUACAMOLE_HOME that defines each available | ||||
|      * LDAP server (if not using guacamole.properties). | ||||
|      */ | ||||
|     private static final String LDAP_SERVERS_YML = "ldap-servers.yml"; | ||||
|  | ||||
|     /** | ||||
|      * The timestamp that the {@link #LDAP_SERVERS_YML} was last modified when | ||||
|      * it was read, as would be returned by {@link File#lastModified()}. | ||||
|      */ | ||||
|     private final AtomicLong lastModified = new AtomicLong(0); | ||||
|  | ||||
|     /** | ||||
|      * The cached copy of the configuration read from {@link #LDAP_SERVERS_YML}. | ||||
|      * If the current set of LDAP servers has not yet been read from the YAML | ||||
|      * configuration file, or if guacamole.properties is being used instead, | ||||
|      * this will be null. | ||||
|      */ | ||||
|     private Collection<JacksonLDAPConfiguration> cachedConfigurations = null; | ||||
|      | ||||
|     /** | ||||
|      * The Guacamole server environment. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Environment environment; | ||||
|  | ||||
|     /** | ||||
|      * Returns the configuration information for all configured LDAP servers. | ||||
|      * If multiple servers are returned, each should be tried in order until a | ||||
|      * successful LDAP connection is established. | ||||
|      * | ||||
|      * @return | ||||
|      *     The configurations of all LDAP servers. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the configuration information of the LDAP servers cannot be | ||||
|      *     retrieved due to an error. | ||||
|      */ | ||||
|     public Collection<? extends LDAPConfiguration> getLDAPConfigurations() throws GuacamoleException { | ||||
|  | ||||
|         // Read/refresh configuration from YAML, if available | ||||
|         File ldapServers = new File(environment.getGuacamoleHome(), LDAP_SERVERS_YML); | ||||
|         if (ldapServers.exists()) { | ||||
|  | ||||
|             long oldLastModified = lastModified.get(); | ||||
|             long currentLastModified = ldapServers.lastModified(); | ||||
|  | ||||
|             // Update cached copy of YAML if things have changed, ensuring only | ||||
|             // one concurrent request updates the cache at any given time | ||||
|             if (currentLastModified > oldLastModified && lastModified.compareAndSet(oldLastModified, currentLastModified)) { | ||||
|                 try { | ||||
|  | ||||
|                     logger.debug("Reading updated LDAP configuration from \"{}\"...", ldapServers); | ||||
|                     Collection<JacksonLDAPConfiguration> configs = mapper.readValue(ldapServers, new TypeReference<Collection<JacksonLDAPConfiguration>>() {}); | ||||
|  | ||||
|                     if (configs != null) { | ||||
|                         logger.debug("Reading LDAP configuration defaults from guacamole.properties..."); | ||||
|                         LDAPConfiguration defaultConfig = new EnvironmentLDAPConfiguration(environment); | ||||
|                         configs.forEach((config) -> config.setDefaults(defaultConfig)); | ||||
|                     } | ||||
|                     else | ||||
|                         logger.debug("Using only guacamole.properties for " | ||||
|                                 + "LDAP server definitions as \"{}\" is " | ||||
|                                 + "empty.", ldapServers); | ||||
|  | ||||
|                     cachedConfigurations = configs; | ||||
|  | ||||
|                 } | ||||
|                 catch (IOException e) { | ||||
|                     logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage()); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|                 logger.debug("Using cached LDAP configuration from \"{}\".", ldapServers); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Clear cached YAML if it no longer exists | ||||
|         else if (cachedConfigurations != null) { | ||||
|             long oldLastModified = lastModified.get(); | ||||
|             if (lastModified.compareAndSet(oldLastModified, 0)) { | ||||
|                 logger.debug("Clearing cached LDAP configuration from \"{}\" (file no longer exists).", ldapServers); | ||||
|                 cachedConfigurations = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Use guacamole.properties if not using YAML | ||||
|         if (cachedConfigurations == null) { | ||||
|             logger.debug("Reading LDAP configuration from guacamole.properties..."); | ||||
|             return Collections.singletonList(new EnvironmentLDAPConfiguration(environment)); | ||||
|         } | ||||
|  | ||||
|         return cachedConfigurations; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,154 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.filter.PresenceNode; | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
|  | ||||
| /** | ||||
|  * LDAPConfiguration implementation that returns the default values for all | ||||
|  * configuration parameters. For any configuration parameters that are | ||||
|  * required (such as {@link #getUserBaseDN()}), an exception is thrown. | ||||
|  */ | ||||
| public class DefaultLDAPConfiguration implements LDAPConfiguration { | ||||
|      | ||||
|     @Override | ||||
|     public String appliesTo(String username) { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getServerHostname() { | ||||
|         return "localhost"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getServerPort() { | ||||
|         return getEncryptionMethod().DEFAULT_PORT; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> getUsernameAttributes() { | ||||
|         return Collections.singletonList("uid"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getUserBaseDN() throws GuacamoleException { | ||||
|         throw new GuacamoleServerException("All LDAP servers must have a defined user base DN."); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getConfigurationBaseDN() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> getGroupNameAttributes() { | ||||
|         return Collections.singletonList("cn"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getGroupBaseDN() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindDN() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindPassword() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public EncryptionMethod getEncryptionMethod() { | ||||
|         return EncryptionMethod.NONE; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public LDAPSSLProtocol getSslProtocol() { | ||||
|         return LDAPSSLProtocol.TLSv1_3; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxResults() { | ||||
|         return 1000; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AliasDerefMode getDereferenceAliases() { | ||||
|         return AliasDerefMode.NEVER_DEREF_ALIASES; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean getFollowReferrals() { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxReferralHops() { | ||||
|         return 5; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getUserSearchFilter() { | ||||
|         return new PresenceNode("objectClass"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getGroupSearchFilter() { | ||||
|         return new PresenceNode("objectClass"); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getOperationTimeout() { | ||||
|         return 30; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getNetworkTimeout() { | ||||
|         return 30000; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<String> getAttributes() { | ||||
|         return Collections.<String>emptyList(); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public String getMemberAttribute() { | ||||
|         return "member"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MemberAttributeType getMemberAttributeType() | ||||
|             throws GuacamoleException { | ||||
|         return MemberAttributeType.DN; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,69 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; | ||||
|  | ||||
| /** | ||||
|  * All possible encryption methods which may be used when connecting to an LDAP | ||||
|  * server. | ||||
|  */ | ||||
| public enum EncryptionMethod { | ||||
|  | ||||
|     /** | ||||
|      * No encryption will be used. All data will be sent to the LDAP server in | ||||
|      * plaintext. Unencrypted LDAP connections use port 389 by default. | ||||
|      */ | ||||
|     @PropertyValue("none") | ||||
|     NONE(389), | ||||
|  | ||||
|     /** | ||||
|      * The connection to the LDAP server will be encrypted with SSL. LDAP over | ||||
|      * SSL (LDAPS) will use port 636 by default. | ||||
|      */ | ||||
|     @PropertyValue("ssl") | ||||
|     SSL(636), | ||||
|  | ||||
|     /** | ||||
|      * The connection to the LDAP server will be encrypted using STARTTLS. TLS | ||||
|      * connections are negotiated over the standard LDAP port of 389 - the same | ||||
|      * port used for unencrypted traffic. | ||||
|      */ | ||||
|     @PropertyValue("starttls") | ||||
|     STARTTLS(389); | ||||
|  | ||||
|     /** | ||||
|      * The default port of this specific encryption method. As with most | ||||
|      * protocols, the default port for LDAP varies by whether SSL is used. | ||||
|      */ | ||||
|     public final int DEFAULT_PORT; | ||||
|  | ||||
|     /** | ||||
|      * Initializes this encryption method such that it is associated with the | ||||
|      * given default port. | ||||
|      * | ||||
|      * @param defaultPort | ||||
|      *     The default port to associate with this encryption method. | ||||
|      */ | ||||
|     private EncryptionMethod(int defaultPort) { | ||||
|         this.DEFAULT_PORT = defaultPort; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,237 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
|  | ||||
| /** | ||||
|  * LDAPConfiguration implementation that reads its configuration details from | ||||
|  * guacamole.properties. | ||||
|  */ | ||||
| public class EnvironmentLDAPConfiguration implements LDAPConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * The Guacamole server environment. | ||||
|      */ | ||||
|     private final Environment environment; | ||||
|  | ||||
|     /** | ||||
|      * The default configuration options for all parameters. | ||||
|      */ | ||||
|     private static final LDAPConfiguration DEFAULT = new DefaultLDAPConfiguration(); | ||||
|  | ||||
|     /** | ||||
|      * Creates a new EnvironmentLDAPConfiguration that reads its configuration | ||||
|      * details from guacamole.properties, as exposed by the given Environment. | ||||
|      * | ||||
|      * @param environment | ||||
|      *     The Guacamole server environment. | ||||
|      */ | ||||
|     public EnvironmentLDAPConfiguration(Environment environment) { | ||||
|         this.environment = environment; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String appliesTo(String username) throws GuacamoleException { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getServerHostname() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_HOSTNAME, | ||||
|             DEFAULT.getServerHostname() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getServerPort() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_PORT, | ||||
|             getEncryptionMethod().DEFAULT_PORT | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getUsernameAttributes() throws GuacamoleException { | ||||
|         return environment.getPropertyCollection( | ||||
|             LDAPGuacamoleProperties.LDAP_USERNAME_ATTRIBUTE, | ||||
|             DEFAULT.getUsernameAttributes() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getUserBaseDN() throws GuacamoleException { | ||||
|         return environment.getRequiredProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_USER_BASE_DN | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getConfigurationBaseDN() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN, | ||||
|             DEFAULT.getConfigurationBaseDN() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getGroupNameAttributes() throws GuacamoleException { | ||||
|         return environment.getPropertyCollection( | ||||
|             LDAPGuacamoleProperties.LDAP_GROUP_NAME_ATTRIBUTE, | ||||
|             DEFAULT.getGroupNameAttributes() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getGroupBaseDN() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN, | ||||
|             DEFAULT.getGroupBaseDN() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindDN() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_SEARCH_BIND_DN, | ||||
|             DEFAULT.getSearchBindDN() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindPassword() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_SEARCH_BIND_PASSWORD, | ||||
|             DEFAULT.getSearchBindPassword() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public EncryptionMethod getEncryptionMethod() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD, | ||||
|             DEFAULT.getEncryptionMethod() | ||||
|         ); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public LDAPSSLProtocol getSslProtocol() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_SSL_PROTOCOL, | ||||
|             DEFAULT.getSslProtocol() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxResults() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_MAX_SEARCH_RESULTS, | ||||
|             DEFAULT.getMaxResults() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AliasDerefMode getDereferenceAliases() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES, | ||||
|             DEFAULT.getDereferenceAliases() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean getFollowReferrals() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_FOLLOW_REFERRALS, | ||||
|             DEFAULT.getFollowReferrals() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxReferralHops() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_MAX_REFERRAL_HOPS, | ||||
|             DEFAULT.getMaxReferralHops() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getUserSearchFilter() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER, | ||||
|             DEFAULT.getUserSearchFilter() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getGroupSearchFilter() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER, | ||||
|             DEFAULT.getGroupSearchFilter() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getOperationTimeout() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_OPERATION_TIMEOUT, | ||||
|             DEFAULT.getOperationTimeout() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getNetworkTimeout() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_NETWORK_TIMEOUT, | ||||
|             DEFAULT.getNetworkTimeout() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getAttributes() throws GuacamoleException { | ||||
|         return environment.getPropertyCollection( | ||||
|             LDAPGuacamoleProperties.LDAP_USER_ATTRIBUTES, | ||||
|             DEFAULT.getAttributes() | ||||
|         ); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public String getMemberAttribute() throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE, | ||||
|             DEFAULT.getMemberAttribute() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MemberAttributeType getMemberAttributeType() | ||||
|             throws GuacamoleException { | ||||
|         return environment.getProperty( | ||||
|             LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE, | ||||
|             DEFAULT.getMemberAttributeType() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,443 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||||
| import static com.fasterxml.jackson.annotation.JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY; | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.properties.GuacamoleProperty; | ||||
|  | ||||
| /** | ||||
|  * LDAPConfiguration implementation that is annotated for deserialization by | ||||
|  * Jackson. | ||||
|  */ | ||||
| public class JacksonLDAPConfiguration implements LDAPConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * The regular expressions that match all users that should be routed to | ||||
|      * the LDAP server represented by this configuration. | ||||
|      */ | ||||
|     @JsonProperty("match-usernames") | ||||
|     @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY) | ||||
|     private List<Pattern> matchUsernames; | ||||
|      | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_HOSTNAME}. If | ||||
|      * not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("hostname") | ||||
|     private String hostname; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_PORT}. If not | ||||
|      * set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("port") | ||||
|     private Integer port; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USERNAME_ATTRIBUTES}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("username-attribute") | ||||
|     @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY) | ||||
|     private List<String> usernameAttributes; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_BASE_DN}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("user-base-dn") | ||||
|     private String userBaseDn; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_CONFIG_BASE_DN}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("config-base-dn") | ||||
|     private String configBaseDn; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_BASE_DN}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("group-base-dn") | ||||
|     private String groupBaseDn; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_NAME_ATTRIBUTES}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("group-name-attribute") | ||||
|     @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY) | ||||
|     private List<String> groupNameAttributes; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_DN}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("search-bind-dn") | ||||
|     private String searchBindDn; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_PASSWORD}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("search-bind-password") | ||||
|     private String searchBindPassword; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_ENCRYPTION_METHOD}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("encryption-method") | ||||
|     private String encryptionMethod; | ||||
|      | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SSL_PROTOCOL}. If | ||||
|      * not set within the YAML, this will be null, and will default to the value | ||||
|      * specified by the LDAP API library. | ||||
|      */ | ||||
|     @JsonProperty("ssl-protocol") | ||||
|     private String sslProtocol; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_SEARCH_RESULTS}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("max-search-results") | ||||
|     private Integer maxSearchResults; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_DEREFERENCE_ALIASES}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("dereference-aliases") | ||||
|     private String dereferenceAliases; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_FOLLOW_REFERRALS}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("follow-referrals") | ||||
|     private Boolean followReferrals; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_REFERRAL_HOPS}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("max-referral-hops") | ||||
|     private Integer maxReferralHops; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_SEARCH_FILTER}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("user-search-filter") | ||||
|     private String userSearchFilter; | ||||
|      | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_SEARCH_FILTER}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("group-search-filter") | ||||
|     private String groupSearchFilter; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_OPERATION_TIMEOUT}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("operation-timeout") | ||||
|     private Integer operationTimeout; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_NETWORK_TIMEOUT}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("network-timeout") | ||||
|     private Integer networkTimeout; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_ATTRIBUTES}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("user-attributes") | ||||
|     @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY) | ||||
|     private List<String> userAttributes; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("member-attribute") | ||||
|     private String memberAttribute; | ||||
|  | ||||
|     /** | ||||
|      * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE_TYPE}. | ||||
|      * If not set within the YAML, this will be null. | ||||
|      */ | ||||
|     @JsonProperty("member-attribute-type") | ||||
|     private String memberAttributeType; | ||||
|  | ||||
|     /** | ||||
|      * The default configuration options for all parameters. | ||||
|      */ | ||||
|     private LDAPConfiguration defaultConfig = new DefaultLDAPConfiguration(); | ||||
|  | ||||
|     /** | ||||
|      * Supplier of default values for LDAP configurations. Unlike | ||||
|      * {@link java.util.function.Supplier}, the {@link #get()} function of | ||||
|      * DefaultSupplier may throw a {@link GuacamoleException}. | ||||
|      * | ||||
|      * @param <T> | ||||
|      *    The type of value returned by this DefaultSupplier. | ||||
|      */ | ||||
|     @FunctionalInterface | ||||
|     private interface DefaultSupplier<T> { | ||||
|  | ||||
|         /** | ||||
|          * Returns the value supplied by this DefaultSupplier. The value | ||||
|          * returned is not cached and may be non-deterministic. | ||||
|          * | ||||
|          * @return | ||||
|          *     The value supplied by this DefaultSupplier. | ||||
|          * | ||||
|          * @throws GuacamoleException | ||||
|          *     If an error occurs while producing/retrieving the value. | ||||
|          */ | ||||
|         T get() throws GuacamoleException; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the given value, if non-null. If null, the given default value | ||||
|      * is returned. | ||||
|      * | ||||
|      * @param <T> | ||||
|      *     The type of value accepted and returned. | ||||
|      * | ||||
|      * @param value | ||||
|      *     The possibly null value to return if non-null. | ||||
|      * | ||||
|      * @param defaultValue | ||||
|      *     A function which supplies the value to return if the provided value | ||||
|      *     is null. | ||||
|      *      | ||||
|      * @return | ||||
|      *     The provided value, if non-null, otherwise the provided default | ||||
|      *     value. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while producing/retrieving the default value. | ||||
|      */ | ||||
|     private <T> T withDefault(T value, DefaultSupplier<T> defaultValue) | ||||
|             throws GuacamoleException { | ||||
|         return value != null ? value : defaultValue.get(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Parses and returns the given value, if non-null. If null, the given | ||||
|      * default value is returned. | ||||
|      * | ||||
|      * @param <T> | ||||
|      *     The type of value accepted and returned. | ||||
|      * | ||||
|      * @param property | ||||
|      *     The GuacamoleProperty implementation to use to parse the provided | ||||
|      *     String value. | ||||
|      * | ||||
|      * @param value | ||||
|      *     The possibly null value to return if non-null. | ||||
|      * | ||||
|      * @param defaultValue | ||||
|      *     A function which supplies the value to return if the provided value | ||||
|      *     is null. | ||||
|      *      | ||||
|      * @return | ||||
|      *     The provided value, if non-null, otherwise the provided default | ||||
|      *     value. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while producing/retrieving the default value. | ||||
|      */ | ||||
|     private <T> T withDefault(GuacamoleProperty<T> property, String value, | ||||
|             DefaultSupplier<T> defaultValue) | ||||
|             throws GuacamoleException { | ||||
|         return withDefault(property.parseValue(value), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Sets the LDAPConfiguration that should be used for the default values of | ||||
|      * any configuration options omitted from the YAML. If not set, an instance | ||||
|      * of {@link DefaultLDAPConfiguration} will be used. | ||||
|      * | ||||
|      * @param defaultConfig | ||||
|      *     The LDAPConfiguration to use for the default values of any omitted | ||||
|      *     configuration options. | ||||
|      */ | ||||
|     public void setDefaults(LDAPConfiguration defaultConfig) { | ||||
|         this.defaultConfig = defaultConfig; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String appliesTo(String username) throws GuacamoleException { | ||||
|  | ||||
|         // Match any user by default | ||||
|         if (matchUsernames == null || matchUsernames.isEmpty()) | ||||
|             return username; | ||||
|          | ||||
|         for (Pattern pattern : matchUsernames) { | ||||
|             Matcher matcher = pattern.matcher(username); | ||||
|             if (matcher.matches()) | ||||
|                 return matcher.groupCount() >= 1 ? matcher.group(1) : username; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getServerHostname() throws GuacamoleException { | ||||
|         return withDefault(hostname, defaultConfig::getServerHostname); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getServerPort() throws GuacamoleException { | ||||
|         return withDefault(port, () -> getEncryptionMethod().DEFAULT_PORT); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getUsernameAttributes() throws GuacamoleException { | ||||
|         return withDefault(usernameAttributes, defaultConfig::getUsernameAttributes); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getUserBaseDN() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_USER_BASE_DN, | ||||
|                 userBaseDn, defaultConfig::getUserBaseDN); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getConfigurationBaseDN() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN, | ||||
|                 configBaseDn, defaultConfig::getConfigurationBaseDN); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getGroupNameAttributes() throws GuacamoleException { | ||||
|         return withDefault(groupNameAttributes, defaultConfig::getGroupNameAttributes); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Dn getGroupBaseDN() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN, | ||||
|                 groupBaseDn, defaultConfig::getGroupBaseDN); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindDN() throws GuacamoleException { | ||||
|         return withDefault(searchBindDn, defaultConfig::getSearchBindDN); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getSearchBindPassword() throws GuacamoleException { | ||||
|         return withDefault(searchBindPassword, defaultConfig::getSearchBindPassword); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public EncryptionMethod getEncryptionMethod() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD, | ||||
|                 encryptionMethod, defaultConfig::getEncryptionMethod); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public LDAPSSLProtocol getSslProtocol() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_SSL_PROTOCOL, | ||||
|                 sslProtocol, defaultConfig::getSslProtocol); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxResults() throws GuacamoleException { | ||||
|         return withDefault(maxSearchResults, defaultConfig::getMaxResults); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AliasDerefMode getDereferenceAliases() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES, | ||||
|                 dereferenceAliases, defaultConfig::getDereferenceAliases); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean getFollowReferrals() throws GuacamoleException { | ||||
|         return withDefault(followReferrals, defaultConfig::getFollowReferrals); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getMaxReferralHops() throws GuacamoleException { | ||||
|         return withDefault(maxReferralHops, defaultConfig::getMaxReferralHops); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getUserSearchFilter() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER, | ||||
|                 userSearchFilter, defaultConfig::getUserSearchFilter); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode getGroupSearchFilter() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER, | ||||
|                 groupSearchFilter, defaultConfig::getGroupSearchFilter); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getOperationTimeout() throws GuacamoleException { | ||||
|         return withDefault(operationTimeout, defaultConfig::getOperationTimeout); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getNetworkTimeout() throws GuacamoleException { | ||||
|         return withDefault(networkTimeout, defaultConfig::getNetworkTimeout); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Collection<String> getAttributes() throws GuacamoleException { | ||||
|         return withDefault(userAttributes, defaultConfig::getAttributes); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public String getMemberAttribute() throws GuacamoleException { | ||||
|         return withDefault(memberAttribute, defaultConfig::getMemberAttribute); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public MemberAttributeType getMemberAttributeType() throws GuacamoleException { | ||||
|         return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE, | ||||
|                 memberAttributeType, defaultConfig::getMemberAttributeType); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,337 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
|  | ||||
| /** | ||||
|  * Configuration information defining how a particular LDAP server should be | ||||
|  * queried. | ||||
|  */ | ||||
| public interface LDAPConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * Tests whether this LDAPConfiguration applies to the user having the | ||||
|      * given username. If the configuration applies, the username that should | ||||
|      * be used to derive the user's DN is returned. | ||||
|      * | ||||
|      * @param username | ||||
|      *     The username to test. | ||||
|      * | ||||
|      * @return | ||||
|      *     The username that should be used to derive this user's DN, or null | ||||
|      *     if the configuration does not apply. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error prevents testing against this configuration. | ||||
|      */ | ||||
|     String appliesTo(String username) throws GuacamoleException; | ||||
|      | ||||
|     /** | ||||
|      * Returns the hostname or IP address of the LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     The hostname or IP address of the LDAP server. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the hostname or IP address of the LDAP server cannot be | ||||
|      *     retrieved. | ||||
|      */ | ||||
|     String getServerHostname() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the port of the LDAP server. The default value depends on which | ||||
|      * encryption method is being used. For unencrypted LDAP and STARTTLS, this | ||||
|      * will be 389. For LDAPS (LDAP over SSL) this will be 636. | ||||
|      * | ||||
|      * @return | ||||
|      *     The port of the LDAP server. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the port of the LDAP server cannot be retrieved. | ||||
|      */ | ||||
|     int getServerPort() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns all username attributes which should be used to query and bind | ||||
|      * users using the LDAP directory. | ||||
|      * | ||||
|      * @return | ||||
|      *     The username attributes which should be used to query and bind users | ||||
|      *     using the LDAP directory. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the username attributes cannot be retrieved. | ||||
|      */ | ||||
|     Collection<String> getUsernameAttributes() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the base DN under which all Guacamole users will be stored | ||||
|      * within the LDAP directory. | ||||
|      * | ||||
|      * @return | ||||
|      *     The base DN under which all Guacamole users will be stored within | ||||
|      *     the LDAP directory. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the user base DN cannot be retrieved. | ||||
|      */ | ||||
|     Dn getUserBaseDN() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the base DN under which all Guacamole configurations | ||||
|      * (connections) will be stored within the LDAP directory. If Guacamole | ||||
|      * configurations will not be stored within LDAP, null is returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The base DN under which all Guacamole configurations will be stored | ||||
|      *     within the LDAP directory, or null if no Guacamole configurations | ||||
|      *     will be stored within the LDAP directory. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the configuration base DN cannot be retrieved. | ||||
|      */ | ||||
|     Dn getConfigurationBaseDN() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns all attributes which should be used to determine the unique | ||||
|      * identifier of each user group. | ||||
|      * | ||||
|      * @return | ||||
|      *     The attributes which should be used to determine the unique | ||||
|      *     identifier of each group. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the group name attributes cannot be retrieved. | ||||
|      */ | ||||
|     Collection<String> getGroupNameAttributes() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the base DN under which all Guacamole role based access control | ||||
|      * (RBAC) groups will be stored within the LDAP directory. If RBAC will not | ||||
|      * be used, null is returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The base DN under which all Guacamole RBAC groups will be stored | ||||
|      *     within the LDAP directory, or null if RBAC will not be used. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the group base DN cannot be retrieved. | ||||
|      */ | ||||
|     Dn getGroupBaseDN() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the login that should be used when searching for the DNs of users | ||||
|      * attempting to authenticate. If no such search should be performed, null | ||||
|      * is returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The DN that should be used when searching for the DNs of users | ||||
|      *     attempting to authenticate, or null if no such search should be | ||||
|      *     performed. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the search bind DN cannot be retrieved. | ||||
|      */ | ||||
|     String getSearchBindDN() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the password that should be used when binding to the LDAP server | ||||
|      * using the DN returned by getSearchBindDN(). If no password should be | ||||
|      * used, null is returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The password that should be used when binding to the LDAP server | ||||
|      *     using the DN returned by getSearchBindDN(), or null if no password | ||||
|      *     should be used. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the search bind password cannot be retrieved. | ||||
|      */ | ||||
|     String getSearchBindPassword() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the encryption method that should be used when connecting to the | ||||
|      * LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     The encryption method that should be used when connecting to the | ||||
|      *     LDAP server. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the encryption method cannot be retrieved. | ||||
|      */ | ||||
|     EncryptionMethod getEncryptionMethod() throws GuacamoleException; | ||||
|      | ||||
|     /** | ||||
|      * Returns the SSL protocol that should be used when making a secure | ||||
|      * connection to the LDAP server. By default the latest available TLS | ||||
|      * version will be used. | ||||
|      *  | ||||
|      * @return | ||||
|      *     The SSL protocol that should be used when making a secure connection | ||||
|      *     to the LDAP server. | ||||
|      *  | ||||
|      * @throws GuacamoleException  | ||||
|      *     If the SSL protocol cannot be retrieved. | ||||
|      */ | ||||
|     LDAPSSLProtocol getSslProtocol() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns maximum number of results a LDAP query can return. | ||||
|      * | ||||
|      * @return | ||||
|      *     The maximum number of results a LDAP query can return. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the maximum number of results cannot be retrieved. | ||||
|      */ | ||||
|     int getMaxResults() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether or not LDAP aliases will be dereferenced. | ||||
|      * | ||||
|      * @return | ||||
|      *     The LDAP alias dereferencing mode. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the LDAP alias dereferencing mode cannot be retrieved. | ||||
|      */ | ||||
|     AliasDerefMode getDereferenceAliases() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether referrals should be automatically followed. | ||||
|      * | ||||
|      * @return | ||||
|      *     Whether referrals should be followed. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the configuration information determining whether LDAP referrals | ||||
|      *     should be followed cannot be retrieved. | ||||
|      */ | ||||
|     boolean getFollowReferrals() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the maximum number of referral hops to follow. | ||||
|      * | ||||
|      * @return | ||||
|      *     The maximum number of referral hops to follow. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the maximum number of referral hops cannot be retrieved. | ||||
|      */ | ||||
|     int getMaxReferralHops() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the search filter that should be used when querying the | ||||
|      * LDAP server for Guacamole users. | ||||
|      * | ||||
|      * @return | ||||
|      *     The search filter that should be used when querying the | ||||
|      *     LDAP server for users that are valid in Guacamole. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the user search filter cannot be retrieved. | ||||
|      */ | ||||
|     ExprNode getUserSearchFilter() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the search filter that should be used when querying the | ||||
|      * LDAP server for Guacamole groups. | ||||
|      * | ||||
|      * @return | ||||
|      *     The search filter that should be used when querying the | ||||
|      *     LDAP server for groups that are valid in Guacamole. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the group search filter cannot be retrieved. | ||||
|      */ | ||||
|     ExprNode getGroupSearchFilter() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the maximum number of milliseconds to wait for a response when | ||||
|      * communicating with the LDAP server. | ||||
|      * | ||||
|      * @return | ||||
|      *     The maximum number of milliseconds to wait for responses from the | ||||
|      *     LDAP server. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the LDAP network timeout cannot be retrieved. | ||||
|      */ | ||||
|     int getNetworkTimeout() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns the maximum number of seconds to wait for LDAP operations. | ||||
|      * | ||||
|      * @return | ||||
|      *     The maximum number of seconds to wait for LDAP operations. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the LDAP operation timeout cannot be retrieved. | ||||
|      */ | ||||
|     int getOperationTimeout() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns names of any LDAP user attributes that should be made available | ||||
|      * as parameter tokens. | ||||
|      * | ||||
|      * @return | ||||
|      *     A list of all LDAP user attributes that should be made available as | ||||
|      *     parameter tokens. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the names of the LDAP user attributes to be exposed as parameter | ||||
|      *     tokens cannot be retrieved. | ||||
|      */ | ||||
|     Collection<String> getAttributes() throws GuacamoleException; | ||||
|      | ||||
|     /** | ||||
|      * Returns the name of the LDAP attribute used to enumerate members in a | ||||
|      * group. | ||||
|      *  | ||||
|      * @return | ||||
|      *     The name of the LDAP attribute to use to enumerate | ||||
|      *     members in a group. | ||||
|      *  | ||||
|      * @throws GuacamoleException | ||||
|      *     If the group member attribute cannot be retrieved. | ||||
|      */ | ||||
|     String getMemberAttribute() throws GuacamoleException; | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the LDAP attribute used to enumerate members in a group | ||||
|      * specifies a UID or DN. | ||||
|      * | ||||
|      * @return | ||||
|      *     The type of data contained in the LDAP attribute used to enumerate | ||||
|      *     members in a group. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the type of attribute used to enumerate group members cannot be | ||||
|      *     retrieved. | ||||
|      */ | ||||
|     MemberAttributeType getMemberAttributeType() throws GuacamoleException; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,310 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import org.apache.directory.api.ldap.model.message.AliasDerefMode; | ||||
| import org.apache.guacamole.properties.BooleanGuacamoleProperty; | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty; | ||||
| import org.apache.guacamole.properties.IntegerGuacamoleProperty; | ||||
| import org.apache.guacamole.properties.StringGuacamoleProperty; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Provides properties required for use of the LDAP authentication provider. | ||||
|  * These properties will be read from guacamole.properties when the LDAP | ||||
|  * authentication provider is used. | ||||
|  */ | ||||
| public class LDAPGuacamoleProperties { | ||||
|  | ||||
|     /** | ||||
|      * This class should not be instantiated. | ||||
|      */ | ||||
|     private LDAPGuacamoleProperties() {} | ||||
|  | ||||
|     /** | ||||
|      * The base DN to search for Guacamole configurations. | ||||
|      */ | ||||
|     public static final LdapDnGuacamoleProperty LDAP_CONFIG_BASE_DN = | ||||
|             new LdapDnGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-config-base-dn"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The base DN of users. All users must be contained somewhere within the | ||||
|      * subtree of this DN. If the LDAP authentication will not be given its own | ||||
|      * credentials for querying other LDAP users, all users must be direct | ||||
|      * children of this base DN, varying only by LDAP_USERNAME_ATTRIBUTE. | ||||
|      */ | ||||
|     public static final LdapDnGuacamoleProperty LDAP_USER_BASE_DN = | ||||
|             new LdapDnGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-user-base-dn"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The base DN of role based access control (RBAC) groups. All groups which | ||||
|      * will be used for RBAC must be contained somewhere within the subtree of | ||||
|      * this DN. | ||||
|      */ | ||||
|     public static final LdapDnGuacamoleProperty LDAP_GROUP_BASE_DN = | ||||
|             new LdapDnGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-group-base-dn"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The attribute or attributes which identify users. One of these | ||||
|      * attributes must be present within each Guacamole user's record in the | ||||
|      * LDAP directory. If the LDAP authentication will not be given its own | ||||
|      * credentials for querying other LDAP users, this list may contain only | ||||
|      * one attribute, and the concatenation of that attribute and the value of | ||||
|      * LDAP_USER_BASE_DN must equal the user's full DN. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_USERNAME_ATTRIBUTE = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-username-attribute"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The attribute or attributes which identify user groups. One of these | ||||
|      * attributes must be present within each Guacamole user group's record in | ||||
|      * the LDAP directory for that group to be visible. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_GROUP_NAME_ATTRIBUTE = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-group-name-attribute"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The port on the LDAP server to connect to when authenticating users. | ||||
|      */ | ||||
|     public static final IntegerGuacamoleProperty LDAP_PORT = | ||||
|             new IntegerGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-port"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The hostname of the LDAP server to connect to when authenticating users. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_HOSTNAME = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-hostname"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The user that the LDAP extension should bind as when searching for the | ||||
|      * accounts of users attempting to log in. The format of this parameter | ||||
|      * will vary based on the LDAP server implementation - often it is expected | ||||
|      * to be in full LDAP DN format; however various LDAP server implementations | ||||
|      * allow this to be in other formats (e.g. Active Directory allows | ||||
|      * User Principal Name, or UPN, format). For this reason the configuration | ||||
|      * allows this to be any string. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_SEARCH_BIND_DN = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-search-bind-dn"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The password to provide to the LDAP server when binding as | ||||
|      * LDAP_SEARCH_BIND_DN. If LDAP_SEARCH_BIND_DN is not specified, this | ||||
|      * property has no effect. If this property is not specified, no password | ||||
|      * will be provided when attempting to bind as LDAP_SEARCH_BIND_DN. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_SEARCH_BIND_PASSWORD = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-search-bind-password"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The encryption method to use when connecting to the LDAP server, if any. | ||||
|      * The chosen method will also dictate the default port if not already | ||||
|      * explicitly specified via LDAP_PORT. | ||||
|      */ | ||||
|     public static final EnumGuacamoleProperty<EncryptionMethod> LDAP_ENCRYPTION_METHOD = | ||||
|             new EnumGuacamoleProperty<EncryptionMethod>(EncryptionMethod.class) { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-encryption-method"; } | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     public static final EnumGuacamoleProperty<LDAPSSLProtocol> LDAP_SSL_PROTOCOL = | ||||
|             new EnumGuacamoleProperty<LDAPSSLProtocol>(LDAPSSLProtocol.class) { | ||||
|      | ||||
|         @Override | ||||
|         public String getName() { return "ldap-ssl-protocol"; } | ||||
|                  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The maximum number of results a LDAP query can return. | ||||
|      */ | ||||
|     public static final IntegerGuacamoleProperty LDAP_MAX_SEARCH_RESULTS = | ||||
|             new IntegerGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-max-search-results"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Property that controls whether or not the LDAP connection follows | ||||
|      * (dereferences) aliases as it searches the tree. | ||||
|      */ | ||||
|     public static final EnumGuacamoleProperty<AliasDerefMode> LDAP_DEREFERENCE_ALIASES = | ||||
|             new EnumGuacamoleProperty<AliasDerefMode>( | ||||
|                 "never",     AliasDerefMode.NEVER_DEREF_ALIASES, | ||||
|                 "searching", AliasDerefMode.DEREF_IN_SEARCHING, | ||||
|                 "finding",   AliasDerefMode.DEREF_FINDING_BASE_OBJ, | ||||
|                 "always",    AliasDerefMode.DEREF_ALWAYS | ||||
|             ) { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-dereference-aliases"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * A search filter to apply to user LDAP queries. | ||||
|      */ | ||||
|     public static final LdapFilterGuacamoleProperty LDAP_USER_SEARCH_FILTER = | ||||
|             new LdapFilterGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-user-search-filter"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * A search filter to apply to group LDAP queries. | ||||
|      */ | ||||
|     public static final LdapFilterGuacamoleProperty LDAP_GROUP_SEARCH_FILTER = | ||||
|             new LdapFilterGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-group-search-filter"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Whether or not we should follow referrals. | ||||
|      */ | ||||
|     public static final BooleanGuacamoleProperty LDAP_FOLLOW_REFERRALS = | ||||
|             new BooleanGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-follow-referrals"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Maximum number of referral hops to follow. | ||||
|      */ | ||||
|     public static final IntegerGuacamoleProperty LDAP_MAX_REFERRAL_HOPS = | ||||
|             new IntegerGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-max-referral-hops"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Number of seconds to wait for LDAP operations to complete. | ||||
|      */ | ||||
|     public static final IntegerGuacamoleProperty LDAP_OPERATION_TIMEOUT = | ||||
|             new IntegerGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-operation-timeout"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Number of milliseconds to wait for responses from the LDAP server. | ||||
|      */ | ||||
|     public static final IntegerGuacamoleProperty LDAP_NETWORK_TIMEOUT = | ||||
|             new IntegerGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-network-timeout"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Custom attribute or attributes to query from Guacamole user's record in | ||||
|      * the LDAP directory. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_USER_ATTRIBUTES = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-user-attributes"; } | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * LDAP attribute used to enumerate members of a group in the LDAP directory. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty LDAP_MEMBER_ATTRIBUTE = | ||||
|             new StringGuacamoleProperty() { | ||||
|        | ||||
|         @Override | ||||
|         public String getName() { return "ldap-member-attribute"; } | ||||
|          | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * Specify the type of data contained in 'ldap-member-attribute'. | ||||
|      */ | ||||
|     public static final EnumGuacamoleProperty<MemberAttributeType> LDAP_MEMBER_ATTRIBUTE_TYPE = | ||||
|             new EnumGuacamoleProperty<MemberAttributeType>(MemberAttributeType.class) { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "ldap-member-attribute-type"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; | ||||
|  | ||||
| /** | ||||
|  * All possible SSL protocols which may be used for secure LDAP connections. | ||||
|  */ | ||||
| public enum LDAPSSLProtocol { | ||||
|  | ||||
|     /** | ||||
|      * Use SSLv3 for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("SSLv3") | ||||
|     SSLv3("SSLv3"), | ||||
|      | ||||
|     /** | ||||
|      * Use original TLS for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("TLS") | ||||
|     TLS("TLS"), | ||||
|      | ||||
|     /** | ||||
|      * Use TLSv1 for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("TLSv1") | ||||
|     TLSv1("TLSv1"), | ||||
|      | ||||
|     /** | ||||
|      * Use TLSv1.1 for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("TLSv1.1") | ||||
|     TLSv1_1("TLSv1.1"), | ||||
|      | ||||
|     /** | ||||
|      * Use TLSv1.2 for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("TLSv1.2") | ||||
|     TLSv1_2("TLSv1.2"), | ||||
|      | ||||
|     /** | ||||
|      * Use TLSv1.3 for secure LDAP connection. | ||||
|      */ | ||||
|     @PropertyValue("TLSv1.3") | ||||
|     TLSv1_3("TLSv1.3"); | ||||
|  | ||||
|     /** | ||||
|      * The string value of the option to use which is ultimately what the LDAP | ||||
|      * API consumes to set the SSL protocol. | ||||
|      */ | ||||
|     public final String STRING_VALUE; | ||||
|  | ||||
|     /** | ||||
|      * Initializes this SSL protocol such that it is associated with the | ||||
|      * given string value. | ||||
|      * | ||||
|      * @param value | ||||
|      *     The string value that will be associated with the enum value. | ||||
|      */ | ||||
|     private LDAPSSLProtocol(String value) { | ||||
|         this.STRING_VALUE = value; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return STRING_VALUE; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,50 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.properties.GuacamoleProperty; | ||||
|  | ||||
| /** | ||||
|  * A GuacamoleProperty that converts a string to a Dn that can be used | ||||
|  * in LDAP connections.  An exception is thrown if the provided DN is invalid | ||||
|  * and cannot be parsed. | ||||
|  */ | ||||
| public abstract class LdapDnGuacamoleProperty implements GuacamoleProperty<Dn> { | ||||
|  | ||||
|     @Override | ||||
|     public Dn parseValue(String value) throws GuacamoleException { | ||||
|  | ||||
|         if (value == null) | ||||
|             return null; | ||||
|  | ||||
|         try { | ||||
|             return new Dn(value); | ||||
|         } | ||||
|         catch (LdapInvalidDnException e) { | ||||
|             throw new GuacamoleServerException("The DN \"" + value + "\" is invalid.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import java.text.ParseException; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.filter.FilterParser; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.properties.GuacamoleProperty; | ||||
|  | ||||
| /** | ||||
|  * A GuacamoleProperty with a value of an ExprNode query filter.  The string | ||||
|  * provided is passed through the FilterParser returning the ExprNode object, | ||||
|  * or an exception is thrown if the filter is invalid and cannot be correctly | ||||
|  * parsed. | ||||
|  */ | ||||
| public abstract class LdapFilterGuacamoleProperty implements GuacamoleProperty<ExprNode> { | ||||
|  | ||||
|     @Override | ||||
|     public ExprNode parseValue(String value) throws GuacamoleException { | ||||
|  | ||||
|         // No value provided, so return null. | ||||
|         if (value == null) | ||||
|             return null; | ||||
|  | ||||
|         try { | ||||
|             return FilterParser.parse(value); | ||||
|         } | ||||
|         catch (ParseException e) { | ||||
|             throw new GuacamoleServerException("\"" + value + "\" is not a valid LDAP filter.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.conf; | ||||
|  | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; | ||||
|  | ||||
| /** | ||||
|  * All possible means of describing membership within LDAP group directory | ||||
|  * records. | ||||
|  */ | ||||
| public enum MemberAttributeType { | ||||
|  | ||||
|     /** | ||||
|      * Group membership is specified by DN. | ||||
|      */ | ||||
|     @PropertyValue("dn") | ||||
|     DN, | ||||
|  | ||||
|     /** | ||||
|      * Group membership is specified by usercode. | ||||
|      */ | ||||
|     @PropertyValue("uid") | ||||
|     UID; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,403 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.connection; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.directory.api.ldap.model.entry.Attribute; | ||||
| import org.apache.directory.api.ldap.model.entry.Entry; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapException; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; | ||||
| import org.apache.directory.api.ldap.model.filter.AndNode; | ||||
| import org.apache.directory.api.ldap.model.filter.EqualityNode; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.filter.OrNode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.ObjectQueryService; | ||||
| import org.apache.guacamole.auth.ldap.group.UserGroupService; | ||||
| import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; | ||||
| import org.apache.guacamole.environment.LocalEnvironment; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Connection; | ||||
| import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration; | ||||
| import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration.EncryptionMethod; | ||||
| import org.apache.guacamole.net.auth.TokenInjectingConnection; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleConnection; | ||||
| import org.apache.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. | ||||
|  */ | ||||
| public class ConnectionService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(ConnectionService.class); | ||||
|      | ||||
|     /** | ||||
|      * The name of the LDAP attribute that stores connection configuration | ||||
|      * parameters for Guacamole. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_PARAMETER = "guacConfigParameter"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the LDAP attribute that stores the protocol for a Guacamole | ||||
|      * connection. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_PROTOCOL = "guacConfigProtocol"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the LDAP attribute that stores guacd proxy hostname. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_PROXY_HOSTNAME = "guacConfigProxyHostname"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the LDAP attribute that stores guacd proxy port. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_PROXY_PORT = "guacConfigProxyPort"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the LDAP attribute that stores guacd proxy encryption method. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_PROXY_ENCRYPTION = "guacConfigProxyEncryption"; | ||||
|  | ||||
|     /** | ||||
|      * Service for executing LDAP queries. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ObjectQueryService queryService; | ||||
|  | ||||
|     /** | ||||
|      * Service for retrieving user groups. | ||||
|      */ | ||||
|     @Inject | ||||
|     private UserGroupService userGroupService; | ||||
|      | ||||
|     /** | ||||
|      * The objectClass that is present on any Guacamole connections stored | ||||
|      * in LDAP. | ||||
|      */ | ||||
|     public static final String CONNECTION_LDAP_OBJECT_CLASS = "guacConfigGroup"; | ||||
|      | ||||
|     /** | ||||
|      * The attribute name that uniquely identifies a Guacamole connection object | ||||
|      * in LDAP. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_NAME_ID = "cn"; | ||||
|      | ||||
|     /** | ||||
|      * The LDAP attribute name where the Guacamole connection protocol is stored. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_NAME_PROTOCOL = "guacConfigProtocol"; | ||||
|      | ||||
|     /** | ||||
|      * The LDAP attribute name that contains any connection parameters. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_NAME_PARAMETER = "guacConfigParameter"; | ||||
|      | ||||
|     /** | ||||
|      * The LDAP attribute name that provides group-based access control for | ||||
|      * Guacamole connection objects. | ||||
|      */ | ||||
|     public static final String LDAP_ATTRIBUTE_NAME_GROUPS = "seeAlso"; | ||||
|      | ||||
|     /** | ||||
|      * A list of all attribute names that could be associated with a Guacamole | ||||
|      * connection object in LDAP. | ||||
|      */ | ||||
|     public static final Collection<String> GUAC_CONFIG_LDAP_ATTRIBUTES = | ||||
|             Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( | ||||
|                     LDAP_ATTRIBUTE_NAME_ID, | ||||
|                     LDAP_ATTRIBUTE_NAME_PROTOCOL, | ||||
|                     LDAP_ATTRIBUTE_NAME_PARAMETER, | ||||
|                     LDAP_ATTRIBUTE_NAME_GROUPS | ||||
|             ))); | ||||
|              | ||||
|              | ||||
|  | ||||
|     /** | ||||
|      * Returns all Guacamole connections accessible to the given user. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The AuthenticatedUser object associated with the user who is | ||||
|      *     currently authenticated with Guacamole. | ||||
|      * | ||||
|      * @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(LDAPAuthenticatedUser user) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         ConnectedLDAPConfiguration ldapConfig = user.getLDAPConfiguration(); | ||||
|  | ||||
|         // Do not return any connections if base DN is not specified | ||||
|         Dn configurationBaseDN = ldapConfig.getConfigurationBaseDN(); | ||||
|         if (configurationBaseDN == null) | ||||
|             return Collections.<String, Connection>emptyMap(); | ||||
|  | ||||
|         try { | ||||
|  | ||||
|             // Get the search filter for finding connections accessible by the | ||||
|             // current user | ||||
|             ExprNode connectionSearchFilter = getConnectionSearchFilter(user); | ||||
|  | ||||
|             // Find all Guacamole connections for the given user by | ||||
|             // looking for direct membership in the guacConfigGroup | ||||
|             // and possibly any groups the user is a member of that are | ||||
|             // referred to in the seeAlso attribute of the guacConfigGroup. | ||||
|             List<Entry> results = queryService.search(ldapConfig, ldapConfig.getLDAPConnection(), | ||||
|                     configurationBaseDN, connectionSearchFilter, 0, GUAC_CONFIG_LDAP_ATTRIBUTES); | ||||
|  | ||||
|             // Return a map of all readable connections | ||||
|             return queryService.asMap(results, (entry) -> { | ||||
|  | ||||
|                 // Get common name (CN) | ||||
|                 Attribute cn = entry.get(LDAP_ATTRIBUTE_NAME_ID); | ||||
|                  | ||||
|                 if (cn == null) { | ||||
|                     logger.warn("{} is missing a {}.", | ||||
|                             CONNECTION_LDAP_OBJECT_CLASS, LDAP_ATTRIBUTE_NAME_ID); | ||||
|                     return null; | ||||
|                 } | ||||
|                  | ||||
|                 String cnName; | ||||
|                  | ||||
|                 try { | ||||
|                     cnName = cn.getString(); | ||||
|                 } | ||||
|                 catch (LdapInvalidAttributeValueException e) { | ||||
|                     logger.error("Invalid value for {} attribute: {}", | ||||
|                             LDAP_ATTRIBUTE_NAME_ID, e.getMessage()); | ||||
|                     logger.debug("LDAP exception while getting CN attribute.", e); | ||||
|                     return null; | ||||
|                 } | ||||
|  | ||||
|                 // Get associated protocol | ||||
|                 Attribute protocol = entry.get(LDAP_ATTRIBUTE_NAME_PROTOCOL); | ||||
|                 if (protocol == null) { | ||||
|                     logger.warn("{} \"{}\" is missing the " | ||||
|                               + "required \"{}\" attribute.", | ||||
|                             CONNECTION_LDAP_OBJECT_CLASS, | ||||
|                             cnName, LDAP_ATTRIBUTE_NAME_PROTOCOL); | ||||
|                     return null; | ||||
|                 } | ||||
|  | ||||
|                 // Set protocol | ||||
|                 GuacamoleConfiguration config = new GuacamoleConfiguration(); | ||||
|                 try { | ||||
|                     config.setProtocol(protocol.getString()); | ||||
|                 } | ||||
|                 catch (LdapInvalidAttributeValueException e) { | ||||
|                     logger.error("Invalid value of the protocol entry: {}", e.getMessage()); | ||||
|                     logger.debug("LDAP exception when getting protocol value.", e); | ||||
|                     return null; | ||||
|                 } | ||||
|                  | ||||
|                 // Get proxy configuration, if any | ||||
|                 GuacamoleProxyConfiguration proxyConfig; | ||||
|                 try { | ||||
|                     proxyConfig = getProxyConfiguration(entry); | ||||
|                 } | ||||
|                 catch (GuacamoleException e) { | ||||
|                     logger.error("Failed to retrieve proxy configuration.", e.getMessage()); | ||||
|                     logger.debug("Guacamole Exception when retrieving proxy configuration.", e); | ||||
|                     return null; | ||||
|                 } | ||||
|  | ||||
|                 // Get parameters, if any | ||||
|                 Attribute parameterAttribute = entry.get(LDAP_ATTRIBUTE_NAME_PARAMETER); | ||||
|                 if (parameterAttribute != null) { | ||||
|  | ||||
|                     // For each parameter | ||||
|                     while (parameterAttribute.size() > 0) { | ||||
|                         String parameter; | ||||
|                         try { | ||||
|                             parameter = parameterAttribute.getString(); | ||||
|                         } | ||||
|                         catch (LdapInvalidAttributeValueException e) { | ||||
|                             logger.warn("Parameter value not valid for {}: {}", cnName, e.getMessage()); | ||||
|                             logger.debug("LDAP exception when getting parameter value.", e); | ||||
|                             return null; | ||||
|                         } | ||||
|                         parameterAttribute.remove(parameter); | ||||
|  | ||||
|                         // 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 | ||||
|                 Connection connection = new SimpleConnection(cnName, cnName, proxyConfig, config, true); | ||||
|                 connection.setParentIdentifier(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP); | ||||
|  | ||||
|                 // Inject LDAP-specific tokens only if LDAP handled user | ||||
|                 // authentication | ||||
|                 if (user instanceof LDAPAuthenticatedUser) | ||||
|                     connection = new TokenInjectingConnection(connection, user.getTokens()); | ||||
|  | ||||
|                 return connection; | ||||
|  | ||||
|             }); | ||||
|  | ||||
|         } | ||||
|         catch (LdapException e) { | ||||
|             throw new GuacamoleServerException("Error while querying for connections.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an LDAP search filter which queries all connections accessible | ||||
|      * by the given user. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The AuthenticatedUser object associated with the user who is | ||||
|      *     currently authenticated with Guacamole. | ||||
|      * | ||||
|      * @return | ||||
|      *     An LDAP search filter which queries all guacConfigGroup objects | ||||
|      *     accessible by the user having the given DN. | ||||
|      * | ||||
|      * @throws LdapException | ||||
|      *     If an error occurs preventing retrieval of user groups. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs retrieving the group base DN. | ||||
|      */ | ||||
|     private ExprNode getConnectionSearchFilter(LDAPAuthenticatedUser user) | ||||
|             throws LdapException, GuacamoleException { | ||||
|  | ||||
|         ConnectedLDAPConfiguration config = user.getLDAPConfiguration(); | ||||
|         Dn userDN = config.getBindDN(); | ||||
|  | ||||
|         AndNode searchFilter = new AndNode(); | ||||
|  | ||||
|         // Add the prefix to the search filter, prefix filter searches for guacConfigGroups with the userDN as the member attribute value | ||||
|         searchFilter.addNode(new EqualityNode("objectClass", CONNECTION_LDAP_OBJECT_CLASS)); | ||||
|          | ||||
|         // Apply group filters | ||||
|         OrNode groupFilter = new OrNode(); | ||||
|         groupFilter.addNode(new EqualityNode(config.getMemberAttribute(), | ||||
|             userDN.toString())); | ||||
|  | ||||
|         // Additionally filter by group membership if the current user is a | ||||
|         // member of any user groups | ||||
|         List<Entry> userGroups = userGroupService.getParentUserGroupEntries(config, userDN); | ||||
|         if (!userGroups.isEmpty()) { | ||||
|             userGroups.forEach(entry -> | ||||
|                 groupFilter.addNode(new EqualityNode(LDAP_ATTRIBUTE_NAME_GROUPS,entry.getDn().toString())) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         // Complete the search filter. | ||||
|         searchFilter.addNode(groupFilter); | ||||
|  | ||||
|         return searchFilter; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Given an LDAP entry that stores a GuacamoleConfiguration, generate a | ||||
|      * GuacamoleProxyConfiguration that tells the client how to connect to guacd. | ||||
|      * If the proxy configuration values are not found in the LDAP entry the | ||||
|      * defaults from the environment are used. If errors occur while trying to | ||||
|      * ready or parse values from the LDAP entry a GuacamoleException is thrown. | ||||
|      *  | ||||
|      * @param connectionEntry | ||||
|      *     The LDAP entry that should be checked for proxy configuration values. | ||||
|      *  | ||||
|      * @return | ||||
|      *     The GuacamoleProxyConfiguration that contains information on how | ||||
|      *     to contact guacd for the given Guacamole connection configuration. | ||||
|      *  | ||||
|      * @throws GuacamoleException  | ||||
|      *     If errors occur trying to parse LDAP values from the entry. | ||||
|      */ | ||||
|     private GuacamoleProxyConfiguration getProxyConfiguration(Entry connectionEntry) | ||||
|             throws GuacamoleException { | ||||
|          | ||||
|         try { | ||||
|                      | ||||
|             // Get default proxy configuration values | ||||
|             GuacamoleProxyConfiguration proxyConfig = LocalEnvironment.getInstance().getDefaultGuacamoleProxyConfiguration(); | ||||
|             String proxyHostname = proxyConfig.getHostname(); | ||||
|             int proxyPort = proxyConfig.getPort(); | ||||
|             EncryptionMethod proxyEncryption = proxyConfig.getEncryptionMethod(); | ||||
|  | ||||
|             // Get the proxy hostname | ||||
|             Attribute proxyHostAttr = connectionEntry.get(LDAP_ATTRIBUTE_PROXY_HOSTNAME); | ||||
|             if (proxyHostAttr != null && proxyHostAttr.size() > 0) | ||||
|                 proxyHostname = proxyHostAttr.getString(); | ||||
|  | ||||
|             // Get the proxy port | ||||
|             Attribute proxyPortAttr = connectionEntry.get(LDAP_ATTRIBUTE_PROXY_PORT); | ||||
|             if (proxyPortAttr != null && proxyPortAttr.size() > 0) | ||||
|                 proxyPort = Integer.parseInt(proxyPortAttr.getString()); | ||||
|  | ||||
|             // Get the proxy encryption method | ||||
|             Attribute proxyEncryptionAttr = connectionEntry.get(LDAP_ATTRIBUTE_PROXY_ENCRYPTION); | ||||
|             if (proxyEncryptionAttr != null && proxyEncryptionAttr.size() > 0) { | ||||
|                 try { | ||||
|                     proxyEncryption = EncryptionMethod.valueOf(proxyEncryptionAttr.getString()); | ||||
|                 } | ||||
|                 catch (IllegalArgumentException e) { | ||||
|                     throw new GuacamoleServerException("Unknown encryption method specified, value must be either \"NONE\" or \"SSL\".", e); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Return a new proxy configuration | ||||
|             return new GuacamoleProxyConfiguration(proxyHostname, proxyPort, proxyEncryption); | ||||
|         } | ||||
|         catch (LdapInvalidAttributeValueException e) { | ||||
|             logger.error("Invalid value in proxy configuration: {}", e.getMessage()); | ||||
|             logger.debug("LDAP exception fetching proxy attribute value.", e); | ||||
|             throw new GuacamoleServerException("Invalid LDAP value in proxy configuration.", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,293 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.group; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import org.apache.directory.api.ldap.model.entry.Entry; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; | ||||
| import org.apache.directory.api.ldap.model.filter.AndNode; | ||||
| import org.apache.directory.api.ldap.model.filter.EqualityNode; | ||||
| import org.apache.directory.api.ldap.model.filter.ExprNode; | ||||
| import org.apache.directory.api.ldap.model.filter.NotNode; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.auth.ldap.conf.MemberAttributeType; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.ObjectQueryService; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.user.LDAPAuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.UserGroup; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleUserGroup; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for querying user group membership and retrieving user groups | ||||
|  * visible to a particular Guacamole user. | ||||
|  */ | ||||
| public class UserGroupService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(UserGroupService.class); | ||||
|  | ||||
|     /** | ||||
|      * Service for executing LDAP queries. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ObjectQueryService queryService; | ||||
|  | ||||
|     /** | ||||
|      * Returns the base search filter which should be used to retrieve user | ||||
|      * groups which do not represent Guacamole connections. As excluding the | ||||
|      * guacConfigGroup object class may not work as expected if it is not | ||||
|      * defined (may always return zero results), it should only be explicitly | ||||
|      * excluded if it is expected to have been defined. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @return | ||||
|      *     The base search filter which should be used to retrieve user groups. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If guacamole.properties cannot be parsed. | ||||
|      */ | ||||
|     private ExprNode getGroupSearchFilter(LDAPConfiguration config) throws GuacamoleException { | ||||
|  | ||||
|         // Use filter defined by "ldap-group-search-filter" as basis for all | ||||
|         // retrieval of user groups | ||||
|         ExprNode groupFilter = config.getGroupSearchFilter(); | ||||
|  | ||||
|         // Explicitly exclude guacConfigGroup object class only if it should | ||||
|         // be assumed to be defined (query may fail due to no such object | ||||
|         // class existing otherwise) | ||||
|         if (config.getConfigurationBaseDN() != null) { | ||||
|             groupFilter = new AndNode( | ||||
|                 groupFilter, | ||||
|                 new NotNode(new EqualityNode<String>("objectClass", "guacConfigGroup")) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return groupFilter; | ||||
|          | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns all Guacamole user groups accessible to the given user. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The AuthenticatedUser object associated with the user who is | ||||
|      *     currently authenticated with Guacamole. | ||||
|      * | ||||
|      * @return | ||||
|      *     All user groups accessible to the user currently bound under the | ||||
|      *     given LDAP connection, as a map of user group identifier to | ||||
|      *     corresponding UserGroup object. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs preventing retrieval of user groups. | ||||
|      */ | ||||
|     public Map<String, UserGroup> getUserGroups(LDAPAuthenticatedUser user) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         ConnectedLDAPConfiguration config = user.getLDAPConfiguration(); | ||||
|          | ||||
|         // Do not return any user groups if base DN is not specified | ||||
|         Dn groupBaseDN = config.getGroupBaseDN(); | ||||
|         if (groupBaseDN == null) | ||||
|             return Collections.emptyMap(); | ||||
|  | ||||
|         // Gather all attributes relevant for a group | ||||
|         String memberAttribute = config.getMemberAttribute(); | ||||
|         Collection<String> groupAttributes = new HashSet<>(config.getGroupNameAttributes()); | ||||
|         groupAttributes.add(memberAttribute); | ||||
|  | ||||
|         // Retrieve all visible user groups which are not guacConfigGroups | ||||
|         Collection<String> attributes = config.getGroupNameAttributes(); | ||||
|         List<Entry> results = queryService.search( | ||||
|             config, | ||||
|             config.getLDAPConnection(), | ||||
|             groupBaseDN, | ||||
|             getGroupSearchFilter(config), | ||||
|             attributes, | ||||
|             null, | ||||
|             groupAttributes | ||||
|         ); | ||||
|  | ||||
|         // Convert retrieved user groups to map of identifier to Guacamole | ||||
|         // user group object | ||||
|         return queryService.asMap(results, entry -> { | ||||
|  | ||||
|             // Translate entry into UserGroup object having proper identifier | ||||
|             try { | ||||
|                 String name = queryService.getIdentifier(entry, attributes); | ||||
|                 if (name != null) | ||||
|                     return new SimpleUserGroup(name); | ||||
|             } | ||||
|             catch (LdapInvalidAttributeValueException e) { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             // Ignore user groups which lack a name attribute | ||||
|             logger.debug("User group \"{}\" is missing a name attribute " | ||||
|                     + "and will be ignored.", entry.getDn().toString()); | ||||
|             return null; | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the LDAP entries representing all user groups that the given | ||||
|      * user is a member of. Only user groups which are readable by the current | ||||
|      * user will be retrieved. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param userDN | ||||
|      *     The DN of the user whose group membership should be retrieved. | ||||
|      * | ||||
|      * @return | ||||
|      *     The LDAP entries representing all readable parent user groups of the | ||||
|      *     user having the given DN. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs preventing retrieval of user groups. | ||||
|      */ | ||||
|     public List<Entry> getParentUserGroupEntries(ConnectedLDAPConfiguration config, Dn userDN) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Do not return any user groups if base DN is not specified | ||||
|         Dn groupBaseDN = config.getGroupBaseDN(); | ||||
|         if (groupBaseDN == null) | ||||
|             return Collections.emptyList(); | ||||
|  | ||||
|         // memberAttribute specified in properties could contain DN or username  | ||||
|         MemberAttributeType memberAttributeType = config.getMemberAttributeType(); | ||||
|         String userIDorDN = userDN.toString(); | ||||
|         Collection<String> userAttributes = config.getUsernameAttributes(); | ||||
|         if (memberAttributeType == MemberAttributeType.UID) { | ||||
|             // Retrieve user objects with userDN | ||||
|             List<Entry> userEntries = queryService.search( | ||||
|                 config, | ||||
|                 config.getLDAPConnection(), | ||||
|                 userDN, | ||||
|                 config.getUserSearchFilter(), | ||||
|                 0, | ||||
|                 userAttributes); | ||||
|             // ... there can surely only be one | ||||
|             if (userEntries.size() != 1) | ||||
|                 logger.warn("user DN \"{}\" does not return unique value " | ||||
|                         + "and will be ignored", userDN.toString()); | ||||
|             else { | ||||
|                 // determine unique identifier for user | ||||
|                 Entry userEntry = userEntries.get(0); | ||||
|                 try { | ||||
|                     userIDorDN = queryService.getIdentifier(userEntry, | ||||
|                                          userAttributes); | ||||
|                 } | ||||
|                 catch (LdapInvalidAttributeValueException e) { | ||||
|                     logger.error("User group missing identifier: {}", | ||||
|                             e.getMessage()); | ||||
|                     logger.debug("LDAP exception while getting " | ||||
|                             + "group identifier.", e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Gather all attributes relevant for a group | ||||
|         String memberAttribute = config.getMemberAttribute(); | ||||
|         Collection<String> groupAttributes = new HashSet<>(config.getGroupNameAttributes()); | ||||
|         groupAttributes.add(memberAttribute); | ||||
|  | ||||
|         // Get all groups the user is a member of starting at the groupBaseDN, | ||||
|         // excluding guacConfigGroups | ||||
|         return queryService.search( | ||||
|             config, | ||||
|             config.getLDAPConnection(), | ||||
|             groupBaseDN, | ||||
|             getGroupSearchFilter(config), | ||||
|             Collections.singleton(memberAttribute), | ||||
|             userIDorDN, | ||||
|             groupAttributes | ||||
|         ); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the identifiers of all user groups that the given user is a | ||||
|      * member of. Only identifiers of user groups which are readable by the | ||||
|      * current user will be retrieved. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param userDN | ||||
|      *     The DN of the user whose group membership should be retrieved. | ||||
|      * | ||||
|      * @return | ||||
|      *     The identifiers of all readable parent user groups of the user | ||||
|      *     having the given DN. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs preventing retrieval of user groups. | ||||
|      */ | ||||
|     public Set<String> getParentUserGroupIdentifiers(ConnectedLDAPConfiguration config, Dn userDN) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         Collection<String> attributes = config.getGroupNameAttributes(); | ||||
|         List<Entry> userGroups = getParentUserGroupEntries(config, userDN); | ||||
|  | ||||
|         Set<String> identifiers = new HashSet<>(userGroups.size()); | ||||
|         userGroups.forEach(entry -> { | ||||
|  | ||||
|             // Determine unique identifier for user group | ||||
|             try { | ||||
|                 String name = queryService.getIdentifier(entry, attributes); | ||||
|                 if (name != null) | ||||
|                     identifiers.add(name); | ||||
|  | ||||
|                 // Ignore user groups which lack a name attribute | ||||
|                 else | ||||
|                     logger.debug("User group \"{}\" is missing a name attribute " | ||||
|                             + "and will be ignored.", entry.getDn().toString()); | ||||
|             } | ||||
|             catch (LdapInvalidAttributeValueException e) { | ||||
|                 logger.error("User group missing identifier: {}", | ||||
|                         e.getMessage()); | ||||
|                 logger.debug("LDAP exception while getting group identifier.", e); | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         return identifiers; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,159 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.user; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.util.Collections; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; | ||||
| import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
|  | ||||
| /** | ||||
|  * An LDAP-specific implementation of AuthenticatedUser, associating a | ||||
|  * particular set of credentials with the LDAP authentication provider. | ||||
|  */ | ||||
| public class LDAPAuthenticatedUser 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; | ||||
|  | ||||
|     /** | ||||
|      * Name/value pairs to be applied as parameter tokens when connections | ||||
|      * are established using this AuthenticatedUser. | ||||
|      */ | ||||
|     private Map<String, String> tokens; | ||||
|  | ||||
|     /** | ||||
|      * The unique identifiers of all user groups which affect the permissions | ||||
|      * available to this user. | ||||
|      */ | ||||
|     private Set<String> effectiveGroups; | ||||
|      | ||||
|     /** | ||||
|      * The LDAP DN used to bind this user. | ||||
|      */ | ||||
|     private Dn bindDn; | ||||
|  | ||||
|     /** | ||||
|      * The configuration of the LDAP server that should be used for all queries | ||||
|      * related to this AuthenticatedUser. | ||||
|      */ | ||||
|     private ConnectedLDAPConfiguration config; | ||||
|      | ||||
|     /** | ||||
|      * Initializes this AuthenticatedUser with the given credentials, | ||||
|      * connection parameter tokens, and set of effective user groups. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server that should be used for all | ||||
|      *     queries related to this AuthenticatedUser. | ||||
|      * | ||||
|      * @param credentials | ||||
|      *     The credentials provided when this user was authenticated. | ||||
|      * | ||||
|      * @param tokens | ||||
|      *     A Map of all name/value pairs that should be applied as parameter | ||||
|      *     tokens when connections are established using the AuthenticatedUser. | ||||
|      * | ||||
|      * @param effectiveGroups | ||||
|      *     The unique identifiers of all user groups which affect the | ||||
|      *     permissions available to this user. | ||||
|      */ | ||||
|     public void init(UserLDAPConfiguration config, Credentials credentials, | ||||
|             Map<String, String> tokens, Set<String> effectiveGroups) { | ||||
|         this.config = config; | ||||
|         this.credentials = credentials; | ||||
|         this.tokens = Collections.unmodifiableMap(tokens); | ||||
|         this.effectiveGroups = effectiveGroups; | ||||
|         this.bindDn = config.getBindDN(); | ||||
|         setIdentifier(config.getGuacamoleUsername()); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Returns a Map of all name/value pairs that should be applied as | ||||
|      * parameter tokens when connections are established using this | ||||
|      * AuthenticatedUser. | ||||
|      * | ||||
|      * @return | ||||
|      *     A Map of all name/value pairs that should be applied as parameter | ||||
|      *     tokens when connections are established using this | ||||
|      *     AuthenticatedUser. | ||||
|      */ | ||||
|     public Map<String, String> getTokens() { | ||||
|         return tokens; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the LDAP DN used to bind this user. | ||||
|      *  | ||||
|      * @return  | ||||
|      *     The LDAP DN used to bind this user. | ||||
|      */ | ||||
|     public Dn getBindDn() { | ||||
|         return bindDn; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the configuration of the LDAP server that should be used for all | ||||
|      * queries related to this AuthenticatedUser. | ||||
|      * | ||||
|      * @return | ||||
|      *     The configuration of the LDAP server related to this | ||||
|      *     AuthenticatedUser. | ||||
|      */ | ||||
|     public ConnectedLDAPConfiguration getLDAPConfiguration() { | ||||
|         return config; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public AuthenticationProvider getAuthenticationProvider() { | ||||
|         return authProvider; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Credentials getCredentials() { | ||||
|         return credentials; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Set<String> getEffectiveUserGroups() { | ||||
|         return effectiveGroups; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void invalidate() { | ||||
|         config.close(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,196 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.user; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.util.Collections; | ||||
| import org.apache.guacamole.auth.ldap.connection.ConnectionService; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider; | ||||
| import org.apache.guacamole.auth.ldap.group.UserGroupService; | ||||
| import org.apache.guacamole.net.auth.AbstractUserContext; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
| import org.apache.guacamole.net.auth.Connection; | ||||
| import org.apache.guacamole.net.auth.ConnectionGroup; | ||||
| import org.apache.guacamole.net.auth.Directory; | ||||
| import org.apache.guacamole.net.auth.User; | ||||
| import org.apache.guacamole.net.auth.UserGroup; | ||||
| import org.apache.guacamole.net.auth.permission.ObjectPermissionSet; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleConnectionGroup; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleDirectory; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleUser; | ||||
|  | ||||
| /** | ||||
|  * An LDAP-specific implementation of UserContext which queries all Guacamole | ||||
|  * connections and users from the LDAP directory. | ||||
|  */ | ||||
| public class LDAPUserContext extends AbstractUserContext { | ||||
|  | ||||
|     /** | ||||
|      * 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; | ||||
|  | ||||
|     /** | ||||
|      * Service for retrieving user groups. | ||||
|      */ | ||||
|     @Inject | ||||
|     private UserGroupService userGroupService; | ||||
|  | ||||
|     /** | ||||
|      * 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 UserGroup objects accessible to the user | ||||
|      * associated with this UserContext. | ||||
|      */ | ||||
|     private Directory<UserGroup> userGroupDirectory; | ||||
|  | ||||
|     /** | ||||
|      * Directory containing all Connection objects accessible to the user | ||||
|      * associated with this UserContext. | ||||
|      */ | ||||
|     private Directory<Connection> connectionDirectory; | ||||
|  | ||||
|     /** | ||||
|      * Reference to the root connection group. | ||||
|      */ | ||||
|     private ConnectionGroup rootGroup; | ||||
|  | ||||
|     /** | ||||
|      * Initializes this UserContext using the provided AuthenticatedUser. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The AuthenticatedUser representing the user that authenticated. This | ||||
|      *     user will always have been authenticated via LDAP, as LDAP data is | ||||
|      *     not provided to non-LDAP users. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If associated data stored within the LDAP directory cannot be | ||||
|      *     queried due to an error. | ||||
|      */ | ||||
|     public void init(LDAPAuthenticatedUser user) throws GuacamoleException { | ||||
|  | ||||
|         // Query all accessible users | ||||
|         userDirectory = new SimpleDirectory<>( | ||||
|             userService.getUsers(user) | ||||
|         ); | ||||
|  | ||||
|         // Query all accessible user groups | ||||
|         userGroupDirectory = new SimpleDirectory<>( | ||||
|             userGroupService.getUserGroups(user) | ||||
|         ); | ||||
|  | ||||
|         // Query all accessible connections | ||||
|         connectionDirectory = new SimpleDirectory<>( | ||||
|             connectionService.getConnections(user) | ||||
|         ); | ||||
|  | ||||
|         // Root group contains only connections | ||||
|         rootGroup = new SimpleConnectionGroup( | ||||
|             LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP, | ||||
|             LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP, | ||||
|             connectionDirectory.getIdentifiers(), | ||||
|             Collections.<String>emptyList() | ||||
|         ); | ||||
|  | ||||
|         // Init self with basic permissions | ||||
|         self = new SimpleUser(user.getIdentifier()) { | ||||
|  | ||||
|             @Override | ||||
|             public ObjectPermissionSet getUserPermissions() throws GuacamoleException { | ||||
|                 return new SimpleObjectPermissionSet(userDirectory.getIdentifiers()); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public ObjectPermissionSet getUserGroupPermissions() throws GuacamoleException { | ||||
|                 return new SimpleObjectPermissionSet(userGroupDirectory.getIdentifiers()); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException { | ||||
|                 return new SimpleObjectPermissionSet(connectionDirectory.getIdentifiers()); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException { | ||||
|                 return new SimpleObjectPermissionSet(Collections.singleton(LDAPAuthenticationProvider.ROOT_CONNECTION_GROUP)); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public User self() { | ||||
|         return self; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public AuthenticationProvider getAuthenticationProvider() { | ||||
|         return authProvider; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<User> getUserDirectory() throws GuacamoleException { | ||||
|         return userDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<UserGroup> getUserGroupDirectory() throws GuacamoleException { | ||||
|         return userGroupDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Directory<Connection> getConnectionDirectory() | ||||
|             throws GuacamoleException { | ||||
|         return connectionDirectory; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ConnectionGroup getRootConnectionGroup() throws GuacamoleException { | ||||
|         return rootGroup; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,79 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.user; | ||||
|  | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
|  | ||||
| /** | ||||
|  * LDAPConfiguration implementation that represents the configuration and | ||||
|  * network connection of an LDAP that has been bound on behalf of a Guacamole | ||||
|  * user. | ||||
|  */ | ||||
| public class UserLDAPConfiguration extends ConnectedLDAPConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * The username of the associated Guacamole user. | ||||
|      */ | ||||
|     private final String username; | ||||
|      | ||||
|     /** | ||||
|      * Creates a new UserLDAPConfiguration that associates the given | ||||
|      * LDAPConfiguration of an LDAP server with the active network connection to | ||||
|      * that server, as well as the username of the Guacamole user on behalf of | ||||
|      * whom that connection was established. All functions inherited from the | ||||
|      * LDAPConfiguration interface are delegated to the given LDAPConfiguration. | ||||
|      * It is the responsibility of the caller to ensure the provided | ||||
|      * LdapNetworkConnection is closed after it is no longer needed. | ||||
|      * | ||||
|      * @param config | ||||
|      *      The LDAPConfiguration to wrap. | ||||
|      * | ||||
|      * @param username | ||||
|      *      The username of the associated Guacamole user. | ||||
|      * | ||||
|      * @param bindDn | ||||
|      *      The LDAP DN that was used to bind with the LDAP server to produce | ||||
|      *      the given LdapNetworkConnection. | ||||
|      * | ||||
|      * @param connection | ||||
|      *      The connection to the LDAP server represented by the given | ||||
|      *      configuration. | ||||
|      */ | ||||
|     public UserLDAPConfiguration(LDAPConfiguration config, | ||||
|             String username, Dn bindDn, LdapNetworkConnection connection) { | ||||
|         super(config, bindDn, connection); | ||||
|         this.username = username; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the username of the Guacamole user on behalf of whom the | ||||
|      * associated LDAP network connection was established. | ||||
|      * | ||||
|      * @return | ||||
|      *     The username of the associated Guacamole user. | ||||
|      */ | ||||
|     public String getGuacamoleUsername() { | ||||
|         return username; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,209 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you under the Apache License, Version 2.0 (the | ||||
|  * "License"); you may not use this file except in compliance | ||||
|  * with the License.  You may obtain a copy of the License at | ||||
|  * | ||||
|  *   http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, | ||||
|  * software distributed under the License is distributed on an | ||||
|  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||||
|  * KIND, either express or implied.  See the License for the | ||||
|  * specific language governing permissions and limitations | ||||
|  * under the License. | ||||
|  */ | ||||
|  | ||||
| package org.apache.guacamole.auth.ldap.user; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.directory.api.ldap.model.entry.Entry; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; | ||||
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; | ||||
| import org.apache.directory.api.ldap.model.name.Dn; | ||||
| import org.apache.directory.api.ldap.model.name.Rdn; | ||||
| import org.apache.directory.ldap.client.api.LdapNetworkConnection; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.ldap.ConnectedLDAPConfiguration; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPGuacamoleProperties; | ||||
| import org.apache.guacamole.auth.ldap.ObjectQueryService; | ||||
| import org.apache.guacamole.auth.ldap.conf.LDAPConfiguration; | ||||
| import org.apache.guacamole.net.auth.User; | ||||
| import org.apache.guacamole.net.auth.simple.SimpleUser; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for queries the users visible to a particular Guacamole user | ||||
|  * according to an LDAP directory. | ||||
|  */ | ||||
| public class UserService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(UserService.class); | ||||
|  | ||||
|     /** | ||||
|      * Service for executing LDAP queries. | ||||
|      */ | ||||
|     @Inject | ||||
|     private ObjectQueryService queryService; | ||||
|  | ||||
|     /** | ||||
|      * Returns all Guacamole users accessible to the given user. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The AuthenticatedUser object associated with the user who is | ||||
|      *     currently authenticated with Guacamole. | ||||
|      * | ||||
|      * @return | ||||
|      *     All users accessible to the user currently bound under the given | ||||
|      *     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(LDAPAuthenticatedUser user) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         ConnectedLDAPConfiguration config = user.getLDAPConfiguration(); | ||||
|          | ||||
|         // Retrieve all visible user objects | ||||
|         Collection<String> usernameAttrs = config.getUsernameAttributes(); | ||||
|         Collection<String> attributes = new HashSet<>(usernameAttrs); | ||||
|         attributes.addAll(config.getAttributes()); | ||||
|         List<Entry> results = queryService.search( | ||||
|             config, | ||||
|             config.getLDAPConnection(), | ||||
|             config.getUserBaseDN(), | ||||
|             config.getUserSearchFilter(), | ||||
|             usernameAttrs, | ||||
|             null, | ||||
|             attributes | ||||
|         ); | ||||
|  | ||||
|         // Convert retrieved users to map of identifier to Guacamole user object | ||||
|         return queryService.asMap(results, entry -> { | ||||
|  | ||||
|             // Get username from record | ||||
|             try { | ||||
|                 String username = queryService.getIdentifier(entry, attributes); | ||||
|                 if (username == null) { | ||||
|                     logger.warn("User \"{}\" is missing a username attribute " | ||||
|                             + "and will be ignored.", entry.getDn().toString()); | ||||
|                     return null; | ||||
|                 } | ||||
|                  | ||||
|                 return new SimpleUser(username); | ||||
|             } | ||||
|             catch (LdapInvalidAttributeValueException e) { | ||||
|                  | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a list of all DNs corresponding to the users having the given | ||||
|      * username. If multiple username attributes are defined, or if uniqueness | ||||
|      * is not enforced across the username attribute, it is possible that this | ||||
|      * will return multiple DNs. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param ldapConnection | ||||
|      *     The connection to the LDAP server to use when querying user DNs. | ||||
|      * | ||||
|      * @param username | ||||
|      *     The username of the user whose corresponding user account DNs are | ||||
|      *     to be retrieved. | ||||
|      * | ||||
|      * @return | ||||
|      *     A list of all DNs corresponding to the users having the given | ||||
|      *     username. If no such DNs exist, this list will be empty. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If an error occurs while querying the user DNs, or if the username | ||||
|      *     attribute property cannot be parsed within guacamole.properties. | ||||
|      */ | ||||
|     public List<Dn> getUserDNs(LDAPConfiguration config, LdapNetworkConnection ldapConnection, | ||||
|             String username) throws GuacamoleException { | ||||
|  | ||||
|         // Retrieve user objects having a matching username | ||||
|         List<Entry> results = queryService.search(config, ldapConnection, | ||||
|                 config.getUserBaseDN(), | ||||
|                 config.getUserSearchFilter(), | ||||
|                 config.getUsernameAttributes(), | ||||
|                 username, | ||||
|                 Collections.singletonList("dn")); | ||||
|  | ||||
|         // Build list of all DNs for retrieved users | ||||
|         List<Dn> userDNs = new ArrayList<>(results.size()); | ||||
|         results.forEach(entry -> userDNs.add(entry.getDn())); | ||||
|  | ||||
|         return userDNs; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Determines the DN which corresponds to the user having the given | ||||
|      * username. The DN will either be derived directly from the user base DN, | ||||
|      * or queried from the LDAP server, depending on how LDAP authentication | ||||
|      * has been configured. | ||||
|      * | ||||
|      * @param config | ||||
|      *     The configuration of the LDAP server being queried. | ||||
|      * | ||||
|      * @param username | ||||
|      *     The username of the user whose corresponding DN should be returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The DN which corresponds to the user having the given username. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If required properties are missing, and thus the user DN cannot be | ||||
|      *     determined. | ||||
|      */ | ||||
|     public Dn deriveUserDN(LDAPConfiguration config, String username) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Pull username attributes from properties | ||||
|         List<String> usernameAttributes = new ArrayList<>(config.getUsernameAttributes()); | ||||
|  | ||||
|         // We need exactly one base DN to derive the user DN | ||||
|         if (usernameAttributes.size() != 1) { | ||||
|             logger.warn(String.format("Cannot directly derive user DN when " | ||||
|                       + "multiple username attributes are specified. Please " | ||||
|                       + "define an LDAP search DN using the \"%s\" property " | ||||
|                       + "in your \"guacamole.properties\".", | ||||
|                       LDAPGuacamoleProperties.LDAP_SEARCH_BIND_DN.getName())); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Derive user DN from base DN | ||||
|         try { | ||||
|             return new Dn(new Rdn(usernameAttributes.get(0), username), | ||||
|                 config.getUserBaseDN()); | ||||
|         } | ||||
|         catch (LdapInvalidAttributeValueException | LdapInvalidDnException e) { | ||||
|             throw new GuacamoleServerException("Error trying to derive user DN.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| { | ||||
|  | ||||
|     "guacamoleVersion" : "1.6.0", | ||||
|  | ||||
|     "name"      : "LDAP Authentication", | ||||
|     "namespace" : "ldap", | ||||
|  | ||||
|     "authProviders" : [ | ||||
|         "org.apache.guacamole.auth.ldap.LDAPAuthenticationProvider" | ||||
|     ], | ||||
|  | ||||
|     "translations" : [ | ||||
|         "translations/de.json", | ||||
|         "translations/en.json", | ||||
|         "translations/zh.json" | ||||
|     ] | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| { | ||||
|  | ||||
|     "DATA_SOURCE_LDAP" : { | ||||
|         "NAME" : "LDAP" | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| { | ||||
|  | ||||
|     "DATA_SOURCE_LDAP" : { | ||||
|         "NAME" : "轻型目录访问协议" | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user