Automated test for session-limits authenticator with identity brokering (post-broker login flow) (#11723)

Closes #11004
This commit is contained in:
Douglas Palmer 2022-04-28 01:29:41 -07:00 committed by GitHub
parent 37134ec8b0
commit fdcbc9b27b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 176 additions and 43 deletions

View file

@ -0,0 +1,85 @@
package org.keycloak.testsuite.sessionlimits;
import org.junit.Test;
import org.keycloak.authentication.authenticators.sessionlimits.UserSessionLimitsAuthenticatorFactory;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.testsuite.broker.AbstractInitializedBaseBrokerTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.assertSessionCount;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.configureSessionLimits;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.ERROR_TO_DISPLAY;
public abstract class AbstractUserSessionLimitsBrokerTest extends AbstractInitializedBaseBrokerTest {
@Test
public void testSessionCountExceededAndNewSessionDeniedFirstBrokerLoginFlow() throws Exception {
configureFlow(UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION, "0", "1");
loginTwiceAndVerifyBehavior(UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
}
@Test
public void testSessionCountExceededAndOldestSessionRemovedFirstBrokerLoginFlow() throws Exception {
configureFlow(UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION, "0", "1");
loginTwiceAndVerifyBehavior(UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
}
@Test
public void testRealmSessionCountExceededAndNewSessionDeniedFirstBrokerLoginFlow() throws Exception {
configureFlow(UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION, "1", "0");
loginTwiceAndVerifyBehavior(UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
}
@Test
public void testRealmSessionCountExceededAndOldestFirstBrokerLoginFlow() throws Exception {
configureFlow(UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION, "1", "0");
loginTwiceAndVerifyBehavior(UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION);
}
private void configureFlow(String behavior, String realmLimit, String clientLimit)
{
String realmName = bc.consumerRealmName();
String idpAlias = bc.getIDPAlias();
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName(realmName);
AuthenticationFlowModel postBrokerFlow = new AuthenticationFlowModel();
postBrokerFlow.setAlias("post-broker");
postBrokerFlow.setDescription("post-broker flow with session limits");
postBrokerFlow.setProviderId("basic-flow");
postBrokerFlow.setTopLevel(true);
postBrokerFlow.setBuiltIn(false);
postBrokerFlow = realm.addAuthenticationFlow(postBrokerFlow);
configureSessionLimits(realm, postBrokerFlow, behavior, realmLimit, clientLimit);
IdentityProviderModel idp = realm.getIdentityProviderByAlias(idpAlias);
idp.setPostBrokerLoginFlowId(postBrokerFlow.getId());
realm.updateIdentityProvider(idp);
});
}
private void loginTwiceAndVerifyBehavior(String behavior) {
logInAsUserInIDPForFirstTime();
assertLoggedInAccountManagement();
deleteAllCookiesForRealm(bc.consumerRealmName());
deleteAllCookiesForRealm(bc.providerRealmName());
logInAsUserInIDP();
if (UserSessionLimitsAuthenticatorFactory.TERMINATE_OLDEST_SESSION.equals(behavior)) {
assertLoggedInAccountManagement();
testingClient.server(bc.consumerRealmName()).run(assertSessionCount(bc.consumerRealmName(), bc.getUserLogin(), 1));
}
else if (UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION.equals(behavior)) {
errorPage.assertCurrent();
assertEquals(ERROR_TO_DISPLAY, errorPage.getError());
}
else {
fail("Invalid behavior " + behavior);
}
}
}

View file

@ -0,0 +1,15 @@
package org.keycloak.testsuite.sessionlimits;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.broker.BrokerConfiguration;
import org.keycloak.testsuite.broker.KcOidcBrokerConfiguration;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
@AuthServerContainerExclude(REMOTE)
public class KcOidcUserSessionLimitsBrokerTest extends AbstractUserSessionLimitsBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcOidcBrokerConfiguration.INSTANCE;
}
}

View file

@ -0,0 +1,15 @@
package org.keycloak.testsuite.sessionlimits;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.broker.BrokerConfiguration;
import org.keycloak.testsuite.broker.KcSamlBrokerConfiguration;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
@AuthServerContainerExclude(REMOTE)
public class KcSamlUserSessionLimitsBrokerTest extends AbstractUserSessionLimitsBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcSamlBrokerConfiguration.INSTANCE;
}
}

View file

@ -30,7 +30,6 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows; 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;
@ -44,8 +43,6 @@ import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.MailUtils; import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
@ -53,11 +50,14 @@ import javax.mail.internet.MimeMessage;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE; import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.assertSessionCount;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.configureSessionLimits;
import static org.keycloak.testsuite.sessionlimits.UserSessionLimitsUtil.ERROR_TO_DISPLAY;
@AuthServerContainerExclude(REMOTE) @AuthServerContainerExclude(REMOTE)
public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest { public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
private String realmName = "test";
private static final String ERROR_TO_DISPLAY = "This account has too many sessions"; private String username = "test-user@localhost";
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
@ -75,13 +75,13 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
AuthenticationFlowModel browser = realm.getBrowserFlow(); AuthenticationFlowModel browser = realm.getBrowserFlow();
configureUsernamePassword(realm, browser); configureUsernamePassword(realm, browser);
configureSessionLimits(realm, browser); configureSessionLimits(realm, browser, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION, "0", "1");
AuthenticationFlowModel directGrant = realm.getDirectGrantFlow(); AuthenticationFlowModel directGrant = realm.getDirectGrantFlow();
configureSessionLimits(realm, directGrant); configureSessionLimits(realm, directGrant, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION, "0", "1");
AuthenticationFlowModel resetPasswordFlow = realm.getResetCredentialsFlow(); AuthenticationFlowModel resetPasswordFlow = realm.getResetCredentialsFlow();
configureSessionLimits(realm, resetPasswordFlow); configureSessionLimits(realm, resetPasswordFlow, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION, "0", "1");
}); });
testContext.setInitialized(true); testContext.setInitialized(true);
} }
@ -96,27 +96,6 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
realm.addAuthenticatorExecution(execution); 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 @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -169,7 +148,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
loginPage.open(); loginPage.open();
loginPage.login("test-user@localhost", "password"); loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent(); events.expectLogin().assertEvent();
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION); setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
} }
@ -215,7 +194,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
loginPage.open(); loginPage.open();
loginPage.login("test-user@localhost", "password"); loginPage.login("test-user@localhost", "password");
events.expectLogin().assertEvent(); events.expectLogin().assertEvent();
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.BROWSER_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION); 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_REALM_LIMIT, "0");
@ -243,7 +222,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode()); assertEquals(200, response.getStatusCode());
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION); setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
} }
@ -278,7 +257,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password");
assertEquals(200, response.getStatusCode()); assertEquals(200, response.getStatusCode());
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
setAuthenticatorConfigItem(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW, UserSessionLimitsAuthenticatorFactory.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION); 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_REALM_LIMIT, "0");
@ -365,7 +344,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.assertCurrent(); updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("resetPassword", "resetPassword"); updatePasswordPage.changePassword("resetPassword", "resetPassword");
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false); 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.BEHAVIOR, UserSessionLimitsAuthenticatorFactory.DENY_NEW_SESSION);
@ -452,7 +431,7 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
updatePasswordPage.assertCurrent(); updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("resetPassword", "resetPassword"); updatePasswordPage.changePassword("resetPassword", "resetPassword");
assertSessionCount(1); testingClient.server(realmName).run(assertSessionCount(realmName, username, 1));
} finally { } finally {
ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false); ApiUtil.resetUserPassword(testRealm().users().get(findUser("test-user@localhost").getId()), "password", false);
@ -471,12 +450,4 @@ public class UserSessionLimitsTest extends AbstractTestRealmKeycloakTest {
realm.updateAuthenticatorConfig(configModel); realm.updateAuthenticatorConfig(configModel);
}); });
} }
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());
});
}
} }

View file

@ -0,0 +1,47 @@
package org.keycloak.testsuite.sessionlimits;
import org.keycloak.authentication.authenticators.sessionlimits.UserSessionLimitsAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.testsuite.runonserver.RunOnServer;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class UserSessionLimitsUtil {
protected static final String ERROR_TO_DISPLAY = "This account has too many sessions";
protected static void configureSessionLimits(RealmModel realm, AuthenticationFlowModel flow, String behavior, String realmLimit, String clientLimit) {
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, behavior);
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_REALM_LIMIT, realmLimit);
sessionAuthenticatorConfig.put(UserSessionLimitsAuthenticatorFactory.USER_CLIENT_LIMIT, clientLimit);
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);
}
static RunOnServer assertSessionCount(String realmName, String username, int count) {
return (session) -> {
RealmModel realm = session.realms().getRealmByName(realmName);
UserModel user = session.users().getUserByUsername(realm, username);
assertEquals(count, session.sessions().getUserSessionsStream(realm, user).count());
};
}
}