Fire logout event when logout other sessions (#26658)
Closes #26658 Signed-off-by: Lex Cao <lexcao@foxmail.com>
This commit is contained in:
parent
3382e16954
commit
a53cacc0a7
11 changed files with 128 additions and 42 deletions
|
@ -50,6 +50,8 @@ public interface RequiredActionContext {
|
||||||
ERROR
|
ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getAction();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the action URL for the required action.
|
* Get the action URL for the required action.
|
||||||
*
|
*
|
||||||
|
|
|
@ -91,4 +91,7 @@ public interface Details {
|
||||||
|
|
||||||
String NOT_BEFORE = "not_before";
|
String NOT_BEFORE = "not_before";
|
||||||
String NUM_FAILURES = "num_failures";
|
String NUM_FAILURES = "num_failures";
|
||||||
|
|
||||||
|
String LOGOUT_TRIGGERED_BY_ACTION_TOKEN = "logout_triggered_by_action_token";
|
||||||
|
String LOGOUT_TRIGGERED_BY_REQUIRED_ACTION = "logout_triggered_by_required_action";
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
||||||
import org.keycloak.authentication.actiontoken.DefaultActionToken;
|
import org.keycloak.authentication.actiontoken.DefaultActionToken;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -136,28 +139,39 @@ public class AuthenticatorUtil {
|
||||||
* @param context The required action context
|
* @param context The required action context
|
||||||
*/
|
*/
|
||||||
public static void logoutOtherSessions(RequiredActionContext context) {
|
public static void logoutOtherSessions(RequiredActionContext context) {
|
||||||
|
EventBuilder event = context.getEvent().clone()
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, context.getAction());
|
||||||
logoutOtherSessions(context.getSession(), context.getRealm(), context.getUser(),
|
logoutOtherSessions(context.getSession(), context.getRealm(), context.getUser(),
|
||||||
context.getAuthenticationSession(), context.getConnection(), context.getHttpRequest());
|
context.getAuthenticationSession(), context.getConnection(), context.getHttpRequest(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logouts all sessions that are different to the current authentication session
|
* Logouts all sessions that are different to the current authentication session
|
||||||
* managed in the action token context.
|
* managed in the action token context.
|
||||||
*
|
*
|
||||||
|
* @param token The action token
|
||||||
* @param context The required action token context
|
* @param context The required action token context
|
||||||
*/
|
*/
|
||||||
public static void logoutOtherSessions(ActionTokenContext<? extends DefaultActionToken> context) {
|
public static void logoutOtherSessions(DefaultActionToken token, ActionTokenContext<? extends DefaultActionToken> context) {
|
||||||
|
EventBuilder event = context.getEvent().clone()
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_ACTION_TOKEN, token.getActionId());
|
||||||
logoutOtherSessions(context.getSession(), context.getRealm(), context.getAuthenticationSession().getAuthenticatedUser(),
|
logoutOtherSessions(context.getSession(), context.getRealm(), context.getAuthenticationSession().getAuthenticatedUser(),
|
||||||
context.getAuthenticationSession(), context.getClientConnection(), context.getRequest());
|
context.getAuthenticationSession(), context.getClientConnection(), context.getRequest(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void logoutOtherSessions(KeycloakSession session, RealmModel realm, UserModel user,
|
private static void logoutOtherSessions(KeycloakSession session, RealmModel realm, UserModel user,
|
||||||
AuthenticationSessionModel authSession, ClientConnection conn, HttpRequest req) {
|
AuthenticationSessionModel authSession, ClientConnection conn, HttpRequest req, EventBuilder event) {
|
||||||
session.sessions().getUserSessionsStream(realm, user)
|
session.sessions().getUserSessionsStream(realm, user)
|
||||||
.filter(s -> !Objects.equals(s.getId(), authSession.getParentSession().getId()))
|
.filter(s -> !Objects.equals(s.getId(), authSession.getParentSession().getId()))
|
||||||
.collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
|
.collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions.
|
||||||
.forEach(s -> AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(),
|
.forEach(s -> {
|
||||||
conn, req.getHttpHeaders(), true)
|
AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(),
|
||||||
);
|
conn, req.getHttpHeaders(), true);
|
||||||
|
|
||||||
|
event.event(EventType.LOGOUT)
|
||||||
|
.session(s)
|
||||||
|
.user(s.getUser())
|
||||||
|
.success();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
status = Status.IGNORE;
|
status = Status.IGNORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAction() {
|
||||||
|
return getFactory().getId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getActionUrl(String code) {
|
public URI getActionUrl(String code) {
|
||||||
ClientModel client = authenticationSession.getClient();
|
ClientModel client = authenticationSession.getClient();
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class UpdateEmailActionTokenHandler extends AbstractActionTokenHandler<Up
|
||||||
UpdateEmail.updateEmailNow(tokenContext.getEvent(), user, emailUpdateValidationResult);
|
UpdateEmail.updateEmailNow(tokenContext.getEvent(), user, emailUpdateValidationResult);
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(token.getLogoutSessions())) {
|
if (Boolean.TRUE.equals(token.getLogoutSessions())) {
|
||||||
AuthenticatorUtil.logoutOtherSessions(tokenContext);
|
AuthenticatorUtil.logoutOtherSessions(token, tokenContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenContext.getEvent().success();
|
tokenContext.getEvent().success();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
|
@ -227,7 +228,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
|
||||||
final String firstSessionId = sessions.get(0).getId();
|
final String firstSessionId = sessions.get(0).getId();
|
||||||
|
|
||||||
oauth2.doLogin("test-user@localhost", "password");
|
oauth2.doLogin("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
EventRepresentation event2 = events.expectLogin().assertEvent();
|
||||||
assertEquals(2, testUser.getUserSessions().size());
|
assertEquals(2, testUser.getUserSessions().size());
|
||||||
|
|
||||||
doAIA();
|
doAIA();
|
||||||
|
@ -235,6 +236,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
assertTrue("Logout sessions is checked by default", changePasswordPage.isLogoutSessionsChecked());
|
assertTrue("Logout sessions is checked by default", changePasswordPage.isLogoutSessionsChecked());
|
||||||
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
|
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
|
||||||
|
events.expectLogout(event2.getSessionId()).detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, UserModel.RequiredAction.UPDATE_PASSWORD.name()).assertEvent();
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
assertKcActionStatus(SUCCESS);
|
assertKcActionStatus(SUCCESS);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.actions;
|
package org.keycloak.testsuite.actions;
|
||||||
|
|
||||||
|
import org.hamcrest.MatcherAssert;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.drone.api.annotation.Drone;
|
import org.jboss.arquillian.drone.api.annotation.Drone;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -24,6 +26,7 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
|
import org.keycloak.authentication.authenticators.browser.UsernameFormFactory;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
@ -31,6 +34,7 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
@ -39,14 +43,16 @@ import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
|
import org.keycloak.testsuite.pages.LoginUsernameOnlyPage;
|
||||||
|
import org.keycloak.testsuite.util.FlowUtil;
|
||||||
import org.keycloak.testsuite.util.GreenMailRule;
|
import org.keycloak.testsuite.util.GreenMailRule;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.SecondBrowser;
|
|
||||||
import org.keycloak.testsuite.util.FlowUtil;
|
|
||||||
import org.keycloak.testsuite.util.RealmManager;
|
import org.keycloak.testsuite.util.RealmManager;
|
||||||
|
import org.keycloak.testsuite.util.SecondBrowser;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -118,14 +124,21 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void logoutSessionsCheckboxNotPresent() {
|
public void resetPasswordLogoutSessionsChecked() {
|
||||||
|
resetPassword(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resetPasswordLogoutSessionsNotChecked() {
|
||||||
|
resetPassword(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetPassword(boolean logoutOtherSessions) {
|
||||||
OAuthClient oauth2 = new OAuthClient();
|
OAuthClient oauth2 = new OAuthClient();
|
||||||
oauth2.init(driver2);
|
oauth2.init(driver2);
|
||||||
|
|
||||||
UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId());
|
UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId());
|
||||||
|
|
||||||
oauth2.doLogin("test-user@localhost", "password");
|
oauth2.doLogin("test-user@localhost", "password");
|
||||||
events.expectLogin().assertEvent();
|
EventRepresentation event1 = events.expectLogin().assertEvent();
|
||||||
assertEquals(1, testUser.getUserSessions().size());
|
assertEquals(1, testUser.getUserSessions().size());
|
||||||
|
|
||||||
requireUpdatePassword();
|
requireUpdatePassword();
|
||||||
|
@ -135,12 +148,29 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
assertTrue(changePasswordPage.isLogoutSessionDisplayed());
|
assertTrue(changePasswordPage.isLogoutSessionDisplayed());
|
||||||
assertTrue(changePasswordPage.isLogoutSessionsChecked());
|
assertTrue(changePasswordPage.isLogoutSessionsChecked());
|
||||||
changePasswordPage.uncheckLogoutSessions();
|
if (!logoutOtherSessions) {
|
||||||
|
changePasswordPage.uncheckLogoutSessions();
|
||||||
|
}
|
||||||
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
|
changePasswordPage.changePassword("All Right Then, Keep Your Secrets", "All Right Then, Keep Your Secrets");
|
||||||
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
|
||||||
events.expectLogin().assertEvent();
|
|
||||||
|
|
||||||
assertEquals("All sessions are still active", 2, testUser.getUserSessions().size());
|
if (logoutOtherSessions) {
|
||||||
|
events.expectLogout(event1.getSessionId())
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, RequiredAction.UPDATE_PASSWORD.name())
|
||||||
|
.assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
||||||
|
EventRepresentation event2 = events.expectLogin().assertEvent();
|
||||||
|
List<UserSessionRepresentation> sessions = testUser.getUserSessions();
|
||||||
|
if (logoutOtherSessions) {
|
||||||
|
assertEquals(1, sessions.size());
|
||||||
|
assertEquals(event2.getSessionId(), sessions.iterator().next().getId());
|
||||||
|
} else {
|
||||||
|
assertEquals(2, sessions.size());
|
||||||
|
MatcherAssert.assertThat(sessions.stream().map(UserSessionRepresentation::getId).collect(Collectors.toList()),
|
||||||
|
Matchers.containsInAnyOrder(event1.getSessionId(), event2.getSessionId()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -162,8 +192,7 @@ public class RequiredActionResetPasswordTest extends AbstractTestRealmKeycloakTe
|
||||||
loginUsernameOnlyPage.open();
|
loginUsernameOnlyPage.open();
|
||||||
loginUsernameOnlyPage.login("test-user@localhost");
|
loginUsernameOnlyPage.login("test-user@localhost");
|
||||||
events.expectLogin().assertEvent();
|
events.expectLogin().assertEvent();
|
||||||
}
|
} finally {
|
||||||
finally {
|
|
||||||
//reset browser flow and delete username only flow
|
//reset browser flow and delete username only flow
|
||||||
RealmRepresentation realm = testRealm().toRepresentation();
|
RealmRepresentation realm = testRealm().toRepresentation();
|
||||||
realm.setBrowserFlow(DefaultAuthenticationFlows.BROWSER_FLOW);
|
realm.setBrowserFlow(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||||
|
|
|
@ -671,6 +671,13 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertEquals(logoutOtherSessions, totpPage.isLogoutSessionsChecked());
|
Assert.assertEquals(logoutOtherSessions, totpPage.isLogoutSessionsChecked());
|
||||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
||||||
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
if (logoutOtherSessions) {
|
||||||
|
events.expectLogout(event1.getSessionId())
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, UserModel.RequiredAction.CONFIGURE_TOTP.name())
|
||||||
|
.assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
EventRepresentation event2 = events.expectRequiredAction(EventType.UPDATE_TOTP).user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
EventRepresentation event2 = events.expectRequiredAction(EventType.UPDATE_TOTP).user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID)).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID)).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,12 @@ public class RequiredActionUpdateEmailTest extends AbstractRequiredActionUpdateE
|
||||||
configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name());
|
configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||||
changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions);
|
changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions);
|
||||||
|
|
||||||
|
if (logoutOtherSessions) {
|
||||||
|
events.expectLogout(event1.getSessionId())
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, UserModel.RequiredAction.UPDATE_EMAIL.name())
|
||||||
|
.assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||||
.detail(Details.UPDATED_EMAIL, "new@localhost").assertEvent();
|
.detail(Details.UPDATED_EMAIL, "new@localhost").assertEvent();
|
||||||
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.actions;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot;
|
||||||
|
|
||||||
import jakarta.mail.Address;
|
import jakarta.mail.Address;
|
||||||
import jakarta.mail.Message;
|
import jakarta.mail.Message;
|
||||||
|
@ -27,11 +28,14 @@ import jakarta.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.authentication.actiontoken.updateemail.UpdateEmailActionToken;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -96,31 +100,38 @@ public class RequiredActionUpdateEmailTestWithVerificationTest extends AbstractR
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEmail(boolean logoutOtherSessions) throws Exception {
|
private void updateEmail(boolean logoutOtherSessions) throws Exception {
|
||||||
// login using another session
|
// login using another session
|
||||||
configureRequiredActionsToUser("test-user@localhost");
|
configureRequiredActionsToUser("test-user@localhost");
|
||||||
UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId());
|
UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId());
|
||||||
OAuthClient oauth2 = new OAuthClient();
|
OAuthClient oauth2 = new OAuthClient();
|
||||||
oauth2.init(driver2);
|
oauth2.init(driver2);
|
||||||
oauth2.doLogin("test-user@localhost", "password");
|
oauth2.doLogin("test-user@localhost", "password");
|
||||||
EventRepresentation event1 = events.expectLogin().assertEvent();
|
EventRepresentation event1 = events.expectLogin().assertEvent();
|
||||||
assertEquals(1, testUser.getUserSessions().size());
|
assertEquals(1, testUser.getUserSessions().size());
|
||||||
|
|
||||||
// add action and change email
|
// add action and change email
|
||||||
configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name());
|
configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||||
changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions);
|
changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions);
|
||||||
|
|
||||||
events.expect(EventType.UPDATE_EMAIL)
|
if (logoutOtherSessions) {
|
||||||
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
events.expectLogout(event1.getSessionId())
|
||||||
.detail(Details.UPDATED_EMAIL, "new@localhost")
|
.detail(Details.REDIRECT_URI, getAuthServerContextRoot() + "/auth/realms/test/account/")
|
||||||
.assertEvent();
|
.detail(Details.LOGOUT_TRIGGERED_BY_ACTION_TOKEN, UpdateEmailActionToken.TOKEN_TYPE)
|
||||||
|
.assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
List<UserSessionRepresentation> sessions = testUser.getUserSessions();
|
events.expect(EventType.UPDATE_EMAIL)
|
||||||
if (logoutOtherSessions) {
|
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||||
assertEquals(0, sessions.size());
|
.detail(Details.UPDATED_EMAIL, "new@localhost")
|
||||||
} else {
|
.assertEvent();
|
||||||
assertEquals(1, sessions.size());
|
|
||||||
assertEquals(event1.getSessionId(), sessions.iterator().next().getId());
|
List<UserSessionRepresentation> sessions = testUser.getUserSessions();
|
||||||
}
|
if (logoutOtherSessions) {
|
||||||
|
assertEquals(0, sessions.size());
|
||||||
|
} else {
|
||||||
|
assertEquals(1, sessions.size());
|
||||||
|
assertEquals(event1.getSessionId(), sessions.iterator().next().getId());
|
||||||
|
}
|
||||||
|
|
||||||
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
|
||||||
assertEquals("new@localhost", user.getEmail());
|
assertEquals("new@localhost", user.getEmail());
|
||||||
|
|
|
@ -149,6 +149,13 @@ public class RecoveryAuthnCodesAuthenticatorTest extends AbstractTestRealmKeyclo
|
||||||
Assert.assertEquals(logoutOtherSessions, setupRecoveryAuthnCodesPage.isLogoutSessionsChecked());
|
Assert.assertEquals(logoutOtherSessions, setupRecoveryAuthnCodesPage.isLogoutSessionsChecked());
|
||||||
setupRecoveryAuthnCodesPage.clickSaveRecoveryAuthnCodesButton();
|
setupRecoveryAuthnCodesPage.clickSaveRecoveryAuthnCodesButton();
|
||||||
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
if (logoutOtherSessions) {
|
||||||
|
events.expectLogout(event1.getSessionId())
|
||||||
|
.detail(Details.LOGOUT_TRIGGERED_BY_REQUIRED_ACTION, UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name())
|
||||||
|
.assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
EventRepresentation event2 = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
|
EventRepresentation event2 = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION)
|
||||||
.user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
.user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent();
|
||||||
event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID))
|
event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID))
|
||||||
|
|
Loading…
Reference in a new issue