From d3d1a980da0168e4007de219344d2c97973dde6b Mon Sep 17 00:00:00 2001 From: Safa Bayar Date: Sat, 17 Jan 2026 22:23:52 -0500 Subject: [PATCH 1/5] GUACAMOLE-519: Implement Single Logout on OpenID Extension --- .../sso/SSOAuthenticationProviderService.java | 24 ++++++- .../guacamole/auth/sso/SSOResource.java | 31 ++++++++++ .../openid/AuthenticationProviderService.java | 26 +++++++- .../openid/conf/ConfigurationService.java | 62 ++++++++++++++++++- 4 files changed, 140 insertions(+), 3 deletions(-) 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(); + } + } From 12bcea5c657165b1fe469cf99736d5cf003f66b0 Mon Sep 17 00:00:00 2001 From: Safa Bayar Date: Sun, 18 Jan 2026 08:57:55 -0500 Subject: [PATCH 2/5] GUACAMOLE-519: Javascript fix in module to forward to openid logout endpoint --- .../src/main/resources/openidModule.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js 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..2e8877275 --- /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/sso/logout'; + }); + +}]); + +// Ensure the guacSsoOpenid module is loaded along with the rest of the app +angular.module('index').requires.push('guacSsoOpenid'); From 903fdebf947e68acadab3faa51efe1ca6b6c55ae Mon Sep 17 00:00:00 2001 From: Safa Bayar Date: Sun, 18 Jan 2026 20:34:17 -0500 Subject: [PATCH 3/5] GUACAMOLE-519: fix api endpoint forwarding to use logout address --- .../src/main/resources/openidModule.js | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 2e8877275..61db13887 100644 --- 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 @@ -31,7 +31,7 @@ angular.module('guacSsoOpenid').run(['$rootScope', '$window', // Redirect to SSO logout endpoint when user logs out $rootScope.$on('guacLogout', function handleLogout() { - $window.location.href = 'api/ext/sso/logout'; + $window.location.href = 'api/ext/openid/logout'; }); }]); diff --git a/pom.xml b/pom.xml index 7543dfd1c..8ae2a074d 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ referenced by this and all other pom.xml files within guacamole-client, and is a special case within Maven as one of the few properties allowed within the pom.xml version and intended for this purpose) --> - 1.6.1 + 1.6.0 32.1.3-jre From b5838b96b7506675a0ed56f688d3dcf5f7333a38 Mon Sep 17 00:00:00 2001 From: Safa Bayar Date: Sun, 18 Jan 2026 20:35:32 -0500 Subject: [PATCH 4/5] GUACAMOLE-519: revert pom.xml change --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ae2a074d..7543dfd1c 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ referenced by this and all other pom.xml files within guacamole-client, and is a special case within Maven as one of the few properties allowed within the pom.xml version and intended for this purpose) --> - 1.6.0 + 1.6.1 32.1.3-jre From 4763856847e0c90289da01cca3f60a7fca53f50c Mon Sep 17 00:00:00 2001 From: Safa Bayar Date: Sun, 25 Jan 2026 20:31:37 -0500 Subject: [PATCH 5/5] GUACAMOLE-519: MR code review change to simplify getPostLogoutRedirect --- .../guacamole/auth/openid/conf/ConfigurationService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 1ce8b74f5..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 @@ -483,10 +483,7 @@ public class ConfigurationService { * 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(); + return environment.getProperty(OPENID_POST_LOGOUT_REDIRECT_URI, getRedirectURI()); } }