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..1ce8b74f5 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,39 @@ 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 { + URI postLogoutRedirectURI = environment.getProperty(OPENID_POST_LOGOUT_REDIRECT_URI); + if (postLogoutRedirectURI != null) + return postLogoutRedirectURI; + return getRedirectURI(); + } + }