Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
*~
target/
src/main/resources/generated/

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso-saml</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-sso-saml</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso</artifactId>
<version>1.6.0</version>
<relativePath>../../</relativePath>
</parent>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
</dependency>
<!-- Core SSO support -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso-base</artifactId>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<!-- JAX-RS Annotations -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</dependency>
<!-- OneLogin SAML Library -->
<dependency>
<groupId>com.onelogin</groupId>
<artifactId>java-saml</artifactId>
<version>2.9.0</version>
<exclusions>
<!--
Replace vulnerable version of Woodstox until upstream
releases a version with fixed dependencies
-->
<exclusion>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
</exclusion>
<!--
Replace vulnerable version of xmlsec until upstream
releases a version with fixed dependencies
-->
<exclusion>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
</exclusion>
<!--
Replace slightly older commons-lang3 (3.12.0) with latest
compatible version (3.17.0) so that we don't need two copies
of the same license information.
-->
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</exclusion>
<!--
Replace slightly older commons-codec (1.15) with newer
and identical version to that used by Apache Directory API
for LDAP (1.16.0) so that we don't need two copies of the
same license information.
-->
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Woodstox (see exclusions for java-saml) -->
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>
<version>5.4.0</version>
</dependency>
<!-- Apache XML Security for Java (see exclusions for java-saml) -->
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>2.2.6</version>
<exclusions>
<!--
Replace slightly older commons-codec (1.15) with newer
and identical version to that used by Apache Directory API
for LDAP (1.17.1) so that we don't need two copies of the
same license information.
-->
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Apache Commons Lang (see exclusions for java-saml) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<!-- Apache Commons Codec (see exclusions for java-saml and xmlsec) -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.17.1</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,125 @@
/*
* 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.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.net.URI;
import java.util.Arrays;
import org.apache.guacamole.auth.saml.user.SAMLAuthenticatedUser;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.saml.acs.AssertedIdentity;
import org.apache.guacamole.auth.saml.acs.SAMLAuthenticationSessionManager;
import org.apache.guacamole.auth.saml.acs.SAMLService;
import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService;
import org.apache.guacamole.form.Field;
import org.apache.guacamole.form.RedirectField;
import org.apache.guacamole.language.TranslatableMessage;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
/**
* Service that authenticates Guacamole users by processing the responses of
* SAML identity providers.
*/
@Singleton
public class AuthenticationProviderService implements SSOAuthenticationProviderService {
/**
* The name of the query parameter that identifies an active authentication
* session (in-progress SAML authentication attempt).
*/
public static final String AUTH_SESSION_QUERY_PARAM = "state";
/**
* Provider for AuthenticatedUser objects.
*/
@Inject
private Provider<SAMLAuthenticatedUser> authenticatedUserProvider;
/**
* Manager of active SAML authentication attempts.
*/
@Inject
private SAMLAuthenticationSessionManager sessionManager;
/**
* Service for processing SAML requests/responses.
*/
@Inject
private SAMLService saml;
/**
* Return the value of the session identifier associated with the given
* credentials, or null if no session identifier is found in the
* credentials.
*
* @param credentials
* The credentials from which to extract the session identifier.
*
* @return
* The session identifier associated with the given credentials, or
* null if no identifier is found.
*/
public static String getSessionIdentifier(Credentials credentials) {
// Return the session identifier from the request params, if set, or
// null otherwise
return credentials != null ? credentials.getParameter(AUTH_SESSION_QUERY_PARAM) : null;
}
@Override
public SAMLAuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
// Use established SAML identity if already provided by the SAML IdP
AssertedIdentity identity = sessionManager.getIdentity(
getSessionIdentifier(credentials));
if (identity != null) {
SAMLAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(identity, credentials);
return authenticatedUser;
}
// Redirect to SAML IdP if no SAML identity is associated with the
// Guacamole authentication request
throw new GuacamoleInvalidCredentialsException("Redirecting to SAML IdP.",
new CredentialsInfo(Arrays.asList(new Field[] {
new RedirectField(AUTH_SESSION_QUERY_PARAM, getLoginURI(),
new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING"))
}))
);
}
@Override
public URI getLoginURI() throws GuacamoleException {
return saml.createRequest();
}
@Override
public void shutdown() {
sessionManager.shutdown();
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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 org.apache.guacamole.auth.saml.acs.SAMLAuthenticationSessionManager;
import org.apache.guacamole.auth.sso.SSOAuthenticationEventListener;
import org.apache.guacamole.net.auth.Credentials;
import com.google.inject.Inject;
/**
* A Listener that will reactivate or invalidate SAML auth sessions depending on
* overall auth success or failure.
*/
public class SAMLAuthenticationEventListener extends SSOAuthenticationEventListener {
/**
* Session manager for generating and maintaining unique tokens to
* represent the authentication flow of a user who has only partially
* authenticated.
*
* Requires static injection due to the fact that the webapp just calls the
* constructor directly when creating new Listeners. The instances will not
* be constructed by guice.
*
* Note that is possible to instead inject an AuthenticationSessionManager
* instance directly into the base class, but this results in different
* instances of the session manager injected here and in the rest of the
* extension, regardless of singleton configuration for the service.
*/
@Inject
protected static SAMLAuthenticationSessionManager sessionManager;
@Override
protected String getSessionIdentifier(Credentials credentials) {
return AuthenticationProviderService.getSessionIdentifier(credentials);
}
@Override
protected void reactivateSession(String sessionIdentifier) {
sessionManager.reactivateSession(sessionIdentifier);
}
@Override
protected void invalidateSession(String sessionIdentifier) {
sessionManager.invalidateSession(sessionIdentifier);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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 org.apache.guacamole.auth.saml.acs.AssertionConsumerServiceResource;
import org.apache.guacamole.auth.sso.SSOAuthenticationProvider;
/**
* AuthenticationProvider implementation that authenticates Guacamole users
* against a SAML SSO Identity Provider (IdP). This module does not provide any
* storage for connection information, and must be layered with other modules
* for authenticated users to have access to Guacamole connections.
*/
public class SAMLAuthenticationProvider extends SSOAuthenticationProvider {
/**
* Creates a new SAMLAuthenticationProvider that authenticates users
* against a SAML IdP.
*/
public SAMLAuthenticationProvider() {
super(AuthenticationProviderService.class,
AssertionConsumerServiceResource.class,
new SAMLAuthenticationProviderModule());
}
@Override
public String getIdentifier() {
return "saml";
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.AbstractModule;
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
import org.apache.guacamole.auth.saml.acs.AssertionConsumerServiceResource;
import org.apache.guacamole.auth.saml.acs.SAMLAuthenticationSessionManager;
import org.apache.guacamole.auth.saml.acs.SAMLService;
import org.apache.guacamole.auth.saml.conf.SAMLEnvironment;
import org.apache.guacamole.environment.Environment;
/**
* Guice module which configures SAML-specific injections.
*/
public class SAMLAuthenticationProviderModule extends AbstractModule {
/**
* The environment for this server and extension.
*/
private final Environment environment = new SAMLEnvironment();
@Override
protected void configure() {
bind(AssertionConsumerServiceResource.class);
bind(ConfigurationService.class);
bind(SAMLAuthenticationSessionManager.class);
bind(SAMLService.class);
bind(Environment.class).toInstance(environment);
requestStaticInjection(SAMLAuthenticationEventListener.class);
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.acs;
import com.onelogin.saml2.authn.SamlResponse;
import com.onelogin.saml2.exception.ValidationError;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPathExpressionException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
/**
* Representation of a user's identity as asserted by a SAML IdP.
*/
public class AssertedIdentity {
/**
* The original SAML response received from the SAML IdP that asserted
* the user's identity.
*/
private final SamlResponse response;
/**
* The user's Guacamole username.
*/
private final String username;
/**
* All attributes included in the original SAML response. Attributes may
* possibly be associated with multiple values.
*/
private final Map<String, List<String>> attributes;
/**
* Creates a new AssertedIdentity representing the identity asserted by the
* SAML IdP in the given response.
*
* @param response
* The response received from the SAML IdP.
*
* @throws GuacamoleException
* If the given SAML response cannot be parsed or is missing required
* information.
*/
public AssertedIdentity(SamlResponse response) throws GuacamoleException {
// Parse user identity from SAML response
String nameId;
try {
nameId = response.getNameId();
if (nameId == null)
throw new GuacamoleSecurityException("SAML response did not "
+ "include the relevant user's identity (no name ID).");
}
// Unfortunately, getNameId() is declared as "throws Exception", so
// this error handling has to be pretty generic
catch (Exception e) {
throw new GuacamoleSecurityException("User identity (name ID) "
+ "could not be retrieved from the SAML response: " + e.getMessage(), e);
}
// Retrieve any provided attributes
Map<String, List<String>> responseAttributes;
try {
responseAttributes = Collections.unmodifiableMap(response.getAttributes());
}
catch (XPathExpressionException | ValidationError e) {
throw new GuacamoleSecurityException("SAML attributes could not "
+ "be parsed from the SAML response: " + e.getMessage(), e);
}
this.response = response;
this.username = nameId.toLowerCase(); // Canonicalize username as lowercase
this.attributes = responseAttributes;
}
/**
* Returns the username of the Guacamole user whose identity was asserted
* by the SAML IdP.
*
* @return
* The username of the Guacamole user whose identity was asserted by
* the SAML IdP.
*/
public String getUsername() {
return username;
}
/**
* Returns a Map containing all attributes included in the original SAML
* response that asserted this user's identity. Attributes may possibly be
* associated with multiple values.
*
* @return
* A Map of all attributes included in the original SAML response.
*/
public Map<String, List<String>> getAttributes() {
return attributes;
}
/**
* Returns whether the identity asserted by the original SAML response is
* still valid. An asserted identity may cease to be valid after creation
* if it has expired according to the timestamps included in the response.
*
* @return
* true if the original SAML response is still valid, false otherwise.
*/
public boolean isValid() {
return response.isValid();
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.acs;
import com.google.inject.Inject;
import java.net.URI;
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.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.saml.AuthenticationProviderService;
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
import org.apache.guacamole.auth.sso.SSOResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* REST API resource that provides a SAML assertion consumer service (ACS)
* endpoint. SAML identity providers will issue an HTTP POST to this endpoint
* asserting the user's identity when the user has successfully authenticated.
*/
public class AssertionConsumerServiceResource extends SSOResource {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(AssertionConsumerServiceResource.class);
/**
* The configuration service for this module.
*/
@Inject
private ConfigurationService confService;
/**
* Manager of active SAML authentication attempts.
*/
@Inject
private SAMLAuthenticationSessionManager sessionManager;
/**
* Service for processing SAML requests/responses.
*/
@Inject
private SAMLService saml;
/**
* Processes the SAML response submitted by the SAML IdP via an HTTP POST.
* If SSO has been successful, the user is redirected back to Guacamole to
* complete authentication. If SSO has failed, the user is redirected back
* to Guacamole to re-attempt authentication.
*
* @param relayState
* The "RelayState" value originally provided in the SAML request,
* which in our case is the transient the session identifier of the
* in-progress authentication attempt. The SAML standard requires that
* the identity provider include the "RelayState" value it received
* alongside its SAML response.
*
* @param samlResponse
* 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
* An HTTP Response that will redirect the user back to Guacamole,
* with an internal state identifier included in the URL such that the
* the Guacamole side of authentication can complete.
*
* @throws GuacamoleException
* If configuration information required for processing SAML responses
* cannot be read.
*/
@POST
@Path("callback")
public Response processSamlResponse(
@FormParam("RelayState") String relayState,
@FormParam("SAMLResponse") String samlResponse,
@Context HttpServletRequest consumedRequest)
throws GuacamoleException {
URI guacBase = confService.getCallbackUrl();
try {
// Validate and parse identity asserted by SAML IdP
SAMLAuthenticationSession session = saml.processResponse(
consumedRequest.getRequestURL().toString(),
relayState, samlResponse);
// Store asserted identity for future retrieval via redirect
String identifier = sessionManager.defer(session);
// Redirect user such that Guacamole's authentication system can
// retrieve the relevant SAML identity (stored above)
return Response.seeOther(UriBuilder.fromUri(guacBase)
.queryParam(AuthenticationProviderService.AUTH_SESSION_QUERY_PARAM, identifier)
.build()
).build();
}
// If invalid, redirect back to main page to re-attempt authentication
catch (GuacamoleException e) {
logger.warn("Authentication attempted with an invalid SAML response: {}", e.getMessage());
logger.debug("Received SAML response failed validation.", e);
return Response.seeOther(guacBase).build();
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.acs;
import org.apache.guacamole.net.auth.AuthenticationSession;
/**
* Representation of an in-progress SAML authentication attempt.
*/
public class SAMLAuthenticationSession extends AuthenticationSession {
/**
* The request ID of the SAML request associated with the authentication
* attempt.
*/
private final String requestId;
/**
* The identity asserted by the SAML IdP, or null if authentication has not
* yet completed successfully.
*/
private AssertedIdentity identity = null;
/**
* Creates a new AuthenticationSession representing an in-progress SAML
* authentication attempt.
*
* @param requestId
* The request ID of the SAML request associated with the
* authentication attempt.
*
* @param expires
* The number of milliseconds that may elapse before this session must
* be considered invalid.
*/
public SAMLAuthenticationSession(String requestId, long expires) {
super(expires);
this.requestId = requestId;
}
/**
* {@inheritDoc}
*
* <p>If an identity has been asserted by the SAML IdP, this
* considers also whether the SAML response asserting that identity has
* expired.
*/
@Override
public boolean isValid() {
return super.isValid() && (identity == null || identity.isValid());
}
/**
* Returns the request ID of the SAML request associated with the
* authentication attempt.
*
* @return
* The request ID of the SAML request associated with the
* authentication attempt.
*/
public String getRequestID() {
return requestId;
}
/**
* Marks this authentication attempt as completed and successful, with the
* user having been asserted as having the given identity by the SAML IdP.
*
* @param identity
* The identity asserted by the SAML IdP.
*/
public void setIdentity(AssertedIdentity identity) {
this.identity = identity;
}
/**
* Returns the identity asserted by the SAML IdP. If authentication has not
* yet completed successfully, this will be null.
*
* @return
* The identity asserted by the SAML IdP, or null if authentication has
* not yet completed successfully.
*/
public AssertedIdentity getIdentity() {
return identity;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.acs;
import com.google.inject.Singleton;
import org.apache.guacamole.net.auth.AuthenticationSessionManager;
/**
* Manager service that temporarily stores SAML authentication attempts while
* the authentication flow is underway.
*/
@Singleton
public class SAMLAuthenticationSessionManager
extends AuthenticationSessionManager<SAMLAuthenticationSession> {
/**
* Returns the identity finally asserted by the SAML IdP at the end of the
* authentication process represented by the authentication session with
* the given identifier. If there is no such authentication session, or no
* valid identity has been asserted by the SAML IdP for that session, null
* is returned.
*
* @param identifier
* The unique string returned by the call to defer(). For convenience,
* this value may safely be null.
*
* @return
* The identity finally asserted by the SAML IdP at the end of the
* authentication process represented by the authentication session
* with the given identifier, or null if there is no such identity.
*/
public AssertedIdentity getIdentity(String identifier) {
SAMLAuthenticationSession session = resume(identifier);
if (session != null)
return session.getIdentity();
return null;
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.acs;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.onelogin.saml2.Auth;
import com.onelogin.saml2.authn.AuthnRequestParams;
import com.onelogin.saml2.authn.SamlResponse;
import com.onelogin.saml2.exception.SettingsException;
import com.onelogin.saml2.exception.ValidationError;
import com.onelogin.saml2.settings.Saml2Settings;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleSecurityException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
import org.apache.guacamole.net.auth.IdentifierGenerator;
import org.xml.sax.SAXException;
/**
* Service which abstracts the internals of handling SAML requests and
* responses.
*/
@Singleton
public class SAMLService {
/**
* Service for retrieving SAML configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Manager of active SAML authentication attempts.
*/
@Inject
private SAMLAuthenticationSessionManager sessionManager;
/**
* Creates a new SAML request, beginning the overall authentication flow
* that will ultimately result in an asserted user identity if the user is
* successfully authenticated by the SAML IdP. The URI of the SSO endpoint
* of the SAML IdP that the user must be redirected to for the
* authentication process to continue is returned.
*
* @return
* The URI of the SSO endpoint of the SAML IdP that the user must be
* redirected to.
*
* @throws GuacamoleException
* If an error prevents the SAML request and redirect URI from being
* generated.
*/
public URI createRequest() throws GuacamoleException {
Saml2Settings samlSettings = confService.getSamlSettings();
// Produce redirect for continuing the authentication process with
// the SAML IdP
try {
Auth auth = new Auth(samlSettings, null, null);
// Generate a unique ID to use for the relay state
String identifier = IdentifierGenerator.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
SAMLAuthenticationSession session = new SAMLAuthenticationSession(
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) {
throw new GuacamoleServerException("SAML authentication request "
+ "could not be encoded: " + e.getMessage());
}
catch (URISyntaxException e) {
throw new GuacamoleServerException("SAML IdP redirect could not "
+ "be generated due to an error in the URI syntax: "
+ e.getMessage());
}
catch (SettingsException e) {
throw new GuacamoleServerException("Error while attempting to sign "
+ "request using provided private key / certificate: "
+ e.getMessage(), e);
}
}
/**
* Processes the given SAML response, as received by the SAML ACS endpoint
* at the given URL, producing an {@link SAMLAuthenticationSession} that now
* includes a valid assertion of the user's identity. If the SAML response
* is invalid in any way, an exception is thrown.
*
* @param url
* The URL of the ACS endpoint that received the SAML response. This
* should be the URL pointing to the single POST-handling endpoint of
* {@link AssertionConsumerServiceResource}.
*
* @param relayState
* The "RelayState" value originally provided in the SAML request,
* which in our case is the transient the session identifier of the
* in-progress authentication attempt. The SAML standard requires that
* the identity provider include the "RelayState" value it received
* alongside its SAML response.
*
* @param encodedResponse
* The response received from the SAML IdP via the ACS endpoint at the
* given URL.
*
* @return
* The {@link SAMLAuthenticationSession} associated with the in-progress
* authentication attempt, now associated with the {@link AssertedIdentity}
* representing the identity of the user asserted by the SAML IdP.
*
* @throws GuacamoleException
* If the given SAML response is not valid, or if the configuration
* information required to validate or decrypt the response cannot be
* read.
*/
public SAMLAuthenticationSession processResponse(String url, String relayState,
String encodedResponse) throws GuacamoleException {
if (relayState == null)
throw new GuacamoleSecurityException("\"RelayState\" value "
+ "is missing from SAML response.");
SAMLAuthenticationSession session = sessionManager.resume(relayState);
if (session == null)
throw new GuacamoleSecurityException("\"RelayState\" value "
+ "included with SAML response is not valid.");
try {
// Decode received SAML response
SamlResponse response = new SamlResponse(confService.getSamlSettings(),
url, encodedResponse);
// Validate SAML response timestamp, signature, etc.
if (!response.isValid(session.getRequestID())) {
Exception validationException = response.getValidationException();
throw new GuacamoleSecurityException("SAML response did not "
+ "pass validation: " + validationException.getMessage(),
validationException);
}
// Parse identity asserted by SAML IdP
session.setIdentity(new AssertedIdentity(response));
return session;
}
catch (ValidationError e) {
throw new GuacamoleSecurityException("SAML response did not pass "
+ "validation: " + e.getMessage(), e);
}
catch (SettingsException e) {
throw new GuacamoleServerException("Current SAML settings are "
+ "insufficient to decrypt/parse the received SAML "
+ "response.", e);
}
catch (ParserConfigurationException | SAXException | XPathExpressionException e) {
throw new GuacamoleServerException("XML contents of SAML "
+ "response could not be parsed.", e);
}
catch (IOException e) {
throw new GuacamoleServerException("Contents of SAML response "
+ "could not be decrypted/read.", e);
}
}
}

View File

@@ -0,0 +1,507 @@
/*
* 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.conf;
import com.google.inject.Inject;
import com.onelogin.saml2.settings.IdPMetadataParser;
import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.settings.SettingsBuilder;
import com.onelogin.saml2.util.Constants;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleException;
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.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.properties.URIGuacamoleProperty;
/**
* Service for retrieving configuration information regarding the SAML
* authentication module.
*/
public class ConfigurationService {
/**
* The URI of the file containing the XML Metadata associated with the
* SAML IdP.
*/
private static final URIGuacamoleProperty SAML_IDP_METADATA_URL =
new URIGuacamoleProperty() {
@Override
public String getName() { return "saml-idp-metadata-url"; }
};
/**
* The URL of the SAML IdP.
*/
private static final URIGuacamoleProperty SAML_IDP_URL =
new URIGuacamoleProperty() {
@Override
public String getName() { return "saml-idp-url"; }
};
/**
* The URL identifier for this SAML client.
*/
private static final URIGuacamoleProperty SAML_ENTITY_ID =
new URIGuacamoleProperty() {
@Override
public String getName() { return "saml-entity-id"; }
};
/**
* The callback URL to use for SAML IdP, normally the base
* of the Guacamole install. The SAML extensions callback
* endpoint will be appended to this value.
*/
private static final URIGuacamoleProperty SAML_CALLBACK_URL =
new URIGuacamoleProperty() {
@Override
public String getName() { return "saml-callback-url"; }
};
/**
* Whether or not debugging should be enabled in the SAML library to help
* track down errors.
*/
private static final BooleanGuacamoleProperty SAML_DEBUG =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "saml-debug"; }
};
/**
* Whether or not to enable compression for the SAML request.
*/
private static final BooleanGuacamoleProperty SAML_COMPRESS_REQUEST =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "saml-compress-request"; }
};
/**
* Whether or not to enable compression for the SAML response.
*/
private static final BooleanGuacamoleProperty SAML_COMPRESS_RESPONSE =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "saml-compress-response"; }
};
/**
* Whether or not to enforce strict SAML security during processing.
*/
private static final BooleanGuacamoleProperty SAML_STRICT =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "saml-strict"; }
};
/**
* 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 maximum amount of time to allow for an in-progress SAML
* authentication attempt to be completed, in minutes. A user that takes
* longer than this amount of time to complete authentication with their
* identity provider will be redirected back to the identity provider to
* try again.
*/
private static final IntegerGuacamoleProperty SAML_AUTH_TIMEOUT =
new IntegerGuacamoleProperty() {
@Override
public String getName() { return "saml-auth-timeout"; }
};
/**
* The file containing the X.509 cert to use when signing or encrypting
* requests to the SAML IdP.
*/
private static final FileGuacamoleProperty SAML_X509_CERT_PATH =
new FileGuacamoleProperty() {
@Override
public String getName() { return "saml-x509-cert-path"; }
};
/**
* The file containing the private key to use when signing or encrypting
* requests to the SAML IdP.
*/
private static final FileGuacamoleProperty SAML_PRIVATE_KEY_PATH =
new FileGuacamoleProperty() {
@Override
public String getName() { return "saml-private-key-path"; }
};
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Returns the URL to be submitted as the client ID to the SAML IdP, as
* configured in guacamole.properties.
*
* @return
* The URL to send to the SAML IdP as the Client Identifier.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private URI getEntityId() throws GuacamoleException {
return environment.getProperty(SAML_ENTITY_ID);
}
/**
* The URI that contains the metadata that the SAML client should
* use to communicate with the SAML IdP. This can either be a remote
* URL of a server that provides this, or can be a URI to a file on the
* local filesystem. The metadata file is usually generated by the SAML IdP
* and should be uploaded to the system where the Guacamole client is
* running.
*
* @return
* The URI of the file containing the metadata used by the SAML client
* when it communicates with the SAML IdP.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or if the client
* metadata is missing.
*/
private URI getIdpMetadata() throws GuacamoleException {
return environment.getProperty(SAML_IDP_METADATA_URL);
}
/**
* Return the URL used to log in to the SAML IdP.
*
* @return
* The URL used to log in to the SAML IdP.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private URI getIdpUrl() throws GuacamoleException {
return environment.getProperty(SAML_IDP_URL);
}
/**
* The callback URL used for the SAML IdP to POST a response
* to upon completion of authentication, normally the base
* of the Guacamole install.
*
* @return
* The callback URL to be sent to the SAML IdP that will
* be POSTed to upon completion of SAML authentication.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed, or the property
* is missing.
*/
public URI getCallbackUrl() throws GuacamoleException {
return environment.getRequiredProperty(SAML_CALLBACK_URL);
}
/**
* Return the Boolean value that indicates whether SAML client debugging
* will be enabled, as configured in guacamole.properties. The default is
* false, and debug information will not be generated or logged.
*
* @return
* True if debugging should be enabled in the SAML library, otherwise
* false.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private boolean getDebug() throws GuacamoleException {
return environment.getProperty(SAML_DEBUG, false);
}
/**
* Return the Boolean value that indicates whether or not compression of
* SAML requests to the IdP should be enabled or not, as configured in
* guacamole.properties. The default is to enable compression.
*
* @return
* True if compression should be enabled when sending the SAML request,
* otherwise false.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private boolean getCompressRequest() throws GuacamoleException {
return environment.getProperty(SAML_COMPRESS_REQUEST, true);
}
/**
* Return a Boolean value that indicates whether or not the SAML login
* should enforce strict security controls, as configured in
* guacamole.properties. By default this is true, and should be set to
* true in any production environment.
*
* @return
* True if the SAML login should enforce strict security checks,
* otherwise false.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private boolean getStrict() throws GuacamoleException {
return environment.getProperty(SAML_STRICT, true);
}
/**
* Return a Boolean value that indicates whether or not compression should
* be requested from the server when the SAML response is returned, as
* configured in guacamole.properties. The default is to request that the
* response be compressed.
*
* @return
* True if compression should be requested from the server for the SAML
* response.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed.
*/
private boolean getCompressResponse() throws GuacamoleException {
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 maximum amount of time to allow for an in-progress SAML
* authentication attempt to be completed, in minutes. A user that takes
* longer than this amount of time to complete authentication with their
* identity provider will be redirected back to the identity provider to
* try again.
*
* @return
* The maximum amount of time to allow for an in-progress SAML
* authentication attempt to be completed, in minutes.
*
* @throws GuacamoleException
* If the authentication timeout cannot be parsed.
*/
public int getAuthenticationTimeout() throws GuacamoleException {
return environment.getProperty(SAML_AUTH_TIMEOUT, 5);
}
/**
* Returns the file containing the X.509 certificate to use when signing
* requests to the SAML IdP. If the property is not set, null will be
* returned.
*
* @return
* The file containing the X.509 certificate to use when signing
* requests to the SAML IdP, or null if not defined.
*
* @throws GuacamoleException
* If the X.509 certificate cannot be parsed.
*/
public File getCertificateFile() throws GuacamoleException {
return environment.getProperty(SAML_X509_CERT_PATH);
}
/**
* Returns the file containing the private key to use when signing
* requests to the SAML IdP. If the property is not set, null will be
* returned.
*
* @return
* The file containing the private key to use when signing
* requests to the SAML IdP, or null if not defined.
*
* @throws GuacamoleException
* If the private key file cannot be parsed.
*/
public File getPrivateKeyFile() throws GuacamoleException {
return environment.getProperty(SAML_PRIVATE_KEY_PATH);
}
/**
* Returns the contents of a small file, such as a private key or certificate into
* a String. If the file does not exist, or cannot be read for any reason, an exception
* will be thrown with the details of the failure.
*
* @param file
* The file to read into a string.
*
* @param name
* A human-readable name for the file, to be used when formatting log messages.
*
* @return
* The contents of the file having the given path.
*
* @throws GuacamoleException
* If the provided file does not exist, or cannot be read for any reason.
*/
private String readFileContentsIntoString(File file, String name) throws GuacamoleException {
// Attempt to read the file directly into a String
try {
return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
}
// If the file cannot be read, log a warning and treat it as if it does not exist
catch (IOException e) {
throw new GuacamoleServerException(
name + " at \"" + file.getAbsolutePath() + "\" could not be read.", e);
}
}
/**
* Returns the collection of SAML settings used to initialize the client.
*
* @return
* The collection of SAML settings used to initialize the SAML client.
*
* @throws GuacamoleException
* If guacamole.properties cannot be parsed or if required parameters
* are missing.
*/
public Saml2Settings getSamlSettings() throws GuacamoleException {
// Try to get the XML file, first.
URI idpMetadata = getIdpMetadata();
Map<String, Object> samlMap;
if (idpMetadata != null) {
try {
samlMap = IdPMetadataParser.parseRemoteXML(idpMetadata.toURL());
}
catch (Exception e) {
throw new GuacamoleServerException(
"Could not parse SAML IdP Metadata file.", e);
}
}
// If no XML metadata is provided, fall-back to individual values.
else {
samlMap = new HashMap<>();
samlMap.put(SettingsBuilder.IDP_ENTITYID_PROPERTY_KEY,
getIdpUrl().toString());
samlMap.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_URL_PROPERTY_KEY,
getIdpUrl().toString());
samlMap.put(SettingsBuilder.IDP_SINGLE_SIGN_ON_SERVICE_BINDING_PROPERTY_KEY,
Constants.BINDING_HTTP_REDIRECT);
}
// Read entity ID from properties if not provided within metadata XML
if (!samlMap.containsKey(SettingsBuilder.SP_ENTITYID_PROPERTY_KEY)) {
URI entityId = getEntityId();
if (entityId == null)
throw new GuacamoleServerException("SAML Entity ID was not found"
+ " in either the metadata XML file or guacamole.properties");
samlMap.put(SettingsBuilder.SP_ENTITYID_PROPERTY_KEY, entityId.toString());
}
// Derive ACS URL from properties if not provided within metadata XML
if (!samlMap.containsKey(SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_URL_PROPERTY_KEY)) {
samlMap.put(SettingsBuilder.SP_ASSERTION_CONSUMER_SERVICE_URL_PROPERTY_KEY,
UriBuilder.fromUri(getCallbackUrl()).path("api/ext/saml/callback").build().toString());
}
// If a private key file is set, load the value into the builder now
File privateKeyFile = getPrivateKeyFile();
if (privateKeyFile != null)
samlMap.put(SettingsBuilder.SP_PRIVATEKEY_PROPERTY_KEY,
readFileContentsIntoString(privateKeyFile, "Private Key"));
// If a certificate file is set, load the value into the builder now
File certificateFile = getCertificateFile();
if (certificateFile != null)
samlMap.put(SettingsBuilder.SP_X509CERT_PROPERTY_KEY,
readFileContentsIntoString(certificateFile, "X.509 Certificate"));
SettingsBuilder samlBuilder = new SettingsBuilder();
Saml2Settings samlSettings = samlBuilder.fromValues(samlMap).build();
samlSettings.setStrict(getStrict());
samlSettings.setDebug(getDebug());
samlSettings.setCompressRequest(getCompressRequest());
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;
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.conf;
import org.apache.guacamole.environment.DelegatingEnvironment;
import org.apache.guacamole.environment.LocalEnvironment;
/**
* An environment for retrieving SAML-related properties from the Guacamole
* configuration.
*/
public class SAMLEnvironment extends DelegatingEnvironment {
/**
* Create a new instance of the configuration environment for the
* SAML SSO module, pulling the default instance of the LocalEnvironment.
*/
public SAMLEnvironment() {
super(LocalEnvironment.getInstance());
}
}

View File

@@ -0,0 +1,127 @@
/*
* 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.user;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.saml.acs.AssertedIdentity;
import org.apache.guacamole.auth.saml.conf.ConfigurationService;
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.token.TokenName;
/**
* A SAML-specific implementation of AuthenticatedUser, associating a SAML
* identity and particular set of credentials with the SAML authentication
* provider.
*/
public class SAMLAuthenticatedUser extends SSOAuthenticatedUser {
/**
* The prefix that should be prepended to all parameter tokens generated
* from SAML attributes.
*/
private static final String SAML_ATTRIBUTE_TOKEN_PREFIX = "SAML_";
/**
* Service for retrieving SAML configuration information.
*/
@Inject
private ConfigurationService confService;
/**
* Returns a Map of all parameter tokens that should be made available for
* substitution based on the given {@link AssertedIdentity}. The resulting
* Map will contain one parameter token for each SAML attribute in the
* SAML response that originally asserted the user's identity. Attributes
* that have multiple values will be reduced to a single value, taking the
* first available value and discarding the remaining values.
*
* @param identity
* The {@link AssertedIdentity} representing the user identity
* asserted by the SAML IdP.
*
* @return
* A Map of key and single value pairs that should be made available
* for substitution as parameter tokens.
*/
private Map<String, String> getTokens(AssertedIdentity identity) {
return Collections.unmodifiableMap(identity.getAttributes().entrySet()
.stream()
.filter((entry) -> !entry.getValue().isEmpty())
.collect(Collectors.toMap(
(entry) -> TokenName.canonicalize(entry.getKey(), SAML_ATTRIBUTE_TOKEN_PREFIX),
(entry) -> entry.getValue().get(0)
)));
}
/**
* Returns a set of all group memberships asserted by the SAML IdP.
*
* @param identity
* The {@link AssertedIdentity} representing the user identity
* asserted by the SAML IdP.
*
* @return
* A set of all groups that the SAML IdP asserts this user is a
* member of.
*
* @throws GuacamoleException
* If the configuration information necessary to retrieve group
* memberships from a SAML response cannot be read.
*/
private Set<String> getGroups(AssertedIdentity identity)
throws GuacamoleException {
List<String> samlGroups = identity.getAttributes().get(confService.getGroupAttribute());
if (samlGroups == null || samlGroups.isEmpty())
return Collections.emptySet();
return Collections.unmodifiableSet(new HashSet<>(samlGroups));
}
/**
* Initializes this AuthenticatedUser using the given
* {@link AssertedIdentity} and credentials.
*
* @param identity
* The {@link AssertedIdentity} representing the user identity
* asserted by the SAML IdP.
*
* @param credentials
* The credentials provided when this user was authenticated.
*
* @throws GuacamoleException
* If configuration information required for processing the user's
* identity and group memberships cannot be read.
*/
public void init(AssertedIdentity identity, Credentials credentials)
throws GuacamoleException {
super.init(identity.getUsername(), credentials, getGroups(identity), getTokens(identity));
}
}

View File

@@ -0,0 +1,38 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "SAML Authentication Extension",
"namespace" : "saml",
"authProviders" : [
"org.apache.guacamole.auth.saml.SAMLAuthenticationProvider"
],
"listeners" : [
"org.apache.guacamole.auth.saml.SAMLAuthenticationEventListener"
],
"css" : [
"styles/sso-providers.css"
],
"html" : [
"html/sso-providers.html",
"html/sso-provider-saml.html"
],
"translations" : [
"translations/ca.json",
"translations/de.json",
"translations/en.json",
"translations/fr.json",
"translations/ja.json",
"translations/ko.json",
"translations/pl.json",
"translations/pt.json",
"translations/ru.json",
"translations/zh.json"
]
}

View File

@@ -0,0 +1,4 @@
<meta name="after-children" content=".login-ui .sso-provider-list">
<li class="sso-provider sso-provider-saml"><a href="api/ext/saml/login">{{
'LOGIN.NAME_IDP_SAML' | translate
}}</a></li>

View File

@@ -0,0 +1,18 @@
/*
* 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.
*/