Logout from all clients after IdP logout is performed

Closes #25234

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-06-10 11:20:40 +02:00 committed by Pedro Igor
parent 22da43c619
commit 7d05a7a013
3 changed files with 59 additions and 10 deletions

View file

@ -645,16 +645,11 @@ public class AuthenticationManager {
final AuthenticationSessionManager asm = new AuthenticationSessionManager(session); final AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, true); 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 brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
String initiatingIdp = logoutAuthSession.getAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP); String initiatingIdp = logoutAuthSession.getAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP);
if (brokerId != null && !brokerId.equals(initiatingIdp)) { if (brokerId != null && !brokerId.equals(initiatingIdp)) {
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId); 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) { if (response != null) {
return response; return response;
} }
@ -688,6 +683,11 @@ public class AuthenticationManager {
final AuthenticationSessionManager asm = new AuthenticationSessionManager(session); final AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, true); 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); checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
// For resolving artifact we don't need any cookie, all details are stored in session storage so we can remove // For resolving artifact we don't need any cookie, all details are stored in session storage so we can remove
@ -703,7 +703,7 @@ public class AuthenticationManager {
.setEventBuilder(event); .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 // It may be possible that there are some client sessions that are still in LOGGING_OUT state
long numberOfUnconfirmedSessions = userSession.getAuthenticatedClientSessions().values().stream() long numberOfUnconfirmedSessions = userSession.getAuthenticatedClientSessions().values().stream()

View file

@ -342,7 +342,7 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
.clientId(clientId) .clientId(clientId)
.initiatingIdp(initiatingIdp); .initiatingIdp(initiatingIdp);
if (clientId != null || idTokenHint != null) { if (redirectUri != null && (clientId != null || idTokenHint != null)) {
builder.postLogoutRedirectUri(encodeUrl(redirectUri)); builder.postLogoutRedirectUri(encodeUrl(redirectUri));
} }

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import org.junit.Assert;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
@ -8,11 +9,14 @@ import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.cookie.CookieType; import org.keycloak.cookie.CookieType;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.AccountHelper;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.WaitUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME; import static org.keycloak.testsuite.broker.BrokerTestConstants.REALM_CONS_NAME;
@ -22,7 +26,6 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest { public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest {
@ -162,7 +165,6 @@ public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest {
identityProviderResource.update(representation); identityProviderResource.update(representation);
logInAsUserInIDPForFirstTime(); logInAsUserInIDPForFirstTime();
appPage.assertCurrent(); appPage.assertCurrent();
driver.manage().timeouts().pageLoadTimeout(1, TimeUnit.DAYS);
executeLogoutFromRealm( executeLogoutFromRealm(
getConsumerRoot(), getConsumerRoot(),
bc.consumerRealmName(), bc.consumerRealmName(),
@ -231,4 +233,51 @@ public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest {
identityProviderResource.update(representation); 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<String, String> config = representation.getConfig();
Map<String, String> 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);
}
}
} }