From beea7a15b8745a58f75d43e988c65f23fe2c510f Mon Sep 17 00:00:00 2001 From: Jason Pickering Date: Fri, 9 Jan 2026 08:50:23 +0100 Subject: [PATCH] Add support for configurable Clear-Site-Data directive on logout --- .../oidc/DhisOidcLogoutSuccessHandler.java | 9 ++ .../DhisOidcLogoutSuccessHandlerTest.java | 97 +++++++++++++++++++ .../dhis/external/conf/ConfigurationKey.java | 2 + 3 files changed, 108 insertions(+) create mode 100644 dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandlerTest.java diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandler.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandler.java index b5ec78fdc30a..fb67e3e7e4b5 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandler.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandler.java @@ -30,6 +30,7 @@ package org.hisp.dhis.security.oidc; import static com.google.common.base.Strings.isNullOrEmpty; +import static org.hisp.dhis.external.conf.ConfigurationKey.HTTP_CLEAR_SITE_DATA; import static org.hisp.dhis.external.conf.ConfigurationKey.LINKED_ACCOUNTS_ENABLED; import static org.hisp.dhis.external.conf.ConfigurationKey.LINKED_ACCOUNTS_LOGOUT_URL; import static org.hisp.dhis.external.conf.ConfigurationKey.LINKED_ACCOUNTS_RELOGIN_URL; @@ -92,6 +93,14 @@ private void setOidcLogoutUrl() { public void onLogoutSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + String clearSiteDataValue = config.getProperty(HTTP_CLEAR_SITE_DATA); + if (!isNullOrEmpty(clearSiteDataValue)) { + String headerValue = + DhisConfigurationProvider.isOn(clearSiteDataValue) + ? "\"cache\", \"storage\"" + : clearSiteDataValue; + response.setHeader("Clear-Site-Data", headerValue); + } if (config.isEnabled(OIDC_OAUTH2_LOGIN_ENABLED) && config.isEnabled(LINKED_ACCOUNTS_ENABLED)) { handleLinkedAccountsLogout(request, response, authentication); return; diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandlerTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandlerTest.java new file mode 100644 index 000000000000..4bcf539ee0fa --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/security/oidc/DhisOidcLogoutSuccessHandlerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2004-2025, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.security.oidc; + +import static org.hisp.dhis.external.conf.ConfigurationKey.HTTP_CLEAR_SITE_DATA; +import static org.hisp.dhis.external.conf.ConfigurationKey.OIDC_OAUTH2_LOGIN_ENABLED; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.hisp.dhis.external.conf.DhisConfigurationProvider; +import org.hisp.dhis.user.UserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * @author Jason P. Pickering + */ +@ExtendWith(MockitoExtension.class) +class DhisOidcLogoutSuccessHandlerTest { + + @Mock private DhisConfigurationProvider config; + @Mock private DhisOidcProviderRepository dhisOidcProviderRepository; + @Mock private UserService userService; + @Mock private HttpServletRequest request; + @Mock private HttpServletResponse response; + + private DhisOidcLogoutSuccessHandler handler; + + @BeforeEach + void setUp() { + handler = new DhisOidcLogoutSuccessHandler(config, dhisOidcProviderRepository, userService); + when(config.isEnabled(OIDC_OAUTH2_LOGIN_ENABLED)).thenReturn(false); + handler.init(); + } + + @Test + void setsClearSiteDataHeaderWhenOn() throws Exception { + when(config.getProperty(HTTP_CLEAR_SITE_DATA)).thenReturn("on"); + + handler.onLogoutSuccess(request, response, null); + + verify(response).setHeader("Clear-Site-Data", "\"cache\", \"storage\""); + } + + @Test + void setsClearSiteDataHeaderFromExplicitValue() throws Exception { + when(config.getProperty(HTTP_CLEAR_SITE_DATA)).thenReturn("\"cookies\""); + + handler.onLogoutSuccess(request, response, null); + + verify(response).setHeader("Clear-Site-Data", "\"cookies\""); + } + + @Test + void doesNotSetClearSiteDataHeaderWhenMissing() throws Exception { + when(config.getProperty(HTTP_CLEAR_SITE_DATA)).thenReturn(""); + + handler.onLogoutSuccess(request, response, null); + + verify(response, never()).setHeader(eq("Clear-Site-Data"), anyString()); + } +} diff --git a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java index 9f3cdde267ce..d8c4641f36d5 100644 --- a/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java +++ b/dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java @@ -804,6 +804,8 @@ public enum ConfigurationKey { // Enable saved requests, this will save the URL the user tries to access before they are logged // in, and redirect to that URL after they are logged in. LOGIN_SAVED_REQUESTS_ENABLE("login.saved.requests.enable", Constants.ON, false), + /** Clear-Site-Data response header value set on logout. */ + HTTP_CLEAR_SITE_DATA("http.clear_site_data", "", false), // OIDC internal provider configuration for DHIS2 when Authorization Server is enabled, with // property: (OAUTH2_SERVER_ENABLED)