Return next action if the current action is not supported in AIA

Closes #33513

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-10-07 15:25:30 +02:00 committed by Marek Posolda
parent 8719e2d0d7
commit 44b1290917
2 changed files with 45 additions and 7 deletions

View file

@ -1052,7 +1052,7 @@ public class AuthenticationManager {
final var kcAction = authSession.getClientNote(Constants.KC_ACTION); final var kcAction = authSession.getClientNote(Constants.KC_ACTION);
final var nextApplicableAction = final var nextApplicableAction =
getFirstApplicableRequiredAction(realm, authSession, user, kcAction); getFirstApplicableRequiredAction(realm, authSession, user, kcAction, new HashSet<>());
if (nextApplicableAction != null) { if (nextApplicableAction != null) {
return nextApplicableAction.getAlias(); return nextApplicableAction.getAlias();
} }
@ -1232,7 +1232,7 @@ public class AuthenticationManager {
HttpRequest request, EventBuilder event, RealmModel realm, UserModel user, Set<String> ignoredActions) { HttpRequest request, EventBuilder event, RealmModel realm, UserModel user, Set<String> ignoredActions) {
final var kcAction = authSession.getClientNote(Constants.KC_ACTION); final var kcAction = authSession.getClientNote(Constants.KC_ACTION);
final var firstApplicableRequiredAction = final var firstApplicableRequiredAction =
getFirstApplicableRequiredAction(realm, authSession, user, kcAction); getFirstApplicableRequiredAction(realm, authSession, user, kcAction, ignoredActions);
if (firstApplicableRequiredAction != null) { if (firstApplicableRequiredAction != null) {
return executeAction(session, authSession, firstApplicableRequiredAction, request, event, realm, user, return executeAction(session, authSession, firstApplicableRequiredAction, request, event, realm, user,
@ -1265,11 +1265,13 @@ public class AuthenticationManager {
if (actionProvider.initiatedActionSupport() == InitiatedActionSupport.NOT_SUPPORTED) { if (actionProvider.initiatedActionSupport() == InitiatedActionSupport.NOT_SUPPORTED) {
logger.debugv("Requested action {0} does not support being invoked with kc_action", factory.getId()); logger.debugv("Requested action {0} does not support being invoked with kc_action", factory.getId());
setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.ERROR, authSession); setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.ERROR, authSession);
return null; ignoredActions.add(factory.getId());
return nextActionAfterAuthentication(session, authSession, session.getContext().getConnection(), request, session.getContext().getUri(), event, ignoredActions);
} else if (!model.isEnabled()) { } else if (!model.isEnabled()) {
logger.debugv("Requested action {0} is disabled and can't be invoked with kc_action", factory.getId()); logger.debugv("Requested action {0} is disabled and can't be invoked with kc_action", factory.getId());
setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.ERROR, authSession); setKcActionStatus(factory.getId(), RequiredActionContext.KcActionStatus.ERROR, authSession);
return null; ignoredActions.add(factory.getId());
return nextActionAfterAuthentication(session, authSession, session.getContext().getConnection(), request, session.getContext().getUri(), event, ignoredActions);
} else { } else {
authSession.setClientNote(Constants.KC_ACTION_EXECUTING, factory.getId()); authSession.setClientNote(Constants.KC_ACTION_EXECUTING, factory.getId());
} }
@ -1311,9 +1313,9 @@ public class AuthenticationManager {
} }
private static RequiredActionProviderModel getFirstApplicableRequiredAction(final RealmModel realm, private static RequiredActionProviderModel getFirstApplicableRequiredAction(final RealmModel realm,
final AuthenticationSessionModel authSession, final UserModel user, final String kcAction) { final AuthenticationSessionModel authSession, final UserModel user, final String kcAction, final Set<String> ignoredActions) {
final var applicableRequiredActionsSorted = final var applicableRequiredActionsSorted =
getApplicableRequiredActionsSorted(realm, authSession, user, kcAction); getApplicableRequiredActionsSorted(realm, authSession, user, kcAction, ignoredActions);
final RequiredActionProviderModel firstApplicableRequiredAction; final RequiredActionProviderModel firstApplicableRequiredAction;
if (applicableRequiredActionsSorted.isEmpty()) { if (applicableRequiredActionsSorted.isEmpty()) {
@ -1328,7 +1330,7 @@ public class AuthenticationManager {
} }
private static List<RequiredActionProviderModel> getApplicableRequiredActionsSorted(final RealmModel realm, private static List<RequiredActionProviderModel> getApplicableRequiredActionsSorted(final RealmModel realm,
final AuthenticationSessionModel authSession, final UserModel user, final String kcActionAlias) { final AuthenticationSessionModel authSession, final UserModel user, final String kcActionAlias, final Set<String> ignoredActions) {
final Set<String> nonInitiatedActionAliases = new HashSet<>(); final Set<String> nonInitiatedActionAliases = new HashSet<>();
nonInitiatedActionAliases.addAll(user.getRequiredActionsStream().toList()); nonInitiatedActionAliases.addAll(user.getRequiredActionsStream().toList());
nonInitiatedActionAliases.addAll(authSession.getRequiredActions()); nonInitiatedActionAliases.addAll(authSession.getRequiredActions());
@ -1336,6 +1338,7 @@ public class AuthenticationManager {
final var applicableNonInitiatedActions = nonInitiatedActionAliases.stream() final var applicableNonInitiatedActions = nonInitiatedActionAliases.stream()
.map(alias -> getApplicableRequiredAction(realm, alias)) .map(alias -> getApplicableRequiredAction(realm, alias))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(model -> !ignoredActions.contains(model.getProviderId()))
.collect(Collectors.toMap(RequiredActionProviderModel::getAlias, Function.identity())); .collect(Collectors.toMap(RequiredActionProviderModel::getAlias, Function.identity()));
RequiredActionProviderModel kcAction = null; RequiredActionProviderModel kcAction = null;

View file

@ -16,9 +16,11 @@
*/ */
package org.keycloak.testsuite.actions; package org.keycloak.testsuite.actions;
import java.io.IOException;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authentication.requiredactions.TermsAndConditions; import org.keycloak.authentication.requiredactions.TermsAndConditions;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -27,6 +29,8 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.VerifyEmailPage;
import org.keycloak.testsuite.updaters.UserAttributeUpdater;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -49,6 +53,9 @@ public class AppInitiatedActionTest extends AbstractTestRealmKeycloakTest {
@Page @Page
protected LoginPage loginPage; protected LoginPage loginPage;
@Page
protected VerifyEmailPage verifyEmailPage;
@Test @Test
public void executeUnknownAction() { public void executeUnknownAction() {
oauth.kcAction("nosuch").openLoginForm(); oauth.kcAction("nosuch").openLoginForm();
@ -93,4 +100,32 @@ public class AppInitiatedActionTest extends AbstractTestRealmKeycloakTest {
testRealm().flows().updateRequiredAction("CONFIGURE_TOTP", configureTotp); testRealm().flows().updateRequiredAction("CONFIGURE_TOTP", configureTotp);
} }
} }
@Test
public void executeActionWithVerifyEmailUnsupportedAIA() throws IOException {
RealmResource realm = testRealm();
RequiredActionProviderRepresentation model = realm.flows().getRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL.name());
int prevPriority = model.getPriority();
try (UserAttributeUpdater userUpdater = UserAttributeUpdater
.forUserByUsername(realm, "test-user@localhost")
.setRequiredActions(UserModel.RequiredAction.VERIFY_EMAIL).update()) {
// Set max priority for verify email (AIA not supported) to be executed before update password
model.setPriority(1);
realm.flows().updateRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL.name(), model);
oauth.kcAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()).openLoginForm();
loginPage.login("test-user@localhost", "password");
// the update password should be displayed
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("password", "password");
// once the AIA password is executed the verify profile should be displayed for the login
verifyEmailPage.assertCurrent();
} finally {
model.setPriority(prevPriority);
realm.flows().updateRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL.name(), model);
}
}
} }