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

View file

@ -16,9 +16,11 @@
*/
package org.keycloak.testsuite.actions;
import java.io.IOException;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authentication.requiredactions.TermsAndConditions;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
@ -27,6 +29,8 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.pages.AppPage;
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.assertTrue;
@ -49,6 +53,9 @@ public class AppInitiatedActionTest extends AbstractTestRealmKeycloakTest {
@Page
protected LoginPage loginPage;
@Page
protected VerifyEmailPage verifyEmailPage;
@Test
public void executeUnknownAction() {
oauth.kcAction("nosuch").openLoginForm();
@ -93,4 +100,32 @@ public class AppInitiatedActionTest extends AbstractTestRealmKeycloakTest {
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);
}
}
}