From 7b8dc36644be90f5ddb90e4d7835e062475421ea Mon Sep 17 00:00:00 2001 From: Ron Record Date: Sat, 5 Dec 2020 22:09:45 -0800 Subject: [PATCH 1/5] GUACAMOLE-793: validateTicket() returns the CASAuthenticatedUser instance rather than just a token so CAS Provider can return Group - like LDAP Provider --- .../cas/AuthenticationProviderService.java | 23 ++++---- .../auth/cas/conf/CASGuacamoleProperties.java | 24 +++++++++ .../auth/cas/conf/ConfigurationService.java | 36 +++++++++++++ .../cas/ticket/TicketValidationService.java | 54 ++++++++++++++++--- .../auth/cas/user/CASAuthenticatedUser.java | 16 +++++- 5 files changed, 130 insertions(+), 23 deletions(-) diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java index 9f171e8ac..6fb400264 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java @@ -20,9 +20,9 @@ package org.apache.guacamole.auth.cas; import com.google.inject.Inject; -import com.google.inject.Provider; import java.util.Arrays; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.form.Field; import org.apache.guacamole.GuacamoleException; @@ -34,6 +34,8 @@ import org.apache.guacamole.auth.cas.form.CASTicketField; import org.apache.guacamole.auth.cas.ticket.TicketValidationService; import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser; import org.apache.guacamole.language.TranslatableMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Service providing convenience functions for the CAS AuthenticationProvider @@ -41,6 +43,11 @@ import org.apache.guacamole.language.TranslatableMessage; */ public class AuthenticationProviderService { + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); + /** * Service for retrieving CAS configuration information. */ @@ -53,12 +60,6 @@ public class AuthenticationProviderService { @Inject private TicketValidationService ticketService; - /** - * Provider for AuthenticatedUser objects. - */ - @Inject - private Provider authenticatedUserProvider; - /** * Returns an AuthenticatedUser representing the user authenticated by the * given credentials. @@ -82,13 +83,7 @@ public class AuthenticationProviderService { if (request != null) { String ticket = request.getParameter(CASTicketField.PARAMETER_NAME); if (ticket != null) { - Map tokens = ticketService.validateTicket(ticket, credentials); - String username = credentials.getUsername(); - if (username != null) { - CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(username, credentials, tokens); - return authenticatedUser; - } + return ticketService.validateTicket(ticket, credentials); } } diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java index 2ee42dba9..77c7667e5 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.cas.conf; import org.apache.guacamole.properties.URIGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; /** * Provides properties required for use of the CAS authentication provider. @@ -68,5 +69,28 @@ public class CASGuacamoleProperties { public String getName() { return "cas-clearpass-key"; } }; + + /** + * The attribute used for group membership + * example: memberOf (case sensitive) + */ + public static final StringGuacamoleProperty CAS_GROUP_ATTRIBUTE = + new StringGuacamoleProperty() { + @Override + public String getName() { return "cas-group-attribute"; } + + }; + + /** + * The name of the attribute used for group membership, such as "memberOf". + * This attribute is case sensitive. + */ + public static final StringGuacamoleProperty CAS_GROUP_DN_FORMAT = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "cas-group-dn-format"; } + + }; } diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java index 680f17057..1285c619c 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java @@ -85,4 +85,40 @@ public class ConfigurationService { return environment.getProperty(CASGuacamoleProperties.CAS_CLEARPASS_KEY); } + /** + * Returns the attribute used to determine group memberships + * in CAS, or null if not defined. + * + * @return + * The attribute name used to determine group memberships in CAS, + * null if not defined. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getGroupAttribute() throws GuacamoleException { + return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_ATTRIBUTE); + } + + /** + * Returns the full DN specification used to format group DN's in CAS, + * or null if not defined. + * If CAS is backed by LDAP, it will return an LDAP DN, such as + * CN=foo,OU=bar,DC=example,DC=com. This DN specification may be set to + * CN=%s,OU=bar,DC=example,DC=com and given the example above, would result + * in a group called "foo". CAS backed by something other than LDAP would + * likely not need this. + * + * @return + * The DN format specification. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getGroupDnFormat() throws GuacamoleException { + return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_DN_FORMAT); + } + + + } diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java index fce476040..24c3e24a8 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.cas.ticket; import com.google.common.io.BaseEncoding; import com.google.inject.Inject; +import com.google.inject.Provider; import java.net.URI; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -33,10 +34,15 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.auth.cas.conf.ConfigurationService; +import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.token.TokenName; import org.jasig.cas.client.authentication.AttributePrincipal; @@ -68,6 +74,12 @@ public class TicketValidationService { @Inject private ConfigurationService confService; + /** + * Provider for AuthenticatedUser objects. + */ + @Inject + private Provider authenticatedUserProvider; + /** * Validates and parses the given ID ticket, returning a map of all * available tokens for the given user based on attributes provided by the @@ -81,14 +93,13 @@ public class TicketValidationService { * password values in. * * @return - * A Map all of tokens for the user parsed from attributes returned - * by the CAS server. + * A CASAuthenticatedUser instance containing the ticket data returned by the CAS server. * * @throws GuacamoleException * If the ID ticket is not valid or guacamole.properties could * not be parsed. */ - public Map validateTicket(String ticket, + public CASAuthenticatedUser validateTicket(String ticket, Credentials credentials) throws GuacamoleException { // Retrieve the configured CAS URL, establish a ticket validator, @@ -100,6 +111,7 @@ public class TicketValidationService { validator.setEncoding("UTF-8"); try { Map tokens = new HashMap<>(); + Set effectiveGroups = new HashSet<>(); URI confRedirectURI = confService.getRedirectURI(); Assertion a = validator.validate(ticket, confRedirectURI.toString()); AttributePrincipal principal = a.getPrincipal(); @@ -122,16 +134,44 @@ public class TicketValidationService { } // Convert remaining attributes that have values to Strings + String groupAttribute = confService.getGroupAttribute(); + // Use cas-member-attribute to retrieve and set group memberships + String groupDnFormat = confService.getGroupDnFormat(); + String groupTemplate = ""; + if (groupDnFormat != null) { + // if CAS is backended to LDAP, groups come in as RFC4514 DN + // syntax. If cas-group-dn-format is set, this strips an + // entry such as "CN=Foo,OU=Bar,DC=example,DC=com" to "Foo" + groupTemplate = groupDnFormat.replace("%s","([A-Za-z0-9_\\(\\)\\-\\.\\s+]+)"); + // the underlying parser aggregates all instances of the same + // attribute, so we need to be able to parse them out + groupTemplate=groupTemplate+",*\\s*"; + } + else { + groupTemplate = "([A-Za-z0-9_\\(\\)\\-\\.\\s+]+,*\\s*)"; + } + Pattern pattern = Pattern.compile(groupTemplate); + for (Entry attr : ticketAttrs.entrySet()) { String tokenName = TokenName.canonicalize(attr.getKey(), CAS_ATTRIBUTE_TOKEN_PREFIX); Object value = attr.getValue(); - if (value != null) - tokens.put(tokenName, value.toString()); + if (value != null) { + String attrValue = value.toString(); + tokens.put(tokenName, attrValue); + if (attr.getKey().equals(groupAttribute)) { + Matcher matcher = + pattern.matcher(attrValue.substring(1,attrValue.length()-1)); + while (matcher.find()) { + effectiveGroups.add(matcher.group(1)); + } + } + } } - return tokens; - + CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(username, credentials, tokens, effectiveGroups); + return authenticatedUser; } catch (TicketValidationException e) { throw new GuacamoleException("Ticket validation failed.", e); diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/user/CASAuthenticatedUser.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/user/CASAuthenticatedUser.java index 1b3a948cc..b79344eb8 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/user/CASAuthenticatedUser.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/user/CASAuthenticatedUser.java @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.cas.user; import com.google.inject.Inject; import java.util.Collections; import java.util.Map; +import java.util.Set; import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; import org.apache.guacamole.net.auth.AuthenticationProvider; import org.apache.guacamole.net.auth.Credentials; @@ -50,6 +51,11 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { */ private Map tokens; + /** + * The unique identifiers of all user groups which this user is a member of. + */ + private Set effectiveGroups; + /** * Initializes this AuthenticatedUser using the given username and * credentials, and an empty map of parameter tokens. @@ -61,7 +67,7 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { * The credentials provided when this user was authenticated. */ public void init(String username, Credentials credentials) { - this.init(username, credentials, Collections.emptyMap()); + this.init(username, credentials, Collections.emptyMap(), Collections.emptySet()); } /** @@ -79,9 +85,10 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { * as tokens when connections are established with this user. */ public void init(String username, Credentials credentials, - Map tokens) { + Map tokens, Set effectiveGroups) { this.credentials = credentials; this.tokens = Collections.unmodifiableMap(tokens); + this.effectiveGroups = effectiveGroups; setIdentifier(username.toLowerCase()); } @@ -107,4 +114,9 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { return credentials; } + @Override + public Set getEffectiveUserGroups() { + return effectiveGroups; + } + } From 749e53b9c3f26fc38b79f67d6b912e7806342c08 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Dec 2020 02:02:06 -0800 Subject: [PATCH 2/5] GUACAMOLE-793: Add abstract mechanism for parsing CAS group names. --- extensions/guacamole-auth-cas/pom.xml | 20 +++ .../guacamole/auth/cas/group/GroupFormat.java | 41 +++++ .../guacamole/auth/cas/group/GroupParser.java | 44 +++++ .../auth/cas/group/LDAPGroupParser.java | 106 +++++++++++ .../auth/cas/group/PlainGroupParser.java | 32 ++++ .../auth/cas/group/LDAPGroupParserTest.java | 164 ++++++++++++++++++ 6 files changed, 407 insertions(+) create mode 100644 extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupFormat.java create mode 100644 extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupParser.java create mode 100644 extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/LDAPGroupParser.java create mode 100644 extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/PlainGroupParser.java create mode 100644 extensions/guacamole-auth-cas/src/test/java/org/apache/guacamole/auth/cas/group/LDAPGroupParserTest.java diff --git a/extensions/guacamole-auth-cas/pom.xml b/extensions/guacamole-auth-cas/pom.xml index f4b30e4b4..2ffe8182e 100644 --- a/extensions/guacamole-auth-cas/pom.xml +++ b/extensions/guacamole-auth-cas/pom.xml @@ -183,6 +183,26 @@ provided + + + org.junit.jupiter + junit-jupiter-api + 5.6.0 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.0 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.6.0 + test + + diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupFormat.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupFormat.java new file mode 100644 index 000000000..4e315daf4 --- /dev/null +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupFormat.java @@ -0,0 +1,41 @@ +/* + * 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.cas.group; + +import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; + +/** + * Possible formats of group names received from CAS. + */ +public enum GroupFormat { + + /** + * Simple, plain-text group names. + */ + @PropertyValue("plain") + PLAIN, + + /** + * Group names formatted as LDAP DNs. + */ + @PropertyValue("ldap") + LDAP + +} diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupParser.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupParser.java new file mode 100644 index 000000000..5c31d5db4 --- /dev/null +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/GroupParser.java @@ -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. + */ + +package org.apache.guacamole.auth.cas.group; + +/** + * Parser which converts the group names returned by CAS into names usable by + * Guacamole. The format of a CAS group name may vary by the underlying + * authentication backend. For example, a CAS deployment backed by LDAP may + * provide group names as LDAP DNs, which must be transformed into normal group + * names to be usable within Guacamole. + * + * @see LDAPGroupParser + */ +public interface GroupParser { + + /** + * Parses the given CAS group name into a group name usable by Guacamole. + * + * @param casGroup + * The group name retrieved from CAS. + * + * @return + * A group name usable by Guacamole, or null if the group is not valid. + */ + String parse(String casGroup); + +} diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/LDAPGroupParser.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/LDAPGroupParser.java new file mode 100644 index 000000000..9a33ef738 --- /dev/null +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/LDAPGroupParser.java @@ -0,0 +1,106 @@ +/* + * 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.cas.group; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * GroupParser that converts group names from LDAP DNs into normal group names, + * using the last (leftmost) attribute of the DN as the name. Groups may + * optionally be restricted to only those beneath a specific base DN, or only + * those using a specific attribute as their last (leftmost) attribute. + */ +public class LDAPGroupParser implements GroupParser { + + /** + * Logger for this class. + */ + private static final Logger logger = LoggerFactory.getLogger(LDAPGroupParser.class); + + /** + * The LDAP attribute to require for all accepted group names. If null, any + * LDAP attribute will be allowed. + */ + private final String nameAttribute; + + /** + * The base DN to require for all accepted group names. If null, ancestor + * tree structure will not be considered in accepting/rejecting a group. + */ + private final LdapName baseDn; + + /** + * Creates a new LDAPGroupParser which applies the given restrictions on + * any provided group names. + * + * @param nameAttribute + * The LDAP attribute to require for all accepted group names. This + * restriction applies to the last (leftmost) attribute only, which is + * always used to determine the name of the group. If null, any LDAP + * attribute will be allowed in the last (leftmost) position. + * + * @param baseDn + * The base DN to require for all accepted group names. If null, + * ancestor tree structure will not be considered in + * accepting/rejecting a group. + */ + public LDAPGroupParser(String nameAttribute, LdapName baseDn) { + this.nameAttribute = nameAttribute; + this.baseDn = baseDn; + } + + @Override + public String parse(String casGroup) { + + // Reject null/empty group names + if (casGroup == null || casGroup.isEmpty()) + return null; + + // Parse group as an LDAP DN + LdapName group; + try { + group = new LdapName(casGroup); + } + catch (InvalidNameException e) { + logger.debug("CAS group \"{}\" has been rejected as it is not a " + + "valid LDAP DN.", casGroup, e); + return null; + } + + // Reject any group that is not beneath the base DN + if (baseDn != null && !group.startsWith(baseDn)) + return null; + + // If a specific name attribute is defined, restrict to groups that + // use that attribute to distinguish themselves + Rdn last = group.getRdn(group.size() - 1); + if (nameAttribute != null && !nameAttribute.equalsIgnoreCase(last.getType())) + return null; + + // The group name is the string value of the final attribute in the DN + return last.getValue().toString(); + + } + +} diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/PlainGroupParser.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/PlainGroupParser.java new file mode 100644 index 000000000..04c6c0588 --- /dev/null +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/group/PlainGroupParser.java @@ -0,0 +1,32 @@ +/* + * 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.cas.group; + +/** + * GroupParser which simply passes through all CAS group names untouched. + */ +public class PlainGroupParser implements GroupParser { + + @Override + public String parse(String casGroup) { + return casGroup; + } + +} diff --git a/extensions/guacamole-auth-cas/src/test/java/org/apache/guacamole/auth/cas/group/LDAPGroupParserTest.java b/extensions/guacamole-auth-cas/src/test/java/org/apache/guacamole/auth/cas/group/LDAPGroupParserTest.java new file mode 100644 index 000000000..ffff0a750 --- /dev/null +++ b/extensions/guacamole-auth-cas/src/test/java/org/apache/guacamole/auth/cas/group/LDAPGroupParserTest.java @@ -0,0 +1,164 @@ +/* + * 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.cas.group; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +/** + * Test which confirms that the LDAPGroupParser implementation of GroupParser + * parses CAS groups correctly. + */ +public class LDAPGroupParserTest { + + /** + * LdapName instance representing the LDAP DN: "dc=example,dc=net". + */ + private final LdapName exampleBaseDn; + + /** + * Creates a new LDAPGroupParserTest that verifies the functionality of + * LDAPGroupParser. + * + * @throws InvalidNameException + * If the static string LDAP DN of any test instance of LdapName is + * unexpectedly invalid. + */ + public LDAPGroupParserTest() throws InvalidNameException { + exampleBaseDn = new LdapName("dc=example,dc=net"); + } + + /** + * Verifies that LDAPGroupParser correctly parses LDAP-based CAS groups + * when no restrictions are enforced on LDAP attributes or the base DN. + */ + @Test + public void testParseRestrictNothing() { + + GroupParser parser = new LDAPGroupParser(null, null); + + // null input should be rejected as null + assertNull(parser.parse(null)); + + // Invalid DNs should be rejected as null + assertNull(parser.parse("")); + assertNull(parser.parse("foo")); + + // Valid DNs should be accepted + assertEquals("bar", parser.parse("foo=bar")); + assertEquals("baz", parser.parse("CN=baz,dc=example,dc=com")); + assertEquals("baz", parser.parse("ou=baz,dc=example,dc=net")); + assertEquals("foo", parser.parse("ou=foo,cn=baz,dc=example,dc=net")); + assertEquals("foo", parser.parse("cn=foo,DC=example,dc=net")); + assertEquals("bar", parser.parse("CN=bar,OU=groups,dc=example,Dc=net")); + + } + + /** + * Verifies that LDAPGroupParser correctly parses LDAP-based CAS groups + * when restrictions are enforced on LDAP attributes only. + */ + @Test + public void testParseRestrictAttribute() { + + GroupParser parser = new LDAPGroupParser("cn", null); + + // null input should be rejected as null + assertNull(parser.parse(null)); + + // Invalid DNs should be rejected as null + assertNull(parser.parse("")); + assertNull(parser.parse("foo")); + + // Valid DNs not using the correct attribute should be rejected as null + assertNull(parser.parse("foo=bar")); + assertNull(parser.parse("ou=baz,dc=example,dc=com")); + assertNull(parser.parse("ou=foo,cn=baz,dc=example,dc=com")); + + // Valid DNs using the correct attribute should be accepted + assertEquals("foo", parser.parse("cn=foo,DC=example,dc=net")); + assertEquals("bar", parser.parse("CN=bar,OU=groups,dc=example,Dc=net")); + assertEquals("baz", parser.parse("CN=baz,dc=example,dc=com")); + + } + + /** + * Verifies that LDAPGroupParser correctly parses LDAP-based CAS groups + * when restrictions are enforced on the LDAP base DN only. + */ + @Test + public void testParseRestrictBaseDN() { + + GroupParser parser = new LDAPGroupParser(null, exampleBaseDn); + + // null input should be rejected as null + assertNull(parser.parse(null)); + + // Invalid DNs should be rejected as null + assertNull(parser.parse("")); + assertNull(parser.parse("foo")); + + // Valid DNs outside the base DN should be rejected as null + assertNull(parser.parse("foo=bar")); + assertNull(parser.parse("CN=baz,dc=example,dc=com")); + + // Valid DNs beneath the base DN should be accepted + assertEquals("foo", parser.parse("cn=foo,DC=example,dc=net")); + assertEquals("bar", parser.parse("CN=bar,OU=groups,dc=example,Dc=net")); + assertEquals("baz", parser.parse("ou=baz,dc=example,dc=net")); + assertEquals("foo", parser.parse("ou=foo,cn=baz,dc=example,dc=net")); + + } + + /** + * Verifies that LDAPGroupParser correctly parses LDAP-based CAS groups + * when restrictions are enforced on both LDAP attributes and the base DN. + */ + @Test + public void testParseRestrictAll() { + + GroupParser parser = new LDAPGroupParser("cn", exampleBaseDn); + + // null input should be rejected as null + assertNull(parser.parse(null)); + + // Invalid DNs should be rejected as null + assertNull(parser.parse("")); + assertNull(parser.parse("foo")); + + // Valid DNs outside the base DN should be rejected as null + assertNull(parser.parse("foo=bar")); + assertNull(parser.parse("CN=baz,dc=example,dc=com")); + + // Valid DNs beneath the base DN but not using the correct attribute + // should be rejected as null + assertNull(parser.parse("ou=baz,dc=example,dc=net")); + assertNull(parser.parse("ou=foo,cn=baz,dc=example,dc=net")); + + // Valid DNs beneath the base DN and using the correct attribute should + // be accepted + assertEquals("foo", parser.parse("cn=foo,DC=example,dc=net")); + assertEquals("bar", parser.parse("CN=bar,OU=groups,dc=example,Dc=net")); + + } + +} From 1303dabbb1353bd2049b5e8d36c54ed76ca8b33b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Dec 2020 02:27:20 -0800 Subject: [PATCH 3/5] GUACAMOLE-793: Refactor CAS group parsing to leverage LDAP-aware abstractions and parameters. --- .../auth/cas/conf/CASGuacamoleProperties.java | 37 +++- .../auth/cas/conf/ConfigurationService.java | 92 ++++++++-- .../cas/conf/LdapNameGuacamoleProperty.java | 49 ++++++ .../cas/ticket/TicketValidationService.java | 161 ++++++++++-------- 4 files changed, 252 insertions(+), 87 deletions(-) create mode 100644 extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/LdapNameGuacamoleProperty.java diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java index 77c7667e5..7bb363f9c 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java @@ -19,6 +19,8 @@ package org.apache.guacamole.auth.cas.conf; +import org.apache.guacamole.auth.cas.group.GroupFormat; +import org.apache.guacamole.properties.EnumGuacamoleProperty; import org.apache.guacamole.properties.URIGuacamoleProperty; import org.apache.guacamole.properties.StringGuacamoleProperty; @@ -71,8 +73,8 @@ public class CASGuacamoleProperties { }; /** - * The attribute used for group membership - * example: memberOf (case sensitive) + * The name of the CAS attribute used for group membership, such as + * "memberOf". This attribute is case sensitive. */ public static final StringGuacamoleProperty CAS_GROUP_ATTRIBUTE = new StringGuacamoleProperty() { @@ -83,14 +85,37 @@ public class CASGuacamoleProperties { }; /** - * The name of the attribute used for group membership, such as "memberOf". - * This attribute is case sensitive. + * The format used by CAS to represent group names. Possible formats are + * "plain" (simple text names) or "ldap" (fully-qualified LDAP DNs). */ - public static final StringGuacamoleProperty CAS_GROUP_DN_FORMAT = + public static final EnumGuacamoleProperty CAS_GROUP_FORMAT = + new EnumGuacamoleProperty(GroupFormat.class) { + + @Override + public String getName() { return "cas-group-format"; } + + }; + + /** + * The LDAP base DN to require for all CAS groups. + */ + public static final LdapNameGuacamoleProperty CAS_GROUP_LDAP_BASE_DN = + new LdapNameGuacamoleProperty() { + + @Override + public String getName() { return "cas-group-ldap-base-dn"; } + + }; + + /** + * The LDAP attribute to require for the names of CAS groups. + */ + public static final StringGuacamoleProperty CAS_GROUP_LDAP_ATTRIBUTE = new StringGuacamoleProperty() { @Override - public String getName() { return "cas-group-dn-format"; } + public String getName() { return "cas-group-ldap-attribute"; } }; + } diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java index 1285c619c..ce5edd838 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java @@ -22,8 +22,14 @@ package org.apache.guacamole.auth.cas.conf; import com.google.inject.Inject; import java.net.URI; import java.security.PrivateKey; +import javax.naming.ldap.LdapName; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.auth.cas.group.GroupFormat; import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.auth.cas.group.GroupParser; +import org.apache.guacamole.auth.cas.group.LDAPGroupParser; +import org.apache.guacamole.auth.cas.group.PlainGroupParser; /** * Service for retrieving configuration information regarding the CAS service. @@ -86,8 +92,9 @@ public class ConfigurationService { } /** - * Returns the attribute used to determine group memberships - * in CAS, or null if not defined. + * Returns the CAS attribute that should be used to determine group + * memberships in CAS, such as "memberOf". If no attribute has been + * specified, null is returned. * * @return * The attribute name used to determine group memberships in CAS, @@ -101,24 +108,85 @@ public class ConfigurationService { } /** - * Returns the full DN specification used to format group DN's in CAS, - * or null if not defined. - * If CAS is backed by LDAP, it will return an LDAP DN, such as - * CN=foo,OU=bar,DC=example,DC=com. This DN specification may be set to - * CN=%s,OU=bar,DC=example,DC=com and given the example above, would result - * in a group called "foo". CAS backed by something other than LDAP would - * likely not need this. + * Returns the format that CAS is expected to use for its group names, such + * as {@link GroupFormat#PLAIN} (simple plain-text names) or + * {@link GroupFormat#LDAP} (fully-qualified LDAP DNs). If not specified, + * PLAIN is used by default. * * @return - * The DN format specification. + * The format that CAS is expected to use for its group names. + * + * @throws GuacamoleException + * If the format specified within guacamole.properties is not valid. + */ + public GroupFormat getGroupFormat() throws GuacamoleException { + return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_FORMAT, GroupFormat.PLAIN); + } + + /** + * Returns the base DN that all LDAP-formatted CAS groups must reside + * beneath. Any groups that are not beneath this base DN should be ignored. + * If no such base DN is provided, the tree structure of the ancestors of + * LDAP-formatted CAS groups should not be considered. + * + * @return + * The base DN that all LDAP-formatted CAS groups must reside beneath, + * or null if the tree structure of the ancestors of LDAP-formatted + * CAS groups should not be considered. + * + * @throws GuacamoleException + * If the provided base DN is not a valid LDAP DN. + */ + public LdapName getGroupLDAPBaseDN() throws GuacamoleException { + return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_LDAP_BASE_DN); + } + + /** + * Returns the LDAP attribute that should be required for all LDAP-formatted + * CAS groups. Any groups that do not use this attribute as the last + * (leftmost) attribute of their DN should be ignored. If no such LDAP + * attribute is provided, the last (leftmost) attribute should still be + * used to determine the group name, but the specific attribute involved + * should not be considered. + * + * @return + * The LDAP attribute that should be required for all LDAP-formatted + * CAS groups, or null if any attribute should be allowed. * * @throws GuacamoleException * If guacamole.properties cannot be parsed. */ - public String getGroupDnFormat() throws GuacamoleException { - return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_DN_FORMAT); + public String getGroupLDAPAttribute() throws GuacamoleException { + return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_LDAP_ATTRIBUTE); } + /** + * Returns a GroupParser instance that can be used to parse CAS group + * names. The parser returned will take into account the configured CAS + * group format, as well as any configured LDAP-specific restrictions. + * + * @return + * A GroupParser instance that can be used to parse CAS group names as + * configured in guacamole.properties. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public GroupParser getGroupParser() throws GuacamoleException { + switch (getGroupFormat()) { + // Simple, plain-text groups + case PLAIN: + return new PlainGroupParser(); + + // LDAP DNs + case LDAP: + return new LDAPGroupParser(getGroupLDAPAttribute(), getGroupLDAPBaseDN()); + + default: + throw new GuacamoleServerException("Unsupported CAS group format: " + getGroupFormat()); + + } + } } diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/LdapNameGuacamoleProperty.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/LdapNameGuacamoleProperty.java new file mode 100644 index 000000000..5469376bc --- /dev/null +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/LdapNameGuacamoleProperty.java @@ -0,0 +1,49 @@ +/* + * 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.cas.conf; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import org.apache.guacamole.properties.GuacamoleProperty; +import org.apache.guacamole.GuacamoleServerException; + +/** + * A GuacamoleProperty whose value is an LDAP DN. + */ +public abstract class LdapNameGuacamoleProperty implements GuacamoleProperty { + + @Override + public LdapName parseValue(String value) throws GuacamoleServerException { + + // Consider null/empty values to be empty + if (value == null || value.isEmpty()) + return null; + + // Parse provided value as an LDAP DN + try { + return new LdapName(value); + } + catch (InvalidNameException e) { + throw new GuacamoleServerException("Invalid LDAP distinguished name.", e); + } + + } + +} diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java index 24c3e24a8..17ef92342 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java @@ -31,13 +31,12 @@ import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.HashSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; import org.apache.guacamole.GuacamoleServerException; @@ -80,6 +79,40 @@ public class TicketValidationService { @Inject private Provider authenticatedUserProvider; + /** + * Converts the given CAS attribute value object (whose type is variable) + * to a Set of String values. If the value is already a Collection of some + * kind, its values are converted to Strings and returned as the members of + * the Set. If the value is not already a Collection, it is assumed to be a + * single value, converted to a String, and used as the sole member of the + * set. + * + * @param obj + * The CAS attribute value to convert to a Set of Strings. + * + * @return + * A Set of all String values contained within the given CAS attribute + * value. + */ + private Set toStringSet(Object obj) { + + // Consider null to represent no provided values + if (obj == null) + return Collections.emptySet(); + + // If the provided object is already a Collection, produce a Collection + // where we know for certain that all values are Strings + if (obj instanceof Collection) { + return ((Collection) obj).stream() + .map(Object::toString) + .collect(Collectors.toSet()); + } + + // Otherwise, assume we have only a single value + return Collections.singleton(obj.toString()); + + } + /** * Validates and parses the given ID ticket, returning a map of all * available tokens for the given user based on attributes provided by the @@ -102,81 +135,71 @@ public class TicketValidationService { public CASAuthenticatedUser validateTicket(String ticket, Credentials credentials) throws GuacamoleException { - // Retrieve the configured CAS URL, establish a ticket validator, - // and then attempt to validate the supplied ticket. If that succeeds, - // grab the principal returned by the validator. + // Create a ticket validator that uses the configured CAS URL URI casServerUrl = confService.getAuthorizationEndpoint(); Cas20ProxyTicketValidator validator = new Cas20ProxyTicketValidator(casServerUrl.toString()); validator.setAcceptAnyProxy(true); validator.setEncoding("UTF-8"); + + // Attempt to validate the supplied ticket + Assertion assertion; try { - Map tokens = new HashMap<>(); - Set effectiveGroups = new HashSet<>(); URI confRedirectURI = confService.getRedirectURI(); - Assertion a = validator.validate(ticket, confRedirectURI.toString()); - AttributePrincipal principal = a.getPrincipal(); - Map ticketAttrs = - new HashMap<>(principal.getAttributes()); - - // Retrieve username and set the credentials. - String username = principal.getName(); - if (username == null) - throw new GuacamoleSecurityException("No username provided by CAS."); - - credentials.setUsername(username); - - // Retrieve password, attempt decryption, and set credentials. - Object credObj = ticketAttrs.remove("credential"); - if (credObj != null) { - String clearPass = decryptPassword(credObj.toString()); - if (clearPass != null && !clearPass.isEmpty()) - credentials.setPassword(clearPass); - } - - // Convert remaining attributes that have values to Strings - String groupAttribute = confService.getGroupAttribute(); - // Use cas-member-attribute to retrieve and set group memberships - String groupDnFormat = confService.getGroupDnFormat(); - String groupTemplate = ""; - if (groupDnFormat != null) { - // if CAS is backended to LDAP, groups come in as RFC4514 DN - // syntax. If cas-group-dn-format is set, this strips an - // entry such as "CN=Foo,OU=Bar,DC=example,DC=com" to "Foo" - groupTemplate = groupDnFormat.replace("%s","([A-Za-z0-9_\\(\\)\\-\\.\\s+]+)"); - // the underlying parser aggregates all instances of the same - // attribute, so we need to be able to parse them out - groupTemplate=groupTemplate+",*\\s*"; - } - else { - groupTemplate = "([A-Za-z0-9_\\(\\)\\-\\.\\s+]+,*\\s*)"; - } - Pattern pattern = Pattern.compile(groupTemplate); - - for (Entry attr : ticketAttrs.entrySet()) { - String tokenName = TokenName.canonicalize(attr.getKey(), - CAS_ATTRIBUTE_TOKEN_PREFIX); - Object value = attr.getValue(); - if (value != null) { - String attrValue = value.toString(); - tokens.put(tokenName, attrValue); - if (attr.getKey().equals(groupAttribute)) { - Matcher matcher = - pattern.matcher(attrValue.substring(1,attrValue.length()-1)); - while (matcher.find()) { - effectiveGroups.add(matcher.group(1)); - } - } - } - } - - CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(username, credentials, tokens, effectiveGroups); - return authenticatedUser; - } + assertion = validator.validate(ticket, confRedirectURI.toString()); + } catch (TicketValidationException e) { throw new GuacamoleException("Ticket validation failed.", e); } + // Pull user principal and associated attributes + AttributePrincipal principal = assertion.getPrincipal(); + Map ticketAttrs = new HashMap<>(principal.getAttributes()); + + // Retrieve user identity from principal + String username = principal.getName(); + if (username == null) + throw new GuacamoleSecurityException("No username provided by CAS."); + + // Update credentials with username provided by CAS for sake of + // ${GUAC_USERNAME} token + credentials.setUsername(username); + + // Retrieve password, attempt decryption, and set credentials. + Object credObj = ticketAttrs.remove("credential"); + if (credObj != null) { + String clearPass = decryptPassword(credObj.toString()); + if (clearPass != null && !clearPass.isEmpty()) + credentials.setPassword(clearPass); + } + + Set effectiveGroups; + + // Parse effective groups from principal attributes if a specific + // group attribute has been configured + String groupAttribute = confService.getGroupAttribute(); + if (groupAttribute != null) { + effectiveGroups = toStringSet(ticketAttrs.get(groupAttribute)).stream() + .map(confService.getGroupParser()::parse) + .collect(Collectors.toSet()); + } + + // Otherwise, assume no effective groups + else + effectiveGroups = Collections.emptySet(); + + // Convert remaining attributes that have values to Strings + Map tokens = new HashMap<>(ticketAttrs.size()); + ticketAttrs.forEach((key, value) -> { + if (value != null) { + String tokenName = TokenName.canonicalize(key, CAS_ATTRIBUTE_TOKEN_PREFIX); + tokens.put(tokenName, value.toString()); + } + }); + + CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(username, credentials, tokens, effectiveGroups); + return authenticatedUser; + } /** From 1da9b7dc13046b571f897e47afd9301acda4a6db Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Dec 2020 13:46:00 -0800 Subject: [PATCH 4/5] GUACAMOLE-793: Remove unnecessary/unused Logger instance. --- .../auth/cas/AuthenticationProviderService.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java index 6fb400264..8150f97b3 100644 --- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java @@ -21,8 +21,6 @@ package org.apache.guacamole.auth.cas; import com.google.inject.Inject; import java.util.Arrays; -import java.util.Map; -import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.form.Field; import org.apache.guacamole.GuacamoleException; @@ -34,8 +32,6 @@ import org.apache.guacamole.auth.cas.form.CASTicketField; import org.apache.guacamole.auth.cas.ticket.TicketValidationService; import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser; import org.apache.guacamole.language.TranslatableMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Service providing convenience functions for the CAS AuthenticationProvider @@ -43,11 +39,6 @@ import org.slf4j.LoggerFactory; */ public class AuthenticationProviderService { - /** - * Logger for this class. - */ - private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); - /** * Service for retrieving CAS configuration information. */ From a5acb5acd684d256b9d2c1274f8cd1d57be9f263 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 12 Dec 2020 13:54:57 -0800 Subject: [PATCH 5/5] GUACAMOLE-793: Add Docker environment variables for group-related CAS properties. --- guacamole-docker/bin/start.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guacamole-docker/bin/start.sh b/guacamole-docker/bin/start.sh index 5123a8ac4..e58ba9e09 100755 --- a/guacamole-docker/bin/start.sh +++ b/guacamole-docker/bin/start.sh @@ -678,6 +678,10 @@ END set_property "cas-authorization-endpoint" "$CAS_AUTHORIZATION_ENDPOINT" set_property "cas-redirect-uri" "$CAS_REDIRECT_URI" set_optional_property "cas-clearpass-key" "$CAS_CLEARPASS_KEY" + set_optional_property "cas-group-attribute" "$CAS_GROUP_ATTRIBUTE" + set_optional_property "cas-group-format" "$CAS_GROUP_FORMAT" + set_optional_property "cas-group-ldap-base-dn" "$CAS_GROUP_LDAP_BASE_DN" + set_optional_property "cas-group-ldap-attribute" "$CAS_GROUP_LDAP_ATTRIBUTE" # Add required .jar files to GUACAMOLE_EXT ln -s /opt/guacamole/cas/guacamole-auth-*.jar "$GUACAMOLE_EXT"