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