diff --git a/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProvider.java b/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProvider.java new file mode 100644 index 00000000000..70dc6475d5f --- /dev/null +++ b/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProvider.java @@ -0,0 +1,43 @@ +package org.keycloak.models.workflow; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import org.jboss.logging.Logger; + + +public class RemoveRequiredActionStepProvider implements WorkflowStepProvider { + + public static String REQUIRED_ACTION_KEY = "action"; + + private final KeycloakSession session; + private final ComponentModel stepModel; + private final Logger log = Logger.getLogger(RemoveRequiredActionStepProvider.class); + + public RemoveRequiredActionStepProvider(KeycloakSession session, ComponentModel model) { + this.session = session; + this.stepModel = model; + } + + @Override + public void run(WorkflowExecutionContext context) { + RealmModel realm = session.getContext().getRealm(); + UserModel user = session.users().getUserById(realm, context.getResourceId()); + + if (user != null) { + try { + UserModel.RequiredAction action = UserModel.RequiredAction.valueOf(stepModel.getConfig().getFirst(REQUIRED_ACTION_KEY)); + log.debugv("Removing required action {0} from user {1})", action, user.getId()); + user.removeRequiredAction(action); + } catch (IllegalArgumentException e) { + log.warnv("Invalid required action {0} configured in RemoveRequiredActionStepProvider", stepModel.getConfig().getFirst(REQUIRED_ACTION_KEY)); + } + } + } + + @Override + public void close() { + } +} diff --git a/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProviderFactory.java b/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProviderFactory.java new file mode 100644 index 00000000000..f7046698a8c --- /dev/null +++ b/services/src/main/java/org/keycloak/models/workflow/RemoveRequiredActionStepProviderFactory.java @@ -0,0 +1,63 @@ +package org.keycloak.models.workflow; + +import java.util.List; + +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; + +public class RemoveRequiredActionStepProviderFactory implements WorkflowStepProviderFactory, ConfiguredProvider { + + public static final String ID = "remove-user-required-action"; + + @Override + public RemoveRequiredActionStepProvider create(KeycloakSession session, ComponentModel model) { + return new RemoveRequiredActionStepProvider(session, model); + } + + @Override + public void init(Config.Scope config) { + // no-op + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + // no-op + } + + @Override + public void close() { + // no-op + } + + @Override + public String getId() { + return ID; + } + + @Override + public List getConfigProperties() { + return ProviderConfigurationBuilder.create() + .property() + .name("action") + .label("Required Action") + .helpText("The required action to remove from the user (e.g., UPDATE_PASSWORD)") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .build(); + } + + @Override + public ResourceType getType() { + return ResourceType.USERS; + } + + @Override + public String getHelpText() { + return ""; + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.models.workflow.WorkflowStepProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.models.workflow.WorkflowStepProviderFactory index d610644e9d2..78ed0cea286 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.models.workflow.WorkflowStepProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.models.workflow.WorkflowStepProviderFactory @@ -23,4 +23,5 @@ org.keycloak.models.workflow.AddRequiredActionStepProviderFactory org.keycloak.models.workflow.GrantRoleStepProviderFactory org.keycloak.models.workflow.RevokeRoleStepProviderFactory org.keycloak.models.workflow.JoinGroupStepProviderFactory -org.keycloak.models.workflow.LeaveGroupStepProviderFactory \ No newline at end of file +org.keycloak.models.workflow.LeaveGroupStepProviderFactory +org.keycloak.models.workflow.RemoveRequiredActionStepProviderFactory \ No newline at end of file diff --git a/tests/base/src/test/java/org/keycloak/tests/workflow/step/RemoveRequiredActionTest.java b/tests/base/src/test/java/org/keycloak/tests/workflow/step/RemoveRequiredActionTest.java new file mode 100644 index 00000000000..60a3e382f19 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/workflow/step/RemoveRequiredActionTest.java @@ -0,0 +1,56 @@ +package org.keycloak.tests.workflow.step; + +import java.time.Duration; + +import org.keycloak.models.UserModel; +import org.keycloak.models.workflow.RemoveRequiredActionStepProvider; +import org.keycloak.models.workflow.RemoveRequiredActionStepProviderFactory; +import org.keycloak.representations.workflows.WorkflowRepresentation; +import org.keycloak.representations.workflows.WorkflowStepRepresentation; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.realm.UserConfigBuilder; +import org.keycloak.tests.workflow.AbstractWorkflowTest; +import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +/** + * Tests the execution of the 'remove-required-action' workflow step. + */ +@KeycloakIntegrationTest(config = WorkflowsBlockingServerConfig.class) +public class RemoveRequiredActionTest extends AbstractWorkflowTest { + + @Test + public void testStepRun() { + managedRealm.admin().workflows().create(WorkflowRepresentation.withName("remove-action-workflow") + .onEvent(USER_CREATED.name()) + .withSteps( + WorkflowStepRepresentation.create() + .of(RemoveRequiredActionStepProviderFactory.ID) + .withConfig(RemoveRequiredActionStepProvider.REQUIRED_ACTION_KEY, "UPDATE_PASSWORD") + .build() + ).build()).close(); + + managedRealm.admin().users().create(UserConfigBuilder.create() + .username("testuser_remove") + .requiredActions(UserModel.RequiredAction.UPDATE_PASSWORD.name()) + .build()).close(); + + Awaitility.await() + .timeout(Duration.ofSeconds(30)) + .pollInterval(Duration.ofSeconds(1)) + .untilAsserted(() -> { + var users = managedRealm.admin().users().search("testuser_remove"); + assertThat(users, hasSize(1)); + var userRepresentation = users.get(0); + Assertions.assertTrue(userRepresentation.getRequiredActions() == null || userRepresentation.getRequiredActions().isEmpty()); + }); + } +}