From 286b7337ab22283941ccc2e23f1cd397dc3950a3 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 10 Jun 2024 11:20:40 +0200 Subject: [PATCH] Logout from all clients after IdP logout is performed Closes #25234 Signed-off-by: rmartinc (cherry picked from commit 7d05a7a013495a8c59c3bdc71a04f743d3391b34) --- .../managers/AuthenticationManager.java | 14 ++--- .../broker/AbstractBaseBrokerTest.java | 2 +- .../broker/KcOidcBrokerLogoutTest.java | 53 ++++++++++++++++++- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 179ec9378670..c8abe2a0708f 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -643,16 +643,11 @@ public static Response browserLogout(KeycloakSession session, final AuthenticationSessionManager asm = new AuthenticationSessionManager(session); AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, true); - Response response = browserLogoutAllClients(userSession, session, realm, headers, uriInfo, logoutAuthSession); - if (response != null) { - return response; - } - String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER); String initiatingIdp = logoutAuthSession.getAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP); if (brokerId != null && !brokerId.equals(initiatingIdp)) { IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId); - response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm); + Response response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm); if (response != null) { return response; } @@ -686,6 +681,11 @@ public static Response finishBrowserLogout(KeycloakSession session, RealmModel r final AuthenticationSessionManager asm = new AuthenticationSessionManager(session); AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, true); + Response response = browserLogoutAllClients(userSession, session, realm, headers, uriInfo, logoutAuthSession); + if (response != null) { + return response; + } + checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession); // For resolving artifact we don't need any cookie, all details are stored in session storage so we can remove @@ -701,7 +701,7 @@ public static Response finishBrowserLogout(KeycloakSession session, RealmModel r .setEventBuilder(event); - Response response = protocol.finishBrowserLogout(userSession, logoutAuthSession); + response = protocol.finishBrowserLogout(userSession, logoutAuthSession); // It may be possible that there are some client sessions that are still in LOGGING_OUT state long numberOfUnconfirmedSessions = userSession.getAuthenticatedClientSessions().values().stream() diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java index b55e7126ca83..0f28b163ef2a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBaseBrokerTest.java @@ -338,7 +338,7 @@ protected void executeLogoutFromRealm(String contextRoot, String realm, String i .clientId(clientId) .initiatingIdp(initiatingIdp); - if (clientId != null || idTokenHint != null) { + if (redirectUri != null && (clientId != null || idTokenHint != null)) { builder.postLogoutRedirectUri(encodeUrl(redirectUri)); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java index d0bce18f0f13..733bdaab0620 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.broker; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; @@ -8,11 +9,14 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.common.VerificationException; import org.keycloak.cookie.CookieType; +import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.WaitUtils; import static org.junit.Assert.assertEquals; import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME; @@ -22,7 +26,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest { @@ -162,7 +165,6 @@ public void testFrontChannelLogoutRequestsSendingOnlyClientId() { identityProviderResource.update(representation); logInAsUserInIDPForFirstTime(); appPage.assertCurrent(); - driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.DAYS); executeLogoutFromRealm( getConsumerRoot(), bc.consumerRealmName(), @@ -231,4 +233,51 @@ public void testFrontChannelLogoutRequestsSendingOnlyIdTokenHint() throws Verifi identityProviderResource.update(representation); } } + + @Test + public void testFrontChannelLogoutRequestsSendingOnlyClientIdWithFrontChannelLogoutApp() throws Exception { + RealmResource realm = adminClient.realm(bc.consumerRealmName()); + IdentityProviderResource identityProviderResource = realm.identityProviders().get(bc.getIDPAlias()); + IdentityProviderRepresentation representation = identityProviderResource.toRepresentation(); + Map config = representation.getConfig(); + Map originalConfig = new HashMap<>(config); + + try (ClientAttributeUpdater clientUpdater = ClientAttributeUpdater.forClient(adminClient, bc.consumerRealmName(), "broker-app") + .setFrontchannelLogout(true) + .setAttribute(OIDCConfigAttributes.FRONT_CHANNEL_LOGOUT_URI, getConsumerRoot() + "/auth/realms/" + bc.consumerRealmName() + "/app/logout") + .update()){ + config.put("backchannelSupported", Boolean.FALSE.toString()); + config.put("sendIdTokenOnLogout", Boolean.FALSE.toString()); + config.put("sendClientIdOnLogout", Boolean.TRUE.toString()); + identityProviderResource.update(representation); + logInAsUserInIDPForFirstTime(); + appPage.assertCurrent(); + executeLogoutFromRealm( + getConsumerRoot(), + bc.consumerRealmName(), + "something-else", + null, + "broker-app", + null + ); + logoutConfirmPage.isCurrent(); + // confirm logout at consumer + logoutConfirmPage.confirmLogout(); + // confirm logout at provider + logoutConfirmPage.confirmLogout(); + + WaitUtils.waitForPageToLoad(); + logoutConfirmPage.isCurrent(); + Assert.assertTrue(driver.getPageSource().contains("You are logging out from following apps")); + Assert.assertTrue(driver.getPageSource().contains("broker-app")); + + oauth.clientId("account"); + oauth.redirectUri(getConsumerRoot() + "/auth/realms/" + REALM_PROV_NAME + "/account"); + loginPage.open(REALM_PROV_NAME); + waitForPage(driver, "sign in to provider", true); + } finally { + representation.setConfig(originalConfig); + identityProviderResource.update(representation); + } + } }