diff --git a/doc/licenses/jackson-2.12.2/dep-coordinates.txt b/doc/licenses/jackson-2.12.2/dep-coordinates.txt
index 6dcc7917e..3f73c2e2c 100644
--- a/doc/licenses/jackson-2.12.2/dep-coordinates.txt
+++ b/doc/licenses/jackson-2.12.2/dep-coordinates.txt
@@ -1,4 +1,5 @@
com.fasterxml.jackson.core:jackson-databind:jar:2.12.2
com.fasterxml.jackson.core:jackson-core:jar:2.12.2
com.fasterxml.jackson.core:jackson-annotations:jar:2.12.2
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:jar:2.12.2
com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.12.2
diff --git a/doc/licenses/snakeyaml-1.27/README b/doc/licenses/snakeyaml-1.27/README
new file mode 100644
index 000000000..3fcd837d6
--- /dev/null
+++ b/doc/licenses/snakeyaml-1.27/README
@@ -0,0 +1,8 @@
+SnakeYAML (https://bitbucket.org/asomov/snakeyaml/)
+---------------------------------------------------
+
+ Version: 1.27
+ From: 'Andrey Somov' (https://bitbucket.org/asomov/)
+ License(s):
+ Apache v2.0
+
diff --git a/doc/licenses/snakeyaml-1.27/dep-coordinates.txt b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt
new file mode 100644
index 000000000..d7cbad91a
--- /dev/null
+++ b/doc/licenses/snakeyaml-1.27/dep-coordinates.txt
@@ -0,0 +1 @@
+org.yaml:snakeyaml:jar:1.27
diff --git a/extensions/guacamole-auth-ldap/pom.xml b/extensions/guacamole-auth-ldap/pom.xml
index fa9d8a215..5aa8f967d 100644
--- a/extensions/guacamole-auth-ldap/pom.xml
+++ b/extensions/guacamole-auth-ldap/pom.xml
@@ -60,6 +60,16 @@
guice
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
index 42afdf510..f56f9e592 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java
@@ -196,7 +196,7 @@ public class AuthenticationProviderService {
String password) throws GuacamoleException {
// Get relevant LDAP configurations for user
- Collection configs = confService.getLDAPConfigurations(username);
+ Collection extends LDAPConfiguration> configs = confService.getLDAPConfigurations(username);
if (configs.isEmpty()) {
logger.info("User \"{}\" does not map to any defined LDAP configurations.", username);
return null;
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
index d84ac47ea..e8bafcaa6 100644
--- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/ConfigurationService.java
@@ -19,17 +19,41 @@
package org.apache.guacamole.auth.ldap.conf;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.environment.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Service for retrieving configuration information regarding LDAP servers.
*/
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());
+
+ /**
+ * 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 Guacamole server environment.
*/
@@ -54,8 +78,25 @@ public class ConfigurationService {
* If the configuration information of the LDAP servers related to the
* user having the given username cannot be retrieved due to an error.
*/
- public Collection getLDAPConfigurations(String username) throws GuacamoleException {
+ public Collection extends LDAPConfiguration> getLDAPConfigurations(String username) throws GuacamoleException {
+
+ // Read configuration from YAML, if available
+ File ldapServers = new File(environment.getGuacamoleHome(), LDAP_SERVERS_YML);
+ if (ldapServers.exists()) {
+ try {
+ logger.debug("Reading LDAP configuration from \"{}\"...", ldapServers);
+ return mapper.readValue(ldapServers, new TypeReference>() {});
+ }
+ catch (IOException e) {
+ logger.error("\"{}\" could not be read/parsed: {}", ldapServers, e.getMessage());
+ throw new GuacamoleServerException("Cannot read LDAP configuration from \"" + ldapServers + "\"", e);
+ }
+ }
+
+ // Use guacamole.properties if not using YAML
+ logger.debug("Reading LDAP configuration from guacamole.properties...");
return Collections.singletonList(new EnvironmentLDAPConfiguration(environment));
+
}
}
diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java
new file mode 100644
index 000000000..1676d8ac3
--- /dev/null
+++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java
@@ -0,0 +1,315 @@
+/*
+ * 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.JsonProperty;
+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 is annotated for deserialization by
+ * Jackson.
+ */
+public class JacksonLDAPConfiguration implements LDAPConfiguration {
+
+ /**
+ * 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")
+ private List usernameAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-base-dn")
+ private String userBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_CONFIG_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("config-base-dn")
+ private String configBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_BASE_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-base-dn")
+ private String groupBaseDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_NAME_ATTRIBUTES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-name-attribute")
+ private List groupNameAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_DN}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("search-bind-dn")
+ private String searchBindDn;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_SEARCH_BIND_PASSWORD}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("search-bind-password")
+ private String searchBindPassword;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_ENCRYPTION_METHOD}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("encryption-method")
+ private String encryptionMethod;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_SEARCH_RESULTS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("max-search-results")
+ private Integer maxSearchResults;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_DEREFERENCE_ALIASES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("dereference-aliases")
+ private String dereferenceAliases;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_FOLLOW_REFERRALS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("follow-referrals")
+ private Boolean followReferrals;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MAX_REFERRAL_HOPS}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("max-referral-hops")
+ private Integer maxReferralHops;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_SEARCH_FILTER}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-search-filter")
+ private String userSearchFilter;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_GROUP_SEARCH_FILTER}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("group-search-filter")
+ private String groupSearchFilter;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_OPERATION_TIMEOUT}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("operation-timeout")
+ private Integer operationTimeout;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_USER_ATTRIBUTES}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("user-attributes")
+ private List userAttributes;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("member-attribute")
+ private String memberAttribute;
+
+ /**
+ * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_MEMBER_ATTRIBUTE_TYPE}.
+ * If not set within the YAML, this will be null.
+ */
+ @JsonProperty("member-attribute-type")
+ private String memberAttributeType;
+
+ @Override
+ public String getServerHostname() {
+ return hostname != null ? hostname : "localhost";
+ }
+
+ @Override
+ public int getServerPort() throws GuacamoleException {
+ return port != null ? port : getEncryptionMethod().DEFAULT_PORT;
+ }
+
+ @Override
+ public List getUsernameAttributes() {
+ return usernameAttributes != null ? usernameAttributes : Collections.singletonList("uid");
+ }
+
+ @Override
+ public Dn getUserBaseDN() throws GuacamoleException {
+
+ Dn parsedDn = LDAPGuacamoleProperties.LDAP_USER_BASE_DN.parseValue(userBaseDn);
+ if (parsedDn == null)
+ throw new GuacamoleServerException("The \"user-base-dn\" property is required for all LDAP servers.");
+
+ return parsedDn;
+
+ }
+
+ @Override
+ public Dn getConfigurationBaseDN() throws GuacamoleException {
+ return LDAPGuacamoleProperties.LDAP_CONFIG_BASE_DN.parseValue(configBaseDn);
+ }
+
+ @Override
+ public List getGroupNameAttributes() throws GuacamoleException {
+ return groupNameAttributes != null ? groupNameAttributes : Collections.singletonList("cn");
+ }
+
+ @Override
+ public Dn getGroupBaseDN() throws GuacamoleException {
+ return LDAPGuacamoleProperties.LDAP_GROUP_BASE_DN.parseValue(groupBaseDn);
+ }
+
+ @Override
+ public String getSearchBindDN() throws GuacamoleException {
+ return searchBindDn;
+ }
+
+ @Override
+ public String getSearchBindPassword() throws GuacamoleException {
+ return searchBindPassword;
+ }
+
+ @Override
+ public EncryptionMethod getEncryptionMethod() throws GuacamoleException {
+
+ EncryptionMethod parsedMethod = LDAPGuacamoleProperties.LDAP_ENCRYPTION_METHOD.parseValue(encryptionMethod);
+ if (parsedMethod == null)
+ return EncryptionMethod.NONE;
+
+ return parsedMethod;
+
+ }
+
+ @Override
+ public int getMaxResults() throws GuacamoleException {
+ return maxSearchResults != null ? maxSearchResults : 1000;
+ }
+
+ @Override
+ public AliasDerefMode getDereferenceAliases() throws GuacamoleException {
+
+ AliasDerefMode parsedMode = LDAPGuacamoleProperties.LDAP_DEREFERENCE_ALIASES.parseValue(dereferenceAliases);
+ if (parsedMode == null)
+ return AliasDerefMode.NEVER_DEREF_ALIASES;
+
+ return parsedMode;
+
+ }
+
+ @Override
+ public boolean getFollowReferrals() throws GuacamoleException {
+ return followReferrals != null ? followReferrals : false;
+ }
+
+ @Override
+ public int getMaxReferralHops() throws GuacamoleException {
+ return maxReferralHops != null ? maxReferralHops : 5;
+ }
+
+ @Override
+ public ExprNode getUserSearchFilter() throws GuacamoleException {
+
+ ExprNode parsedFilter = LDAPGuacamoleProperties.LDAP_USER_SEARCH_FILTER.parseValue(userSearchFilter);
+ if (parsedFilter == null)
+ return new PresenceNode("objectClass");
+
+ return parsedFilter;
+
+ }
+
+ @Override
+ public ExprNode getGroupSearchFilter() throws GuacamoleException {
+
+ ExprNode parsedFilter = LDAPGuacamoleProperties.LDAP_GROUP_SEARCH_FILTER.parseValue(groupSearchFilter);
+ if (parsedFilter == null)
+ return new PresenceNode("objectClass");
+
+ return parsedFilter;
+
+ }
+
+ @Override
+ public int getOperationTimeout() throws GuacamoleException {
+ return operationTimeout != null ? operationTimeout : 30;
+ }
+
+ @Override
+ public List getAttributes() throws GuacamoleException {
+ return userAttributes != null ? userAttributes : Collections.emptyList();
+ }
+
+ @Override
+ public String getMemberAttribute() throws GuacamoleException {
+ return memberAttribute != null ? memberAttribute : "member";
+ }
+
+ @Override
+ public MemberAttributeType getMemberAttributeType()
+ throws GuacamoleException {
+
+ MemberAttributeType parsedType = LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE.parseValue(memberAttributeType);
+ if (parsedType == null)
+ return MemberAttributeType.DN;
+
+ return parsedType;
+
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 0480d5bf1..f133e2c18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -377,6 +377,11 @@
jackson-databind
${jackson.version}
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+ ${jackson.version}
+
com.fasterxml.jackson.module
jackson-module-jaxb-annotations