Changing locale on logout confirmation did not work

Closes #11951
This commit is contained in:
mposolda 2022-05-30 12:06:39 +02:00 committed by Marek Posolda
parent 8f54f03f17
commit f90fbb9c71
6 changed files with 274 additions and 48 deletions

View File

@ -494,6 +494,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
case REGISTER:
b = UriBuilder.fromUri(Urls.realmRegisterPage(baseUri, realm.getName()));
break;
case LOGOUT_CONFIRM:
b = UriBuilder.fromUri(Urls.logoutConfirm(baseUri, realm.getName()));
break;
default:
b = UriBuilder.fromUri(baseUri).path(uriInfo.getPath());
break;

View File

@ -38,6 +38,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.SystemClientUtil;
import org.keycloak.protocol.oidc.BackchannelLogoutResponse;
@ -65,6 +66,7 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.LogoutSessionCodeChecks;
import org.keycloak.services.resources.SessionCodeChecks;
import org.keycloak.services.util.LocaleUtil;
import org.keycloak.services.util.MtlsHoKTokenUtil;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
@ -258,9 +260,12 @@ public class LogoutEndpoint {
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
.setAuthenticationSession(logoutSession);
UserSessionModel userSession = null;
// Check if we have session in the browser. If yes and it is different session than referenced by id_token_hint, the confirmation should be displayed
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
userSession = authResult.getSession();
if (idToken != null && idToken.getSessionState() != null && !idToken.getSessionState().equals(authResult.getSession().getId())) {
forcedConfirmation = true;
}
@ -272,6 +277,17 @@ public class LogoutEndpoint {
}
}
if (userSession == null && idToken != null && idToken.getSessionState() != null) {
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
}
// Try to figure user because of localization
if (userSession != null) {
UserModel user = userSession.getUser();
logoutSession.setAuthenticatedUser(user);
loginForm.setUser(user);
}
// Logout confirmation screen will be displayed to the user in this case
if (confirmationNeeded || forcedConfirmation) {
return displayLogoutConfirmationScreen(loginForm, logoutSession);
@ -297,6 +313,7 @@ public class LogoutEndpoint {
* @return response
*/
@POST
@NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response logout() {
MultivaluedMap<String, String> form = request.getDecodedFormParameters();
@ -315,6 +332,7 @@ public class LogoutEndpoint {
@Path("/logout-confirm")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response logoutConfirmAction() {
MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
@ -327,7 +345,7 @@ public class LogoutEndpoint {
SessionCodeChecks checks = new LogoutSessionCodeChecks(realm, session.getContext().getUri(), request, clientConnection, session, event, code, clientId, tabId);
checks.initialVerify();
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.LOGGING_OUT.name(), ClientSessionCode.ActionType.USER) || !formData.containsKey("confirmLogout")) {
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.LOGGING_OUT.name(), ClientSessionCode.ActionType.USER) || !checks.isActionRequest() || !formData.containsKey("confirmLogout")) {
AuthenticationSessionModel logoutSession = checks.getAuthenticationSession();
logger.debugf("Failed verification during logout. logoutSessionId=%s, clientId=%s, tabId=%s",
logoutSession != null ? logoutSession.getParentSession().getId() : "unknown", clientId, tabId);
@ -347,6 +365,48 @@ public class LogoutEndpoint {
}
// Typically shown when user changes localization on the logout confirmation screen
@Path("/logout-confirm")
@NoCache
@GET
public Response logoutConfirmGet() {
event.event(EventType.LOGOUT);
String clientId = session.getContext().getUri().getQueryParameters().getFirst(Constants.CLIENT_ID);
String tabId = session.getContext().getUri().getQueryParameters().getFirst(Constants.TAB_ID);
logger.tracef("Changing localization by user during logout. clientId=%s, tabId=%s, kc_locale: %s", clientId, tabId, session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM));
SessionCodeChecks checks = new LogoutSessionCodeChecks(realm, session.getContext().getUri(), request, clientConnection, session, event, null, clientId, tabId);
AuthenticationSessionModel logoutSession = checks.initialVerifyAuthSession();
if (logoutSession == null) {
logger.debugf("Failed verification when changing locale logout. clientId=%s, tabId=%s", clientId, tabId);
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class);
if (clientId == null || clientId.equals(SystemClientUtil.getSystemClient(realm).getClientId())) {
// Cleanup system client URL to avoid links to account management
loginForm.setAttribute(Constants.SKIP_LINK, true);
}
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
return ErrorPage.error(session, logoutSession, Response.Status.BAD_REQUEST, Messages.FAILED_LOGOUT);
} else {
// Probably changing locale on logout screen after logout was already performed. If there is no session in the browser, we can just display that logout was already finished
return loginForm.setSuccess(Messages.SUCCESS_LOGOUT).createInfoPage();
}
}
LocaleUtil.processLocaleParam(session, realm, logoutSession);
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
.setAuthenticationSession(logoutSession)
.setUser(logoutSession.getAuthenticatedUser());
return displayLogoutConfirmationScreen(loginForm, logoutSession);
}
// Method triggered after user eventually confirmed that he wants to logout and all other checks were done
private Response doBrowserLogout(AuthenticationSessionModel logoutSession) {
UserSessionModel userSession = null;

View File

@ -374,10 +374,10 @@ public class AuthenticationManager {
} else {
logoutAuthSession = rootLogoutSession.createAuthenticationSession(client);
logoutAuthSession.setAction(AuthenticationSessionModel.Action.LOGGING_OUT.name());
session.getContext().setClient(client);
logger.tracef("Creating logout session for client '%s'. Authentication session id: %s", client.getClientId(), rootLogoutSession.getId());
}
session.getContext().setAuthenticationSession(logoutAuthSession);
session.getContext().setClient(client);
return logoutAuthSession;
}

View File

@ -83,6 +83,7 @@ import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.LocaleUtil;
import org.keycloak.sessions.AuthenticationSessionCompoundId;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel;
@ -277,15 +278,7 @@ public class LoginActionsService {
}
protected void processLocaleParam(AuthenticationSessionModel authSession) {
if (authSession != null && realm.isInternationalizationEnabled()) {
String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM);
if (locale != null) {
authSession.setAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE, locale);
LocaleUpdaterProvider localeUpdater = session.getProvider(LocaleUpdaterProvider.class);
localeUpdater.updateLocaleCookie(locale);
}
}
LocaleUtil.processLocaleParam(session, realm, authSession);
}
protected Response processAuthentication(boolean action, String execution, AuthenticationSessionModel authSession, String errorMessage) {

View File

@ -0,0 +1,43 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.
*
*/
package org.keycloak.services.util;
import org.keycloak.locale.LocaleSelectorProvider;
import org.keycloak.locale.LocaleUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LocaleUtil {
public static void processLocaleParam(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession) {
if (authSession != null && realm.isInternationalizationEnabled()) {
String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM);
if (locale != null) {
authSession.setAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE, locale);
LocaleUpdaterProvider localeUpdater = session.getProvider(LocaleUpdaterProvider.class);
localeUpdater.updateLocaleCookie(locale);
}
}
}
}

View File

@ -21,6 +21,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.hamcrest.MatcherAssert;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule;
@ -35,6 +36,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.IDToken;
@ -59,13 +61,14 @@ import java.util.Map;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
@ -76,6 +79,7 @@ import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.pages.PageUtils;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.updaters.UserAttributeUpdater;
import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
import org.keycloak.testsuite.util.Matchers;
import org.keycloak.testsuite.util.OAuthClient;
@ -144,7 +148,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
driver.navigate().to(logoutUrl);
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
assertCurrentUrlEquals(redirectUri);
@ -157,7 +161,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(redirectUri).idTokenHint(idTokenString).state("something").build();
driver.navigate().to(logoutUrl);
events.expectLogout(sessionId2).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId2)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId2)));
assertCurrentUrlEquals(redirectUri + "&state=something");
}
@ -175,7 +179,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
driver.navigate().to(logoutUrl);
events.expectLogout(sessionId).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
assertCurrentUrlEquals(redirectUri);
@ -192,7 +196,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
logoutConfirmPage.assertCurrent();
logoutConfirmPage.confirmLogout();
events.expectLogout(sessionId2).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId2)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId2)));
assertCurrentUrlEquals(redirectUri + "&state=something");
}
@ -215,7 +219,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// should not throw an internal server error. But no logout event is sent as nothing was logged-out
appPage.assertCurrent();
events.assertEmpty();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
// check if the back channel logout succeeded
driver.navigate().to(oauth.getLoginFormUrl());
@ -266,7 +270,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
String idTokenString = tokenResponse.getIdToken();
adminClient.realm("test").logoutAll();
Assert.assertThat(false, is(isSessionActive(sessionId)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
// Try logout even if user already logged-out by admin. Should redirect back to the application, but no logout-event should be triggered
String logoutUrl = oauth.getLogoutUrl().postLogoutRedirectUri(APP_REDIRECT_URI).idTokenHint(idTokenString).build();
@ -283,7 +287,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
driver.navigate().to(logoutUrl);
events.expectLogout(sessionId2).detail(Details.REDIRECT_URI, APP_REDIRECT_URI).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId2)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId2)));
}
@ -310,7 +314,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
events.expectLogoutError(OAuthErrorException.INVALID_TOKEN).assertEvent();
// Session still authenticated
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
}
@Test
@ -333,7 +337,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
}
events.assertEmpty();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
}
@Test
@ -356,7 +360,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
}
events.assertEmpty();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -379,7 +383,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
events.expectLogoutError(OAuthErrorException.INVALID_REQUEST).assertEvent();
// Assert user still authenticated
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -395,7 +399,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
events.expectLogoutError(OAuthErrorException.INVALID_REQUEST).assertEvent();
// Assert user still authenticated
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -416,7 +420,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
events.expectLogoutError(OAuthErrorException.INVALID_REDIRECT_URI).detail(Details.REDIRECT_URI, rootUrlClientRedirectUri).assertEvent();
// Session still authenticated
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -438,7 +442,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
events.expectLogoutError(OAuthErrorException.INVALID_TOKEN).removeDetail(Details.REDIRECT_URI).assertEvent();
// Session still authenticated
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -451,7 +455,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page. Session still exists
logoutConfirmPage.assertCurrent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
logoutConfirmPage.confirmLogout();
@ -467,7 +471,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
}
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -483,11 +487,11 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals("You are logged out", infoPage.getInfo());
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
infoPage.clickBackToApplicationLink();
WaitUtils.waitForPageToLoad();
Assert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
MatcherAssert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
}
@ -500,7 +504,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page. Session still exists
logoutConfirmPage.assertCurrent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
// Set time offset to expire "action" inside logoutSession
@ -511,7 +515,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals("Logout failed", errorPage.getError());
events.expectLogoutError(Errors.EXPIRED_CODE).assertEvent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
// Link not present
try {
@ -532,7 +536,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page. Session still exists
logoutConfirmPage.assertCurrent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
// Set time offset to expire "action" inside logoutSession
@ -558,7 +562,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Logout confirmation page not shown as id_token_hint was included.
// Redirected back to the application with expected "state"
events.expectLogout(tokenResponse.getSessionState()).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
assertCurrentUrlEquals(APP_REDIRECT_URI + "?state=somethingg");
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
@ -578,19 +582,19 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page. Session still exists. Assert czech language on logout page
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
logoutConfirmPage.confirmLogout();
// Info page present with the link "Back to application"
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
infoPage.assertCurrent();
Assert.assertEquals("Odhlášení bylo úspěšné", infoPage.getInfo()); // Logout success message
infoPage.clickBackToApplicationLinkCs();
WaitUtils.waitForPageToLoad();
Assert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
MatcherAssert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
}
}
@ -603,7 +607,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page. Session still exists
logoutConfirmPage.assertCurrent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
// Set time offset to expire "action" inside logoutSession
@ -614,11 +618,11 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals("Logout failed", errorPage.getError());
events.expectLogoutError(Errors.EXPIRED_CODE).assertEvent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
// Link "Back to application" present
errorPage.clickBackToApplication();
Assert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
MatcherAssert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
}
@ -632,13 +636,13 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Assert logout confirmation page as id_token_hint was not sent. Session still exists. Assert default language on logout page (English)
logoutConfirmPage.assertCurrent();
Assert.assertEquals("English", logoutConfirmPage.getLanguageDropdownText());
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
events.assertEmpty();
logoutConfirmPage.confirmLogout();
// Redirected back to the application with expected "state"
events.expectLogout(tokenResponse.getSessionState()).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
assertCurrentUrlEquals(APP_REDIRECT_URI + "?state=somethingg");
}
@ -657,7 +661,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
// Logout done and redirected back to the application with expected "state"
events.expectLogout(tokenResponse.getSessionState()).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
assertCurrentUrlEquals(APP_REDIRECT_URI + "?state=somethingg");
// Test logout only with "client_id" and "post_logout_redirect_uri". Should automatically redirect as there is no logout (No active browser session)
@ -687,7 +691,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals("Invalid parameter: id_token_hint", errorPage.getError());
events.expectLogoutError(Errors.INVALID_TOKEN).client("third-party").assertEvent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
// Case when client_id is non-existing client and redirect uri of different client is used
logoutUrl = oauth.getLogoutUrl()
@ -699,7 +703,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals("Invalid redirect uri", errorPage.getError());
events.expectLogoutError(Errors.INVALID_REDIRECT_URI).assertEvent();
Assert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(true, is(isSessionActive(tokenResponse.getSessionState())));
// Case when client_id is non-existing client. Confirmation is needed.
logoutUrl = oauth.getLogoutUrl()
@ -721,7 +725,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
}
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
Assert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
MatcherAssert.assertThat(false, is(isSessionActive(tokenResponse.getSessionState())));
}
@ -744,7 +748,7 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
URLUtils.sendPOSTRequestWithWebDriver(oauth.getLogoutUrl().build(), postParams);
events.expectLogout(tokenResponse.getSessionState()).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
assertCurrentUrlEquals(redirectUri + "&state=my-state");
// Logout with showing confirmation screen
@ -764,12 +768,135 @@ public class RPInitiatedLogoutTest extends AbstractTestRealmKeycloakTest {
WaitUtils.waitForPageToLoad();
events.expectLogout(tokenResponse.getSessionState()).detail(Details.REDIRECT_URI, redirectUri).assertEvent();
Assert.assertThat(false, is(isSessionActive(sessionId)));
MatcherAssert.assertThat(false, is(isSessionActive(sessionId)));
assertCurrentUrlEquals(redirectUri + "&state=my-state-2");
}
}
@Test
public void testLocalizationPreferenceDuringLogout() throws IOException {
try (RealmAttributeUpdater realmUpdater = new RealmAttributeUpdater(testRealm()).addSupportedLocale("cs").update()) {
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
// Set localization to the user account to "cs". Ensure that it is shown
try (UserAttributeUpdater userUpdater = UserAttributeUpdater.forUserByUsername(testRealm(), "test-user@localhost").setAttribute(UserModel.LOCALE, "cs").update()) {
driver.navigate().to(oauth.getLogoutUrl().build());
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
// Set localization together with ui_locales param. User localization should have preference
driver.navigate().to(oauth.getLogoutUrl().uiLocales("de").build());
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
}
UserAttributeUpdater.forUserByUsername(testRealm(), "test-user@localhost").removeAttribute(UserModel.LOCALE).update();
// Removed localization from user account. Now localization set by ui_locales parameter should be used
driver.navigate().to(oauth.getLogoutUrl().uiLocales("de").build());
Assert.assertEquals("Abmelden", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Deutsch", logoutConfirmPage.getLanguageDropdownText());
logoutConfirmPage.confirmLogout();
WaitUtils.waitForPageToLoad();
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
// Remove ui_locales from logout request. Default locale should be set
tokenResponse = loginUser();
driver.navigate().to(oauth.getLogoutUrl().build());
Assert.assertEquals("Logging out", PageUtils.getPageTitle(driver));
Assert.assertEquals("English", logoutConfirmPage.getLanguageDropdownText());
logoutConfirmPage.confirmLogout();
WaitUtils.waitForPageToLoad();
events.expectLogout(tokenResponse.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent();
}
}
@Test
public void testLocalizationDuringLogout() throws IOException {
try (RealmAttributeUpdater realmUpdater = new RealmAttributeUpdater(testRealm()).addSupportedLocale("cs").update()) {
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
// Display the logout page. Then change the localization to Czech, then back to english and then and logout
driver.navigate().to(oauth.getLogoutUrl().build());
logoutConfirmPage.assertCurrent();
logoutConfirmPage.openLanguage("Čeština");
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
logoutConfirmPage.openLanguage("English");
Assert.assertEquals("Logging out", PageUtils.getPageTitle(driver));
Assert.assertEquals("English", logoutConfirmPage.getLanguageDropdownText());
// Logout
logoutConfirmPage.confirmLogout();
infoPage.assertCurrent();
Assert.assertEquals("You are logged out", infoPage.getInfo());
try {
logoutConfirmPage.clickBackToApplicationLink();
fail();
}
catch (NoSuchElementException ex) {
// expected
}
// Display logout with ui_locales parameter set to "de"
tokenResponse = loginUser();
driver.navigate().to(oauth.getLogoutUrl()
.clientId("test-app")
.uiLocales("de")
.build());
Assert.assertEquals("Abmelden", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Deutsch", logoutConfirmPage.getLanguageDropdownText());
// Change locale. It should have preference over the "de" set by ui_locales
logoutConfirmPage.openLanguage("Čeština");
Assert.assertEquals("Odhlašování", PageUtils.getPageTitle(driver)); // Logging out
Assert.assertEquals("Čeština", logoutConfirmPage.getLanguageDropdownText());
// Logout
logoutConfirmPage.confirmLogout();
infoPage.assertCurrent();
Assert.assertEquals("Odhlášení bylo úspěšné", infoPage.getInfo()); // Logout success message
infoPage.clickBackToApplicationLinkCs();
WaitUtils.waitForPageToLoad();
MatcherAssert.assertThat(driver.getCurrentUrl(), endsWith("/app/auth"));
}
}
@Test
public void testIncorrectChangingParameters() throws IOException {
OAuthClient.AccessTokenResponse tokenResponse = loginUser();
// Display the logout page. Then change the localization to Czech and logout
driver.navigate().to(oauth.getLogoutUrl().uiLocales("de").build());
Assert.assertEquals("Abmelden", PageUtils.getPageTitle(driver)); // Logging out
logoutConfirmPage.openLanguage("English");
// Try to manually change value of parameter tab_id to some incorrect value. Error should be shown in this case
String currentUrl = driver.getCurrentUrl();
String changedUrl = UriBuilder.fromUri(currentUrl)
.replaceQueryParam(Constants.TAB_ID, "invalid")
.build().toString();
driver.navigate().to(changedUrl);
WaitUtils.waitForPageToLoad();
errorPage.assertCurrent();
Assert.assertEquals("Logout failed", errorPage.getError());
events.expectLogoutError(Errors.SESSION_EXPIRED).assertEvent();
}
@Test
public void testFrontChannelLogoutWithPostLogoutRedirectUri() throws Exception {
ClientsResource clients = adminClient.realm(oauth.getRealm()).clients();