GUACAMOLE-1372: Modify usage of SAML library to allow signed requests.

This commit is contained in:
James Muehlner
2022-07-28 00:44:34 +00:00
parent 4b4490b45a
commit b35b4d509f
3 changed files with 66 additions and 12 deletions

View File

@@ -126,6 +126,8 @@ public class AuthenticationSessionManager {
* call to resume(). If authentication is never resumed, the session will * call to resume(). If authentication is never resumed, the session will
* automatically be cleaned up after it ceases to be valid. * automatically be cleaned up after it ceases to be valid.
* *
* This method will automatically generate a new identifier.
*
* @param session * @param session
* The {@link AuthenticationSession} representing the in-progress SAML * The {@link AuthenticationSession} representing the in-progress SAML
* authentication attempt. * authentication attempt.
@@ -140,6 +142,27 @@ public class AuthenticationSessionManager {
return identifier; return identifier;
} }
/**
* Defers the Guacamole side of authentication for the user having the
* given authentication session such that it may be later resumed through a
* call to resume(). If authentication is never resumed, the session will
* automatically be cleaned up after it ceases to be valid.
*
* This method accepts an externally generated ID, which should be a UUID
* or similar unique identifier.
*
* @param session
* The {@link AuthenticationSession} representing the in-progress SAML
* authentication attempt.
*
* @param identifier
* A unique and unpredictable string that may be used to represent the
* given session when calling resume().
*/
public void defer(AuthenticationSession session, String identifier) {
sessions.put(identifier, session);
}
/** /**
* Shuts down the executor service that periodically removes all invalid * Shuts down the executor service that periodically removes all invalid
* authentication sessions. This must be invoked when the SAML extension is * authentication sessions. This must be invoked when the SAML extension is

View File

@@ -21,7 +21,9 @@ package org.apache.guacamole.auth.saml.acs;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.onelogin.saml2.Auth;
import com.onelogin.saml2.authn.AuthnRequest; import com.onelogin.saml2.authn.AuthnRequest;
import com.onelogin.saml2.authn.AuthnRequestParams;
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;
@@ -29,7 +31,6 @@ import com.onelogin.saml2.settings.Saml2Settings;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import javax.ws.rs.core.UriBuilder;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathExpressionException;
import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleException;
@@ -57,6 +58,12 @@ public class SAMLService {
@Inject @Inject
private AuthenticationSessionManager sessionManager; private AuthenticationSessionManager sessionManager;
/**
* Generator of arbitrary, unique, unpredictable identifiers.
*/
@Inject
private IdentifierGenerator idGenerator;
/** /**
* Creates a new SAML request, beginning the overall authentication flow * Creates a new SAML request, beginning the overall authentication flow
* that will ultimately result in an asserted user identity if the user is * that will ultimately result in an asserted user identity if the user is
@@ -75,20 +82,31 @@ public class SAMLService {
public URI createRequest() throws GuacamoleException { public URI createRequest() throws GuacamoleException {
Saml2Settings samlSettings = confService.getSamlSettings(); Saml2Settings samlSettings = confService.getSamlSettings();
AuthnRequest samlReq = new AuthnRequest(samlSettings);
// Create a new authentication session to represent this attempt while
// it is in progress
AuthenticationSession session = new AuthenticationSession(samlReq.getId(),
confService.getAuthenticationTimeout() * 60000L);
// Produce redirect for continuing the authentication process with // Produce redirect for continuing the authentication process with
// the SAML IdP // the SAML IdP
try { try {
return UriBuilder.fromUri(samlSettings.getIdpSingleSignOnServiceUrl().toURI()) Auth auth = new Auth(samlSettings, null, null);
.queryParam("SAMLRequest", samlReq.getEncodedAuthnRequest())
.queryParam("RelayState", sessionManager.defer(session)) // Generate a unique ID to use for the relay state
.build(); String identifier = idGenerator.generateIdentifier();
// Create the request URL for the SAML IdP
String requestUrl = auth.login(
identifier,
new AuthnRequestParams(false, false, true),
true);
// Create a new authentication session to represent this attempt while
// it is in progress, using the request ID that was just issued
AuthenticationSession session = new AuthenticationSession(
auth.getLastRequestId(),
confService.getAuthenticationTimeout() * 60000L);
// Save the session with the unique relay state ID
sessionManager.defer(session, identifier);
return new URI(requestUrl);
} }
catch (IOException e) { catch (IOException e) {
throw new GuacamoleServerException("SAML authentication request " throw new GuacamoleServerException("SAML authentication request "
@@ -99,6 +117,11 @@ public class SAMLService {
+ "be generated due to an error in the URI syntax: " + "be generated due to an error in the URI syntax: "
+ e.getMessage()); + e.getMessage());
} }
catch (SettingsException e) {
throw new GuacamoleServerException("Error while attempting to sign "
+ "request using provided private key / certificate: "
+ e.getMessage(), e);
}
} }

View File

@@ -492,7 +492,15 @@ public class ConfigurationService {
samlSettings.setDebug(getDebug()); samlSettings.setDebug(getDebug());
samlSettings.setCompressRequest(getCompressRequest()); samlSettings.setCompressRequest(getCompressRequest());
samlSettings.setCompressResponse(getCompressResponse()); samlSettings.setCompressResponse(getCompressResponse());
// Request that the SAML library sign everything that it can, if
// both private key and certificate are specified
if (privateKeyFile != null && certificateFile != null) {
samlSettings.setAuthnRequestsSigned(true);
samlSettings.setLogoutRequestSigned(true);
samlSettings.setLogoutResponseSigned(true);
}
return samlSettings; return samlSettings;
} }