diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java index 656ef5f9a..718a0c0f7 100644 --- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.saml; import com.google.inject.Inject; import com.google.inject.Provider; +import com.onelogin.saml2.Auth; import com.onelogin.saml2.authn.AuthnRequest; import com.onelogin.saml2.authn.SamlResponse; import com.onelogin.saml2.exception.SettingsException; @@ -29,6 +30,11 @@ import com.onelogin.saml2.settings.Saml2Settings; import com.onelogin.saml2.util.Util; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; @@ -129,9 +135,22 @@ public class AuthenticationProviderService { // Grab the username, and, if present, finish authentication. String username = samlResponse.getNameId().toLowerCase(); if (username != null) { + + // Retrieve any provided attributes + Map> attributes = + samlResponse.getAttributes(); + + // Back-port the username to the credentials credentials.setUsername(username); - SAMLAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(username, credentials); + + // Configure the AuthenticatedUser and return it + SAMLAuthenticatedUser authenticatedUser = + authenticatedUserProvider.get(); + + authenticatedUser.init(username, credentials, + parseTokens(attributes), + new HashSet<>(attributes.get(confService.getGroupAttribute()))); + return authenticatedUser; } } @@ -199,10 +218,25 @@ public class AuthenticationProviderService { // Redirect to SAML Identity Provider (IdP) throw new GuacamoleInsufficientCredentialsException("Redirecting to SAML IdP.", new CredentialsInfo(Arrays.asList(new Field[] { - new SAMLRedirectField(reqString) + new RedirectField("samlRedirect", reqString, "LOGIN.REDIRECT_PENDING") })) ); } + + private Map parseTokens(Map> attributes) + throws GuacamoleException { + + Map tokens = new HashMap<>(); + for (Entry> entry : attributes.entrySet()) { + + List values = entry.getValue(); + tokens.put(entry.getKey(), values.get(0)); + + } + + return tokens; + + } } diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLResponseMap.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLResponseMap.java index 3a199045c..588811a99 100644 --- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLResponseMap.java +++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLResponseMap.java @@ -21,8 +21,13 @@ package org.apache.guacamole.auth.saml; import com.google.inject.Singleton; import com.onelogin.saml2.authn.SamlResponse; +import com.onelogin.saml2.exception.ValidationError; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * A class that handles mapping of hashes to SAMLResponse objects. @@ -37,6 +42,21 @@ public class SAMLResponseMap { private final ConcurrentMap samlResponseMap = new ConcurrentHashMap<>(); + /** + * Executor service which runs the periodic cleanup task + */ + private final ScheduledExecutorService executor = + Executors.newScheduledThreadPool(1); + + /** + * Create a new instance of this response map and kick off the executor + * that schedules the response cleanup task to run every five minutes. + */ + public SAMLResponseMap() { + // Cleanup unclaimed responses every five minutes + executor.scheduleAtFixedRate(new SAMLResponseCleanupTask(), 5, 5, TimeUnit.MINUTES); + } + /** * Retrieve the SamlResponse from the map that is represented by the * provided hash, or null if no such object exists. @@ -77,4 +97,27 @@ public class SAMLResponseMap { return samlResponseMap.containsKey(hash); } + /** + * Task which runs every five minutes and cleans up any expired SAML + * responses that haven't been claimed and removed from the map. + */ + private class SAMLResponseCleanupTask implements Runnable { + + @Override + public void run() { + + // Loop through responses in map and remove ones that are no longer valid. + for (Entry entry : samlResponseMap.entrySet()) { + try { + entry.getValue().validateTimestamps(); + } + catch (ValidationError e) { + samlResponseMap.remove(entry.getKey()); + } + } + + } + + } + } diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java index 52324ce22..e216ccbca 100644 --- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java @@ -33,6 +33,7 @@ import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.properties.BooleanGuacamoleProperty; import org.apache.guacamole.properties.FileGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; import org.apache.guacamole.properties.URIGuacamoleProperty; /** @@ -141,6 +142,18 @@ public class ConfigurationService { public String getName() { return "saml-strict"; } }; + + /** + * The property that defines what attribute the SAML provider will return + * that contains group membership for the authenticated user. + */ + private static final StringGuacamoleProperty SAML_GROUP_ATTRIBUTE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "saml-group-attribute"; } + + }; /** * The Guacamole server environment. @@ -287,6 +300,20 @@ public class ConfigurationService { private Boolean getCompressResponse() throws GuacamoleException { return environment.getProperty(SAML_COMPRESS_RESPONSE, true); } + + /** + * Return the name of the attribute that will be supplied by the identity + * provider that contains the groups of which this user is a member. + * + * @return + * The name of the attribute that contains the user groups. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public String getGroupAttribute() throws GuacamoleException { + return environment.getProperty(SAML_GROUP_ATTRIBUTE, "groups"); + } /** * Returns the collection of SAML settings used to diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/user/SAMLAuthenticatedUser.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/user/SAMLAuthenticatedUser.java index fcc1a0446..57c70a365 100644 --- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/user/SAMLAuthenticatedUser.java +++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/user/SAMLAuthenticatedUser.java @@ -20,6 +20,8 @@ package org.apache.guacamole.auth.saml.user; import com.google.inject.Inject; +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; @@ -42,6 +44,16 @@ public class SAMLAuthenticatedUser extends AbstractAuthenticatedUser { * The credentials provided when this user was authenticated. */ private Credentials credentials; + + /** + * The effective groups of the authenticated user. + */ + private Set effectiveGroups; + + /** + * Tokens associated with the authenticated user. + */ + private Map tokens; /** * Initializes this AuthenticatedUser using the given username and @@ -52,11 +64,30 @@ public class SAMLAuthenticatedUser extends AbstractAuthenticatedUser { * * @param credentials * The credentials provided when this user was authenticated. + * + * @param tokens + * The tokens available from this authentication provider. + * + * @param effectiveGroups + * The groups of which this user is a member. */ - public void init(String username, Credentials credentials) { + public void init(String username, Credentials credentials, + Map tokens, Set effectiveGroups) { this.credentials = credentials; + this.effectiveGroups = effectiveGroups; + this.tokens = tokens; setIdentifier(username); } + + /** + * Get the tokens associated with this particular user. + * + * @return + * A map of token names and values available from this user account. + */ + public Map getTokens() { + return tokens; + } @Override public AuthenticationProvider getAuthenticationProvider() { @@ -67,5 +98,10 @@ public class SAMLAuthenticatedUser extends AbstractAuthenticatedUser { public Credentials getCredentials() { return credentials; } + + @Override + public Set getEffectiveUserGroups() { + return effectiveGroups; + } }