Automated tests for session limits authenticator (browser, direct grant, reset password) (#11046)

Closes #11003
This commit is contained in:
Douglas Palmer 2022-04-01 09:44:38 -07:00 committed by GitHub
parent cc947df828
commit f57d0dd100
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 410 additions and 70 deletions

View file

@ -9,6 +9,7 @@ import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import java.util.Comparator;
@ -16,6 +17,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.keycloak.events.Errors;
import org.keycloak.models.RealmModel;
@ -141,9 +143,14 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
.orElse(SESSION_LIMIT_EXCEEDED);
context.getEvent().error(Errors.GENERIC_AUTHENTICATION_ERROR);
Response challenge = context.form()
.setError(errorMessage)
.createErrorPage(Response.Status.FORBIDDEN);
Response challenge = null;
if(context.getFlowPath() == null) {
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(Errors.GENERIC_AUTHENTICATION_ERROR, errorMessage);
challenge = Response.status(Response.Status.UNAUTHORIZED.getStatusCode()).entity(errorRep).type(MediaType.APPLICATION_JSON_TYPE).build();
}
else {
challenge = context.form().setError(errorMessage).createErrorPage(Response.Status.FORBIDDEN);
}
context.failure(AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR, challenge, eventDetails, errorMessage);
break;

View file

@ -22,44 +22,46 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.authentication.authenticators.sessionlimits.UserSessionLimitsAuthenticatorFactory;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.pages.LoginPasswordResetPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.OAuthClient;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import javax.mail.internet.MimeMessage;
import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
@AuthServerContainerExclude(REMOTE)
public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
private static final String LOGINTEST1 = "login-test-1";
private static final String PASSWORD1 = "password1";
private static final String ERROR_TO_DISPLAY = "This account has too many sessions";
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
UserRepresentation user1 = UserBuilder.create()
.id(LOGINTEST1)
.username(LOGINTEST1)
.email("login1@test.com")
.enabled(true)
.password(PASSWORD1)
.build();
RealmBuilder.edit(testRealm).user(user1);
findTestApp(testRealm).setDirectAccessGrantsEnabled(true);
}
@Before
@ -71,78 +73,409 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test");
if (realm.getBrowserFlow().getAlias().equals("parent-flow")) {
return;
}
// Parent flow
AuthenticationFlowModel browser = new AuthenticationFlowModel();
browser.setAlias("parent-flow");
browser.setDescription("browser based authentication");
browser.setProviderId("basic-flow");
browser.setTopLevel(true);
browser.setBuiltIn(true);
browser = realm.addAuthenticationFlow(browser);
realm.setBrowserFlow(browser);
AuthenticationFlowModel browser = realm.getBrowserFlow();
configureUsernamePassword(realm, browser);
configureSessionLimits(realm, browser);
// username password
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel directGrant = realm.getDirectGrantFlow();
configureSessionLimits(realm, directGrant);
// user session limits authenticator
execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator(UserSessionLimitsAuthenticatorFactory.USER_SESSION_LIMITS);
execution.setPriority(30);
execution.setAuthenticatorFlow(false);
AuthenticatorConfigModel configModel = new AuthenticatorConfigModel();
Map<String, String> sessionAuthenticatorConfig = new HashMap<>();
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.ERROR_MESSAGE, ERROR_TO_DISPLAY);
configModel.setConfig(sessionAuthenticatorConfig);
configModel.setAlias("user-session-limits");
configModel = realm.addAuthenticatorConfig(configModel);
execution.setAuthenticatorConfig(configModel.getId());
realm.addAuthenticatorExecution(execution);
AuthenticationFlowModel resetPasswordFlow = realm.getResetCredentialsFlow();
configureSessionLimits(realm, resetPasswordFlow);
});
testContext.setInitialized(true);
}
private static void configureUsernamePassword(RealmModel realm, AuthenticationFlowModel flow) {
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(flow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator(UsernamePasswordFormFactory.PROVIDER_ID);
execution.setPriority(20);
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
}
private static void configureSessionLimits(RealmModel realm, AuthenticationFlowModel flow) {
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(flow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
execution.setAuthenticator(UserSessionLimitsAuthenticatorFactory.USER_SESSION_LIMITS);
execution.setPriority(30);
execution.setAuthenticatorFlow(false);
AuthenticatorConfigModel configModel = new AuthenticatorConfigModel();
Map<String, String> sessionAuthenticatorConfig = new HashMap<>();
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.ERROR_MESSAGE, ERROR_TO_DISPLAY);
configModel.setConfig(sessionAuthenticatorConfig);
configModel.setAlias("user-session-limits-" + flow.getId());
configModel = realm.addAuthenticatorConfig(configModel);
execution.setAuthenticatorConfig(configModel.getId());
realm.addAuthenticatorExecution(execution);
}
@Rule
public AssertEvents events = new AssertEvents(this);
@Rule
public GreenMailRule greenMail = new GreenMailRule();
@Page
protected LoginPage loginPage;
@Page
protected AppPage appPage;
@Page
protected ErrorPage errorPage;
@Page
protected LoginPasswordResetPage resetPasswordPage;
@Page
protected LoginPasswordUpdatePage updatePasswordPage;
@Test
public void testSessionCountExceededAndNewSessionDenied() throws InterruptedException {
// Login and verify login was succesfull
public void testClientSessionCountExceededAndNewSessionDeniedBrowserFlow() throws Exception {
// Login and verify login was successful
loginPage.open();
loginPage.login(LOGINTEST1, PASSWORD1);
appPage.assertCurrent();
appPage.openAccount();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
// Login the same user again and verify the configured error message is shown
loginPage.open();
loginPage.login(LOGINTEST1, PASSWORD1);
loginPage.login("test-user@localhost", "password");
events.expect(EventType.LOGIN_ERROR).user((String) null).error(Errors.GENERIC_AUTHENTICATION_ERROR).assertEvent();
errorPage.assertCurrent();
Assert.assertEquals(ERROR_TO_DISPLAY, errorPage.getError());
assertEquals(ERROR_TO_DISPLAY, errorPage.getError());
}
}
@Test
public void testClientSessionCountExceededAndOldestSessionRemovedBrowserFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
// Login and verify login was successful
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
assertSessionCount(1);
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
}
}
@Test
public void testRealmSessionCountExceededAndNewSessionDeniedBrowserFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
// Login the same user again and verify the configured error message is shown
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expect(EventType.LOGIN_ERROR).user((String) null).error(Errors.GENERIC_AUTHENTICATION_ERROR).assertEvent();
errorPage.assertCurrent();
assertEquals(ERROR_TO_DISPLAY, errorPage.getError());
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
@Test
public void testRealmSessionCountExceededAndOldestSessionRemovedBrowserFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
loginPage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent();
assertSessionCount(1);
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
@Test
public void testClientSessionCountExceededAndNewSessionDeniedDirectGrantFlow() throws Exception {
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(401, response.getStatusCode());
assertEquals(Errors.GENERIC_AUTHENTICATION_ERROR, response.getError());
assertEquals(ERROR_TO_DISPLAY, response.getErrorDescription());
}
@Test
public void testClientSessionCountExceededAndOldestSessionRemovedDirectGrantFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
assertSessionCount(1);
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
}
}
@Test
public void testRealmSessionCountExceededAndNewSessionDeniedDirectGrantFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(401, response.getStatusCode());
assertEquals(Errors.GENERIC_AUTHENTICATION_ERROR, response.getError());
assertEquals(ERROR_TO_DISPLAY, response.getErrorDescription());
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
@Test
public void testRealmSessionCountExceededAndOldestSessionRemovedDirectGrantFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode());
assertSessionCount(1);
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
@Test
public void testClientSessionCountExceededAndNewSessionDeniedResetPasswordFlow() throws Exception {
try {
// Login and verify login was successful
String redirect_uri = oauth.AUTH_SERVER_ROOT + "/realms/test/account";
oauth.clientId("account");
oauth.redirectUri(redirect_uri);
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().client("account").detail(Details.REDIRECT_URI, redirect_uri).assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
driver.navigate().to(resetUri);
resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("test-user@localhost");
loginPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.user(loginEvent.getUserId())
.detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/")
.client("account")
.detail(Details.USERNAME, "test-user@localhost")
.detail(Details.EMAIL, "test-user@localhost")
.session((String)null)
.assertEvent();
assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(changePasswordUrl.trim());
events.expect(EventType.RESET_PASSWORD_ERROR).client("account").error(Errors.GENERIC_AUTHENTICATION_ERROR).assertEvent();
} finally {
testRealm().clients().findByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).get(0).setDirectAccessGrantsEnabled(false);
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false);
}
}
@Test
public void testClientSessionCountExceededAndOldestSessionRemovedResetPasswordFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
// Login and verify login was successful
String redirect_uri = oauth.AUTH_SERVER_ROOT + "/realms/test/account";
oauth.clientId("account");
oauth.redirectUri(redirect_uri);
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().client("account").detail(Details.REDIRECT_URI, redirect_uri).assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
driver.navigate().to(resetUri);
resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("test-user@localhost");
loginPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.user(loginEvent.getUserId())
.detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/")
.client("account")
.detail(Details.USERNAME, "test-user@localhost")
.detail(Details.EMAIL, "test-user@localhost")
.session((String)null)
.assertEvent();
assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(changePasswordUrl.trim());
updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("resetPassword", "resetPassword");
assertSessionCount(1);
} finally {
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
}
}
@Test
public void testRealmSessionCountExceededAndNewSessionDeniedResetPasswordFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
// Login and verify login was successful
loginPage.open();
loginPage.login("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
driver.navigate().to(resetUri);
resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("test-user@localhost");
loginPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.user(loginEvent.getUserId())
.detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/")
.client("account")
.detail(Details.USERNAME, "test-user@localhost")
.detail(Details.EMAIL, "test-user@localhost")
.session((String)null)
.assertEvent();
assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(changePasswordUrl.trim());
events.expect(EventType.RESET_PASSWORD_ERROR).client("account").error(Errors.GENERIC_AUTHENTICATION_ERROR).assertEvent();
} finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
@Test
public void testRealmSessionCountExceededAndOldestSessionRemovedResetPasswordFlow() throws Exception {
try {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "1");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "0");
// Login and verify login was successful
loginPage.open();
loginPage.login("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
// Delete the cookies, while maintaining the server side session active
super.deleteCookies();
String resetUri = oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials";
driver.navigate().to(resetUri);
resetPasswordPage.assertCurrent();
resetPasswordPage.changePassword("test-user@localhost");
loginPage.assertCurrent();
assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
.user(loginEvent.getUserId())
.detail(Details.REDIRECT_URI, oauth.AUTH_SERVER_ROOT + "/realms/test/account/")
.client("account")
.detail(Details.USERNAME, "test-user@localhost")
.detail(Details.EMAIL, "test-user@localhost")
.session((String)null)
.assertEvent();
assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String changePasswordUrl = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(changePasswordUrl.trim());
updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("resetPassword", "resetPassword");
assertSessionCount(1);
} finally {
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, "0");
setAuthenticatorConfigItem(DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW, UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, "1");
}
}
private void setAuthenticatorConfigItem(String alias, String key, String value) {
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test");
AuthenticationFlowModel flow = realm.getFlowByAlias(alias);
AuthenticatorConfigModel configModel = realm.getAuthenticatorConfigByAlias("user-session-limits-" + flow.getId());
configModel.getConfig().put(key, value);
});
}
private void assertSessionCount(int count) {
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername(realm, "test-user@localhost");
assertEquals(count, session.sessions().getUserSessionsStream(realm, user).count());
});
}
}