mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-103: Move SAMLResponse processing to authentication service.
This commit is contained in:
@@ -158,9 +158,10 @@
|
||||
|
||||
<!-- Jersey - JAX-RS Implementation -->
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
<version>1.17.1</version>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>jsr311-api</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- OneLogin SAML Library -->
|
||||
|
@@ -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;
|
||||
@@ -70,6 +68,12 @@ public class AuthenticationProviderService {
|
||||
@Inject
|
||||
private Provider<SAMLAuthenticatedUser> authenticatedUserProvider;
|
||||
|
||||
/**
|
||||
* The map used to track active SAML responses.
|
||||
*/
|
||||
@Inject
|
||||
private SAMLResponseMap samlResponseMap;
|
||||
|
||||
/**
|
||||
* Returns an AuthenticatedUser representing the user authenticated by the
|
||||
* given credentials.
|
||||
@@ -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 {
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
Saml2Settings samlSettings = confService.getSamlSettings();
|
||||
try {
|
||||
Response redirectHome = Response.seeOther(
|
||||
new URI(guacBase + "?SAMLResponse=" + Util.urlEncoder(samlResponse))).build();
|
||||
return redirectHome;
|
||||
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 (URISyntaxException e) {
|
||||
throw new GuacamoleServerException("Error processing SAML response.", e);
|
||||
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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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<String, SamlResponse> 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);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user