GUACAMOLE-103: Periodically clean SAMLResponseMap for expired responses.

This commit is contained in:
Virtually Nick
2020-05-25 19:56:47 -04:00
parent 1c9efb2a44
commit 09429492e0
4 changed files with 144 additions and 4 deletions

View File

@@ -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<String, List<String>> 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<String, String> parseTokens(Map<String, List<String>> attributes)
throws GuacamoleException {
Map<String, String> tokens = new HashMap<>();
for (Entry<String, List<String>> entry : attributes.entrySet()) {
List<String> values = entry.getValue();
tokens.put(entry.getKey(), values.get(0));
}
return tokens;
}
}

View File

@@ -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<String, SamlResponse> 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<String, SamlResponse> entry : samlResponseMap.entrySet()) {
try {
entry.getValue().validateTimestamps();
}
catch (ValidationError e) {
samlResponseMap.remove(entry.getKey());
}
}
}
}
}

View File

@@ -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;
/**
@@ -142,6 +143,18 @@ public class ConfigurationService {
};
/**
* 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.
*/
@@ -288,6 +301,20 @@ public class ConfigurationService {
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
* initialize the client.

View File

@@ -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;
@@ -43,6 +45,16 @@ public class SAMLAuthenticatedUser extends AbstractAuthenticatedUser {
*/
private Credentials credentials;
/**
* The effective groups of the authenticated user.
*/
private Set<String> effectiveGroups;
/**
* Tokens associated with the authenticated user.
*/
private Map<String, String> tokens;
/**
* Initializes this AuthenticatedUser using the given username and
* credentials.
@@ -52,12 +64,31 @@ 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<String, String> tokens, Set<String> 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<String, String> getTokens() {
return tokens;
}
@Override
public AuthenticationProvider getAuthenticationProvider() {
return authProvider;
@@ -68,4 +99,9 @@ public class SAMLAuthenticatedUser extends AbstractAuthenticatedUser {
return credentials;
}
@Override
public Set<String> getEffectiveUserGroups() {
return effectiveGroups;
}
}