diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java index d35c07dab..2b3bcee4b 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java @@ -63,6 +63,28 @@ public interface SSOAuthenticationProviderService { */ URI getLoginURI() throws GuacamoleException; + /** + * Returns the full URI of the logout endpoint to which a user should be + * redirected when they log out from Guacamole. This allows the user to + * also log out from the SSO identity provider. If no logout endpoint is + * configured, null is returned. + * + * @param idToken + * The ID token or authentication token from the user's session, if + * available. This may be used as an id_token_hint parameter in the + * logout request. May be null if not available. + * + * @return + * The full URI of the SSO logout endpoint, or null if not configured. + * + * @throws GuacamoleException + * If configuration information required for generating the logout URI + * cannot be read. + */ + default URI getLogoutURI(String idToken) throws GuacamoleException { + return null; + } + /** * Frees all resources associated with the relevant * SSOAuthenticationProvider implementation. This function is automatically @@ -70,5 +92,5 @@ public interface SSOAuthenticationProviderService { * down. */ void shutdown(); - + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java index 91bd39ba2..f4505006e 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java @@ -19,9 +19,11 @@ package org.apache.guacamole.auth.sso; import com.google.inject.Inject; +import java.net.URI; import javax.ws.rs.core.Response; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; import org.apache.guacamole.GuacamoleException; /** @@ -55,4 +57,33 @@ public class SSOResource { return Response.seeOther(authService.getLoginURI()).build(); } + /** + * Redirects the user to the identity provider's logout endpoint, if + * configured. This allows the user to log out from both Guacamole and the + * SSO identity provider. If no logout endpoint is configured, a 204 No + * Content response is returned. + * + * @param idToken + * The ID token from the user's authentication session, if available. + * This will be included as the id_token_hint parameter in the logout + * request. May be null. + * + * @return + * An HTTP Response that will redirect the user to the IdP logout + * endpoint if configured, or a 204 No Content response otherwise. + * + * @throws GuacamoleException + * If an error occurs preventing the redirect from being created. + */ + @GET + @Path("logout") + public Response getLogoutRedirect( + @QueryParam("id_token") String idToken) throws GuacamoleException { + URI logoutURI = authService.getLogoutURI(idToken); + if (logoutURI != null) + return Response.seeOther(logoutURI).build(); + else + return Response.noContent().build(); + } + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java index ca1be9e1f..dc559c6b7 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java @@ -130,9 +130,33 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS .build(); } + @Override + public URI getLogoutURI(String idToken) throws GuacamoleException { + + // If no logout endpoint is configured, return null + URI logoutEndpoint = confService.getLogoutEndpoint(); + if (logoutEndpoint == null) + return null; + + // Build the logout URI with appropriate parameters + UriBuilder logoutUriBuilder = UriBuilder.fromUri(logoutEndpoint); + + // Add post_logout_redirect_uri parameter + logoutUriBuilder.queryParam("post_logout_redirect_uri", + confService.getPostLogoutRedirectURI()); + + // Add id_token_hint if available, otherwise add client_id + if (idToken != null && !idToken.isEmpty()) + logoutUriBuilder.queryParam("id_token_hint", idToken); + else + logoutUriBuilder.queryParam("client_id", confService.getClientID()); + + return logoutUriBuilder.build(); + } + @Override public void shutdown() { // Nothing to clean up } - + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java index 96c6426f5..29aee3139 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java @@ -217,7 +217,32 @@ public class ConfigurationService { @Override public String getName() { return "openid-redirect-uri"; } - + + }; + + /** + * The logout endpoint (URI) of the OpenID service. If specified, users + * will be redirected to this endpoint when they log out, allowing them + * to log out from the OpenID provider as well. + */ + private static final URIGuacamoleProperty OPENID_LOGOUT_ENDPOINT = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "openid-logout-endpoint"; } + + }; + + /** + * The URI that the OpenID service should redirect to after logout is + * complete. If not specified, the main redirect URI will be used. + */ + private static final URIGuacamoleProperty OPENID_POST_LOGOUT_REDIRECT_URI = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "openid-post-logout-redirect-uri"; } + }; /** @@ -429,4 +454,36 @@ public class ConfigurationService { return environment.getProperty(OPENID_MAX_NONCE_VALIDITY, DEFAULT_MAX_NONCE_VALIDITY); } + /** + * Returns the logout endpoint (URI) of the OpenID service, as configured + * with guacamole.properties. If configured, users will be redirected to + * this endpoint when they log out from Guacamole. + * + * @return + * The logout endpoint of the OpenID service, as configured with + * guacamole.properties, or null if not configured. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getLogoutEndpoint() throws GuacamoleException { + return environment.getProperty(OPENID_LOGOUT_ENDPOINT); + } + + /** + * Returns the URI that the OpenID service should redirect to after logout + * is complete, as configured with guacamole.properties. If not configured, + * the main redirect URI will be used. + * + * @return + * The post-logout redirect URI, as configured with + * guacamole.properties, or the main redirect URI if not specified. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getPostLogoutRedirectURI() throws GuacamoleException { + return environment.getProperty(OPENID_POST_LOGOUT_REDIRECT_URI, getRedirectURI()); + } + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js new file mode 100644 index 000000000..61db13887 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js @@ -0,0 +1,40 @@ +/* + * 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. + */ + +/** + * The module for code implementing SSO using OpenID Connect. + */ +angular.module('guacSsoOpenid', []); + +/** + * Redirect to the SSO logout endpoint when the user logs out, allowing + * Single Logout to occur if configured. + */ +angular.module('guacSsoOpenid').run(['$rootScope', '$window', + function guacSsoOpenidLogout($rootScope, $window) { + + // Redirect to SSO logout endpoint when user logs out + $rootScope.$on('guacLogout', function handleLogout() { + $window.location.href = 'api/ext/openid/logout'; + }); + +}]); + +// Ensure the guacSsoOpenid module is loaded along with the rest of the app +angular.module('index').requires.push('guacSsoOpenid');