diff --git a/extensions/guacamole-auth-saml/pom.xml b/extensions/guacamole-auth-saml/pom.xml
index 5029d85f9..135ffacd5 100644
--- a/extensions/guacamole-auth-saml/pom.xml
+++ b/extensions/guacamole-auth-saml/pom.xml
@@ -158,9 +158,10 @@
- com.sun.jersey
- jersey-server
- 1.17.1
+ javax.ws.rs
+ jsr311-api
+ 1.1.1
+ provided
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 db4e08201..e819142d5 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
@@ -25,8 +25,6 @@ import com.onelogin.saml2.authn.AuthnRequest;
import com.onelogin.saml2.authn.SamlResponse;
import com.onelogin.saml2.exception.SettingsException;
import com.onelogin.saml2.exception.ValidationError;
-import com.onelogin.saml2.http.HttpRequest;
-import com.onelogin.saml2.servlet.ServletUtils;
import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.util.Util;
import java.io.IOException;
@@ -69,6 +67,12 @@ public class AuthenticationProviderService {
*/
@Inject
private Provider authenticatedUserProvider;
+
+ /**
+ * The map used to track active SAML responses.
+ */
+ @Inject
+ private SAMLResponseMap samlResponseMap;
/**
* Returns an AuthenticatedUser representing the user authenticated by the
@@ -87,7 +91,7 @@ public class AuthenticationProviderService {
*/
public AuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
-
+
HttpServletRequest request = credentials.getRequest();
// Initialize and configure SAML client.
@@ -96,16 +100,18 @@ public class AuthenticationProviderService {
if (request != null) {
// Look for the SAML Response parameter.
- String samlResponseParam = request.getParameter("SAMLResponse");
+ String responseHash = Util.urlDecoder(request.getParameter("responseHash"));
- if (samlResponseParam != null) {
+ if (responseHash != null) {
- // Convert the SAML response into the version needed for the client.
- HttpRequest httpRequest = ServletUtils.makeHttpRequest(request);
try {
// Generate the response object
- SamlResponse samlResponse = new SamlResponse(samlSettings, httpRequest);
+ if (!samlResponseMap.hasSamlResponse(responseHash))
+ throw new GuacamoleInvalidCredentialsException("Provided response has not found.",
+ CredentialsInfo.USERNAME_PASSWORD);
+
+ SamlResponse samlResponse = samlResponseMap.getSamlResponse(responseHash);
if (!samlResponse.validateNumAssertions()) {
logger.warn("SAML response contained other than single assertion.");
@@ -197,7 +203,6 @@ public class AuthenticationProviderService {
}))
);
-
}
}
diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java
index 997922684..a51d1050d 100644
--- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java
+++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java
@@ -72,7 +72,8 @@ public class SAMLAuthenticationProvider extends AbstractAuthenticationProvider {
throws GuacamoleException {
// Attempt to authenticate user with given credentials
- AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
+ AuthenticationProviderService authProviderService =
+ injector.getInstance(AuthenticationProviderService.class);
return authProviderService.authenticateUser(credentials);
}
diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java
index 9bd5cf20d..faa093550 100644
--- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java
+++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderModule.java
@@ -71,9 +71,10 @@ public class SAMLAuthenticationProviderModule extends AbstractModule {
bind(AuthenticationProvider.class).toInstance(authProvider);
bind(Environment.class).toInstance(environment);
- // Bind saml-specific services
+ // Bind SAML-specific services
bind(ConfigurationService.class);
bind(SAMLAuthenticationProviderResource.class);
+ bind(SAMLResponseMap.class);
}
diff --git a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderResource.java b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderResource.java
index 338e71491..2dcdffaf0 100644
--- a/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderResource.java
+++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProviderResource.java
@@ -20,16 +20,34 @@
package org.apache.guacamole.auth.saml;
import com.google.inject.Inject;
+import com.onelogin.saml2.authn.SamlResponse;
+import com.onelogin.saml2.exception.SettingsException;
+import com.onelogin.saml2.exception.ValidationError;
+import com.onelogin.saml2.http.HttpRequest;
+import com.onelogin.saml2.servlet.ServletUtils;
+import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.util.Util;
+import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;
import javax.ws.rs.FormParam;
import javax.ws.rs.Path;
import javax.ws.rs.POST;
+import javax.ws.rs.core.Context;
+import javax.xml.bind.DatatypeConverter;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathExpressionException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
/**
* A class that implements the REST API necessary for the
@@ -37,19 +55,36 @@ import org.apache.guacamole.auth.saml.conf.ConfigurationService;
*/
public class SAMLAuthenticationProviderResource {
+ /**
+ * Logger for this class.
+ */
+ private final Logger logger =
+ LoggerFactory.getLogger(SAMLAuthenticationProviderResource.class);
+
/**
* The configuration service for this module.
*/
@Inject
private ConfigurationService confService;
+
+ /**
+ * The map used to track active responses.
+ */
+ @Inject
+ private SAMLResponseMap samlResponseMap;
/**
* A REST endpoint that is POSTed to by the SAML IdP
* with the results of the SAML SSO Authentication.
*
- * @param samlResponse
+ * @param samlResponseString
* The encoded response returned by the SAML IdP.
*
+ * @param consumedRequest
+ * The HttpServletRequest associated with the SAML response. The
+ * parameters of this request may not be accessible, as the request may
+ * have been fully consumed by JAX-RS.
+ *
* @return
* A HTTP Response that will redirect the user back to the
* Guacamole home page, with the SAMLResponse encoded in the
@@ -61,21 +96,61 @@ public class SAMLAuthenticationProviderResource {
*/
@POST
@Path("callback")
- public Response processSamlResponse(@FormParam("SAMLResponse") String samlResponse)
+ public Response processSamlResponse(
+ @FormParam("SAMLResponse") String samlResponseString,
+ @Context HttpServletRequest consumedRequest)
throws GuacamoleException {
-
- String guacBase = confService.getCallbackUrl().toString();
- try {
- Response redirectHome = Response.seeOther(
- new URI(guacBase + "?SAMLResponse=" + Util.urlEncoder(samlResponse))).build();
- return redirectHome;
- }
- catch (URISyntaxException e) {
- throw new GuacamoleServerException("Error processing SAML response.", e);
- }
-
+ String guacBase = confService.getCallbackUrl().toString();
+ Saml2Settings samlSettings = confService.getSamlSettings();
+ try {
+ HttpRequest request = ServletUtils
+ .makeHttpRequest(consumedRequest)
+ .addParameter("SAMLResponse", samlResponseString);
+ SamlResponse samlResponse = new SamlResponse(samlSettings, request);
+
+ String responseHash = hashSamlResponse(samlResponseString);
+ samlResponseMap.putSamlResponse(responseHash, samlResponse);
+ return Response.seeOther(new URI(guacBase
+ + "?responseHash="
+ + Util.urlEncoder(responseHash))
+ ).build();
+ }
+ catch (IOException
+ | NoSuchAlgorithmException
+ | ParserConfigurationException
+ | SAXException
+ | SettingsException
+ | URISyntaxException
+ | ValidationError
+ | XPathExpressionException e) {
+ throw new GuacamoleServerException(e);
+ }
+
+ }
+
+ /**
+ * This is a utility method designed to generate a SHA-256 has for the
+ * given string representation of the SAMLResponse, throwing an exception
+ * if, for some reason, the Java implementation in use doesn't support
+ * SHA-256, and returning a hex-formatted hash value.
+ *
+ * @param samlResponse
+ * The String representation of the SAML response.
+ *
+ * @return
+ * A hex-formatted string of the SHA-256 hash.
+ *
+ * @throws NoSuchAlgorithmException
+ * If Java does not support SHA-256.
+ */
+ private String hashSamlResponse(String samlResponse)
+ throws NoSuchAlgorithmException {
+
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ return DatatypeConverter.printHexBinary(
+ digest.digest(samlResponse.getBytes(StandardCharsets.UTF_8)));
}
}
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
new file mode 100644
index 000000000..3a199045c
--- /dev/null
+++ b/extensions/guacamole-auth-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLResponseMap.java
@@ -0,0 +1,80 @@
+/*
+ * 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.saml;
+
+import com.google.inject.Singleton;
+import com.onelogin.saml2.authn.SamlResponse;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A class that handles mapping of hashes to SAMLResponse objects.
+ */
+@Singleton
+public class SAMLResponseMap {
+
+ /**
+ * The internal data structure that holds a map of SHA-256 hashes to
+ * SAML responses.
+ */
+ private final ConcurrentMap samlResponseMap =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Retrieve the SamlResponse from the map that is represented by the
+ * provided hash, or null if no such object exists.
+ *
+ * @param hash
+ * The SHA-256 hash of the SamlResponse.
+ *
+ * @return
+ * The SamlResponse object matching the hash provided.
+ */
+ protected SamlResponse getSamlResponse(String hash) {
+ return samlResponseMap.remove(hash);
+ }
+
+ /**
+ * Place the provided mapping of hash to SamlResponse into the map.
+ *
+ * @param hash
+ * The hash that will be the lookup key for this SamlResponse.
+ *
+ * @param samlResponse
+ * The SamlResponse object.
+ */
+ protected void putSamlResponse(String hash, SamlResponse samlResponse) {
+ samlResponseMap.put(hash, samlResponse);
+ }
+
+ /**
+ * Return true if the provided hash key exists in the map, otherwise false.
+ *
+ * @param hash
+ * The hash key to look for in the map.
+ *
+ * @return
+ * true if the provided hash is present, otherwise false.
+ */
+ protected boolean hasSamlResponse(String hash) {
+ return samlResponseMap.containsKey(hash);
+ }
+
+}