Remove root auth session after backchannel logout

Closes #32197

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Giuseppe Graziano 2024-09-23 15:28:40 +02:00 committed by Marek Posolda
parent cb765c8d73
commit b46fab2308
8 changed files with 65 additions and 40 deletions

View file

@ -308,8 +308,7 @@ public class AuthenticationManager {
checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession); checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
} finally { } finally {
logger.tracef("Removing logout session '%s' after backchannel logout", logoutAuthSession.getParentSession().getId()); logger.tracef("Removing logout session '%s' after backchannel logout", logoutAuthSession.getParentSession().getId());
RootAuthenticationSessionModel rootAuthSession = logoutAuthSession.getParentSession(); session.authenticationSessions().removeRootAuthenticationSession(realm, logoutAuthSession.getParentSession());
rootAuthSession.removeAuthenticationSessionByTabId(logoutAuthSession.getTabId());
} }
userSession.setState(UserSessionModel.State.LOGGED_OUT); userSession.setState(UserSessionModel.State.LOGGED_OUT);

View file

@ -79,6 +79,10 @@ public class BrowserTabUtil implements AutoCloseable {
return instance; return instance;
} }
public static void cleanup() {
instances = new ArrayList<>();
}
public WebDriver getDriver() { public WebDriver getDriver() {
return driver; return driver;
} }

View file

@ -55,6 +55,7 @@ import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.auth.page.login.UpdatePassword; import org.keycloak.testsuite.auth.page.login.UpdatePassword;
import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.util.BrowserTabUtil;
import org.keycloak.testsuite.util.CryptoInitRule; import org.keycloak.testsuite.util.CryptoInitRule;
import org.keycloak.testsuite.util.DroneUtils; import org.keycloak.testsuite.util.DroneUtils;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
@ -258,6 +259,7 @@ public abstract class AbstractKeycloakTest {
// Remove all browsers from queue // Remove all browsers from queue
DroneUtils.resetQueue(); DroneUtils.resetQueue();
BrowserTabUtil.cleanup();
} }
protected TestCleanup getCleanup(String realmName) { protected TestCleanup getCleanup(String realmName) {

View file

@ -49,6 +49,7 @@ import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginTotpPage; import org.keycloak.testsuite.pages.LoginTotpPage;
import org.keycloak.testsuite.pages.LoginUsernameOnlyPage; import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
import org.keycloak.testsuite.pages.PasswordPage; import org.keycloak.testsuite.pages.PasswordPage;
import org.keycloak.testsuite.util.BrowserTabUtil;
import org.keycloak.testsuite.util.FederatedIdentityBuilder; import org.keycloak.testsuite.util.FederatedIdentityBuilder;
import org.keycloak.testsuite.util.FlowUtil; import org.keycloak.testsuite.util.FlowUtil;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
@ -366,6 +367,42 @@ public class ReAuthenticationTest extends AbstractTestRealmKeycloakTest {
realmsResouce().realm(rep.getRealm()).update(rep); realmsResouce().realm(rep.getRealm()).update(rep);
} }
@Test
public void loginAfterLogoutWithDifferentSessionId() {
BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver);
assertThat(tabUtil.getCountOfTabs(), Matchers.is(1));
oauth.openLoginForm();
loginPage.assertCurrent();
tabUtil.newTab(oauth.getLoginFormUrl());
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(2));
oauth.openLoginForm();
tabUtil.closeTab(tabUtil.getCountOfTabs() - 1);
assertThat(tabUtil.getCountOfTabs(), Matchers.equalTo(1));
tabUtil.switchToTab(0);
loginPage.assertCurrent();
loginPage.login("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password");
AccessToken accessToken1 = oauth.verifyToken(response1.getAccessToken());
oauth.doLogout(response1.getRefreshToken(), "password");
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.login("test-user@localhost", "password");
code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response2 = oauth.doAccessTokenRequest(code, "password");
AccessToken accessToken2 = oauth.verifyToken(response2.getAccessToken());
Assert.assertNotEquals(accessToken1.getId(), accessToken2.getId());
Assert.assertNotEquals(accessToken1.getSessionId(), accessToken2.getSessionId());
}
private void setupIdentityFirstFlow() { private void setupIdentityFirstFlow() {
String newFlowAlias = "browser - identity first"; String newFlowAlias = "browser - identity first";
testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias)); testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session).copyBrowserFlow(newFlowAlias));

View file

@ -146,30 +146,14 @@ public class LogoutTest extends AbstractKeycloakTest {
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
Assert.assertFalse(loginPage.isCurrent()); Assert.assertFalse(loginPage.isCurrent());
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse tokenResponse2 = oauth.doAccessTokenRequest(code, "password"); OAuthClient.AccessTokenResponse tokenResponse2 = oauth.doAccessTokenRequest(code, "password");
// POST logout with token should fail
try (CloseableHttpResponse response = oauth.doLogout(refreshToken1, "password")) {
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatusLine().getStatusCode());
}
String logoutUrl = oauth.getLogoutUrl()
.idTokenHint(accessTokenResponse.getIdToken())
.postLogoutRedirectUri(oauth.APP_AUTH_ROOT)
.build();
// GET logout with ID token should fail as well
try (CloseableHttpClient c = HttpClientBuilder.create().disableRedirectHandling().build();
CloseableHttpResponse response = c.execute(new HttpGet(logoutUrl))) {
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatusLine().getStatusCode());
}
// finally POST logout with VALID token should succeed // finally POST logout with VALID token should succeed
try (CloseableHttpResponse response = oauth.doLogout(tokenResponse2.getRefreshToken(), "password")) { try (CloseableHttpResponse response = oauth.doLogout(tokenResponse2.getRefreshToken(), "password")) {
MatcherAssert.assertThat(response, Matchers.statusCodeIsHC(Status.NO_CONTENT)); MatcherAssert.assertThat(response, Matchers.statusCodeIsHC(Status.NO_CONTENT));
@ -178,7 +162,6 @@ public class LogoutTest extends AbstractKeycloakTest {
} }
} }
@Test @Test
public void postLogoutFailWithCredentialsOfDifferentClient() throws Exception { public void postLogoutFailWithCredentialsOfDifferentClient() throws Exception {
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");

View file

@ -1070,8 +1070,8 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
try { try {
// Continue with login // Continue with login
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
assertFalse(loginPage.isCurrent()); assertFalse(loginPage.isCurrent());
@ -1104,8 +1104,8 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
try { try {
// Continue with login // Continue with login
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
assertFalse(loginPage.isCurrent()); assertFalse(loginPage.isCurrent());
@ -1137,8 +1137,8 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
// Continue with login // Continue with login
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
assertFalse(loginPage.isCurrent()); assertFalse(loginPage.isCurrent());

View file

@ -250,8 +250,8 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
events.expectLogin().assertEvent(); events.expectLogin().assertEvent();
Assert.assertFalse(loginPage.isCurrent()); Assert.assertFalse(loginPage.isCurrent());

View file

@ -607,8 +607,8 @@ public class UserInfoTest extends AbstractKeycloakTest {
setTimeOffset(2); setTimeOffset(2);
WaitUtils.waitForPageToLoad(); driver.navigate().refresh();
loginPage.login("password"); oauth.fillLoginForm("test-user@localhost", "password");
events.expectLogin().assertEvent(); events.expectLogin().assertEvent();
Assert.assertFalse(loginPage.isCurrent()); Assert.assertFalse(loginPage.isCurrent());
@ -630,7 +630,7 @@ public class UserInfoTest extends AbstractKeycloakTest {
response.close(); response.close();
events.expect(EventType.USER_INFO_REQUEST_ERROR) events.expect(EventType.USER_INFO_REQUEST_ERROR)
.error(Errors.INVALID_TOKEN) .error(Errors.USER_SESSION_NOT_FOUND)
.user(Matchers.nullValue(String.class)) .user(Matchers.nullValue(String.class))
.session(Matchers.nullValue(String.class)) .session(Matchers.nullValue(String.class))
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN) .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)