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 -->
|
<!-- Jersey - JAX-RS Implementation -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.sun.jersey</groupId>
|
<groupId>javax.ws.rs</groupId>
|
||||||
<artifactId>jersey-server</artifactId>
|
<artifactId>jsr311-api</artifactId>
|
||||||
<version>1.17.1</version>
|
<version>1.1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OneLogin SAML Library -->
|
<!-- OneLogin SAML Library -->
|
||||||
|
@@ -25,8 +25,6 @@ import com.onelogin.saml2.authn.AuthnRequest;
|
|||||||
import com.onelogin.saml2.authn.SamlResponse;
|
import com.onelogin.saml2.authn.SamlResponse;
|
||||||
import com.onelogin.saml2.exception.SettingsException;
|
import com.onelogin.saml2.exception.SettingsException;
|
||||||
import com.onelogin.saml2.exception.ValidationError;
|
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.settings.Saml2Settings;
|
||||||
import com.onelogin.saml2.util.Util;
|
import com.onelogin.saml2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -69,6 +67,12 @@ public class AuthenticationProviderService {
|
|||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
private Provider<SAMLAuthenticatedUser> authenticatedUserProvider;
|
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
|
* Returns an AuthenticatedUser representing the user authenticated by the
|
||||||
@@ -87,7 +91,7 @@ public class AuthenticationProviderService {
|
|||||||
*/
|
*/
|
||||||
public AuthenticatedUser authenticateUser(Credentials credentials)
|
public AuthenticatedUser authenticateUser(Credentials credentials)
|
||||||
throws GuacamoleException {
|
throws GuacamoleException {
|
||||||
|
|
||||||
HttpServletRequest request = credentials.getRequest();
|
HttpServletRequest request = credentials.getRequest();
|
||||||
|
|
||||||
// Initialize and configure SAML client.
|
// Initialize and configure SAML client.
|
||||||
@@ -96,16 +100,18 @@ public class AuthenticationProviderService {
|
|||||||
if (request != null) {
|
if (request != null) {
|
||||||
|
|
||||||
// Look for the SAML Response parameter.
|
// 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 {
|
try {
|
||||||
|
|
||||||
// Generate the response object
|
// 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()) {
|
if (!samlResponse.validateNumAssertions()) {
|
||||||
logger.warn("SAML response contained other than single assertion.");
|
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 {
|
throws GuacamoleException {
|
||||||
|
|
||||||
// Attempt to authenticate user with given credentials
|
// Attempt to authenticate user with given credentials
|
||||||
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
|
AuthenticationProviderService authProviderService =
|
||||||
|
injector.getInstance(AuthenticationProviderService.class);
|
||||||
return authProviderService.authenticateUser(credentials);
|
return authProviderService.authenticateUser(credentials);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -71,9 +71,10 @@ public class SAMLAuthenticationProviderModule extends AbstractModule {
|
|||||||
bind(AuthenticationProvider.class).toInstance(authProvider);
|
bind(AuthenticationProvider.class).toInstance(authProvider);
|
||||||
bind(Environment.class).toInstance(environment);
|
bind(Environment.class).toInstance(environment);
|
||||||
|
|
||||||
// Bind saml-specific services
|
// Bind SAML-specific services
|
||||||
bind(ConfigurationService.class);
|
bind(ConfigurationService.class);
|
||||||
bind(SAMLAuthenticationProviderResource.class);
|
bind(SAMLAuthenticationProviderResource.class);
|
||||||
|
bind(SAMLResponseMap.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,16 +20,34 @@
|
|||||||
package org.apache.guacamole.auth.saml;
|
package org.apache.guacamole.auth.saml;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
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 com.onelogin.saml2.util.Util;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
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.core.Response;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.POST;
|
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.GuacamoleException;
|
||||||
import org.apache.guacamole.GuacamoleServerException;
|
import org.apache.guacamole.GuacamoleServerException;
|
||||||
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
|
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
|
* 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 {
|
public class SAMLAuthenticationProviderResource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger for this class.
|
||||||
|
*/
|
||||||
|
private final Logger logger =
|
||||||
|
LoggerFactory.getLogger(SAMLAuthenticationProviderResource.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration service for this module.
|
* The configuration service for this module.
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
private ConfigurationService confService;
|
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
|
* A REST endpoint that is POSTed to by the SAML IdP
|
||||||
* with the results of the SAML SSO Authentication.
|
* with the results of the SAML SSO Authentication.
|
||||||
*
|
*
|
||||||
* @param samlResponse
|
* @param samlResponseString
|
||||||
* The encoded response returned by the SAML IdP.
|
* 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
|
* @return
|
||||||
* A HTTP Response that will redirect the user back to the
|
* A HTTP Response that will redirect the user back to the
|
||||||
* Guacamole home page, with the SAMLResponse encoded in the
|
* Guacamole home page, with the SAMLResponse encoded in the
|
||||||
@@ -61,21 +96,61 @@ public class SAMLAuthenticationProviderResource {
|
|||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Path("callback")
|
@Path("callback")
|
||||||
public Response processSamlResponse(@FormParam("SAMLResponse") String samlResponse)
|
public Response processSamlResponse(
|
||||||
|
@FormParam("SAMLResponse") String samlResponseString,
|
||||||
|
@Context HttpServletRequest consumedRequest)
|
||||||
throws GuacamoleException {
|
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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