Introduce WorkflowEventSpi

- supports custom event handling beyond the built-in workflow capabilities.

Closes #43916

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2026-01-30 08:57:56 -03:00 committed by Pedro Igor
parent 43b5b3484b
commit 6e408dd7bc
103 changed files with 1134 additions and 558 deletions

View file

@ -2,8 +2,7 @@ package org.keycloak.representations.workflows;
public final class WorkflowConstants {
public static final String DEFAULT_WORKFLOW = "event-based-workflow";
public static final String AD_HOC = "adhoc";
public static final String CONFIG_USES = "uses";
public static final String CONFIG_WITH = "with";
public static final String CONFIG_SUPPORTS = "supports";

View file

@ -203,7 +203,7 @@ public final class WorkflowRepresentation extends AbstractWorkflowComponentRepre
}
public Builder onEvent(String... operation) {
return onEvent(String.join(" or ", operation).toUpperCase());
return onEvent(String.join(" or ", operation));
}
public Builder onCondition(String condition) {

View file

@ -900,7 +900,17 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc
resource = toClientModel(realm, entity);
session.getKeycloakSessionFactory().publish((ClientModel.ClientCreationEvent) () -> resource);
session.getKeycloakSessionFactory().publish(new ClientModel.ClientCreationEvent() {
@Override
public ClientModel getCreatedClient() {
return resource;
}
@Override
public KeycloakSession getKeycloakSession() {
return session;
}
});
return resource;
}

View file

@ -16,9 +16,9 @@ import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
import org.keycloak.models.workflow.conditions.expression.PredicateEvaluator;
import org.keycloak.models.workflow.expression.BooleanConditionParser;
import org.keycloak.models.workflow.expression.EvaluatorUtils;
import org.keycloak.models.workflow.expression.PredicateEvaluator;
import org.keycloak.representations.workflows.WorkflowConstants;
import org.keycloak.utils.StringUtil;

View file

@ -2,10 +2,11 @@ package org.keycloak.models.workflow;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
import org.keycloak.models.workflow.conditions.expression.ConditionEvaluator;
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
import org.keycloak.models.workflow.conditions.expression.EventEvaluator;
import org.keycloak.models.workflow.expression.BooleanConditionParser;
import org.keycloak.models.workflow.expression.ConditionEvaluator;
import org.keycloak.models.workflow.expression.EvaluatorUtils;
import org.keycloak.models.workflow.expression.EventEvaluator;
import org.keycloak.representations.workflows.WorkflowConstants;
import org.keycloak.utils.StringUtil;
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_CANCEL_IN_PROGRESS;
@ -41,7 +42,7 @@ final class EventBasedWorkflow {
if (event == null) {
return false;
}
return supports(event.getResourceType()) && activateOnEvent(event) && validateResourceConditions(executionContext);
return supports(event.getResourceType()) && activateOnEvent(executionContext) && validateResourceConditions(executionContext);
}
/**
@ -89,19 +90,19 @@ final class EventBasedWorkflow {
/**
* Determines whether the workflow should be activated based on the given event or not.
*
* @param event a reference to the workflow event.
* @param executionContext a reference to the workflow execution context.
* @return {@code true} if the workflow should be activated, {@code false} otherwise.
*/
private boolean activateOnEvent(WorkflowEvent event) {
private boolean activateOnEvent(WorkflowExecutionContext executionContext) {
// AD_HOC is a special case that always triggers the workflow regardless of the configured activation events
if (ResourceOperationType.AD_HOC.equals(event.getOperation())) {
if (WorkflowConstants.AD_HOC.equals(executionContext.getEvent().getEventProviderId())) {
return true;
}
String eventConditions = model.getConfig().getFirst(CONFIG_ON_EVENT);
if (StringUtil.isNotBlank(eventConditions)) {
BooleanConditionParser.EvaluatorContext context = EvaluatorUtils.createEvaluatorContext(model, eventConditions);
EventEvaluator eventEvaluator = new EventEvaluator(event);
EventEvaluator eventEvaluator = new EventEvaluator(session, executionContext);
return eventEvaluator.visit(context);
} else {
return false;
@ -131,7 +132,7 @@ final class EventBasedWorkflow {
else {
// the flag has an event expression - parse and evaluate it
BooleanConditionParser.EvaluatorContext context = EvaluatorUtils.createEvaluatorContext(model, concurrencySetting);
EventEvaluator eventEvaluator = new EventEvaluator(executionContext.getEvent());
EventEvaluator eventEvaluator = new EventEvaluator(session, executionContext);
return eventEvaluator.visit(context);
}
}

View file

@ -30,7 +30,7 @@ public final class RestartWorkflowStepProviderFactory implements WorkflowStepPro
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
// Usable for all resource types.
return Set.of(ResourceType.values());
}

View file

@ -31,7 +31,7 @@ final class ScheduleWorkflowTask extends WorkflowTransactionalTask {
WorkflowEvent event = workflowContext.getEvent();
WorkflowStep firstStep = workflow.getSteps().findFirst().orElseThrow(() -> new WorkflowInvalidStateException("No steps found for workflow " + workflow.getName()));
log.debugf("Scheduling first step '%s' of workflow '%s' for resource %s based on on event %s with notBefore %d",
firstStep.getProviderId(), workflow.getName(), event.getResourceId(), event.getOperation(), workflow.getNotBefore());
firstStep.getProviderId(), workflow.getName(), event.getResourceId(), event.getEventProviderId(), workflow.getNotBefore());
String originalAfter = firstStep.getAfter();
try {
firstStep.setAfter(workflow.getNotBefore());
@ -46,7 +46,7 @@ final class ScheduleWorkflowTask extends WorkflowTransactionalTask {
@Override
public String toString() {
WorkflowEvent event = context.getEvent();
return "eventType=" + event.getOperation() +
return "eventType=" + event.getEventProviderId() +
",resourceType=" + event.getResourceType() +
",resourceId=" + event.getResourceId();
}

View file

@ -33,9 +33,9 @@ import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
import org.keycloak.models.workflow.conditions.expression.PredicateEvaluator;
import org.keycloak.models.workflow.expression.BooleanConditionParser;
import org.keycloak.models.workflow.expression.EvaluatorUtils;
import org.keycloak.models.workflow.expression.PredicateEvaluator;
import org.keycloak.representations.workflows.WorkflowConstants;
import org.keycloak.utils.StringUtil;

View file

@ -9,10 +9,10 @@ import java.util.stream.Collectors;
import org.keycloak.common.util.DurationConverter;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser;
import org.keycloak.models.workflow.conditions.expression.ConditionNameCollector;
import org.keycloak.models.workflow.conditions.expression.ConditionTypeCollector;
import org.keycloak.models.workflow.conditions.expression.EvaluatorUtils;
import org.keycloak.models.workflow.expression.BooleanConditionParser;
import org.keycloak.models.workflow.expression.ConditionNameCollector;
import org.keycloak.models.workflow.expression.ConditionTypeCollector;
import org.keycloak.models.workflow.expression.EvaluatorUtils;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.utils.StringUtil;
@ -93,9 +93,9 @@ public class WorkflowValidator {
// ConditionTypeCollector.visit(ctx) throws a WorkflowInvalidStateException if a provider is not found
typeCollector.visit(context);
Set<ResourceType> supporteds = typeCollector.getConditionTypes();
if (!supporteds.contains(workflowType)) {
String formatted = supporteds.stream().map(Enum::name).collect(Collectors.joining(", "));
Set<ResourceType> supportedTypes = typeCollector.getConditionTypes();
if (!supportedTypes.contains(workflowType)) {
String formatted = supportedTypes.stream().map(Enum::name).collect(Collectors.joining(", "));
throw new WorkflowInvalidStateException("Provided condition types (%s) are not compatible with workflow type (%s).".formatted(formatted, workflowType));
}
}
@ -135,19 +135,12 @@ public class WorkflowValidator {
ConditionNameCollector collector = new ConditionNameCollector();
collector.visit(context);
// check if there are providers for the conditions used in the expression
// check if the providers referenced in the condition and event expressions are valid
if ("on".equals(fieldName) || "restart-in-progress".equals(fieldName) || "cancel-in-progress".equals(fieldName)) {
// check if we can get a ResourceOperationType for the events in the expression
for (String name : collector.getConditionNames()) {
try {
ResourceOperationType.valueOf(name.replace("-", "_").toUpperCase());
} catch (IllegalArgumentException iae) {
throw new WorkflowInvalidStateException("Could not find event: " + name);
}
}
collector.getConditionNames().forEach(name -> Workflows.getEventProviderFactory(session, name.replace("_", "-").toLowerCase()));
} else if ("if".equals(fieldName)) {
// try to get an instance of the provider -> method throws a WorkflowInvalidStateException if provider is not found
collector.getConditionNames().forEach(name -> Workflows.getConditionProvider(session, name, expression));
// try to get an instance of the provider factory -> method throws a WorkflowInvalidStateException if provider factory is not found
collector.getConditionNames().forEach(name -> Workflows.getConditionProviderFactory(session, name.replace("_", "-").toLowerCase()));
}
}

View file

@ -1,7 +1,5 @@
package org.keycloak.models.workflow.conditions;
import java.util.Set;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
@ -31,8 +29,8 @@ public class GroupMembershipWorkflowConditionProvider implements WorkflowConditi
}
@Override
public Set<ResourceType> supportedTypes() {
return Set.of(ResourceType.USERS);
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override

View file

@ -1,6 +1,5 @@
package org.keycloak.models.workflow.conditions;
import java.util.Set;
import java.util.stream.Stream;
import jakarta.persistence.criteria.CriteriaBuilder;
@ -31,8 +30,8 @@ public class IdentityProviderWorkflowConditionProvider implements WorkflowCondit
}
@Override
public Set<ResourceType> supportedTypes() {
return Set.of(ResourceType.USERS);
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override

View file

@ -33,8 +33,8 @@ public class RoleWorkflowConditionProvider implements WorkflowConditionProvider
}
@Override
public Set<ResourceType> supportedTypes() {
return Set.of(ResourceType.USERS);
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override

View file

@ -4,7 +4,6 @@ import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
@ -35,8 +34,8 @@ public class UserAttributeWorkflowConditionProvider implements WorkflowCondition
}
@Override
public Set<ResourceType> supportedTypes() {
return Set.of(ResourceType.USERS);
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override

View file

@ -1,21 +0,0 @@
package org.keycloak.models.workflow.conditions.expression;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.WorkflowEvent;
public class EventEvaluator extends AbstractBooleanEvaluator {
private final WorkflowEvent event;
public EventEvaluator(WorkflowEvent event) {
this.event = event;
}
@Override
public Boolean visitConditionCall(BooleanConditionParser.ConditionCallContext ctx) {
String name = ctx.Identifier().getText();
ResourceOperationType operation = ResourceOperationType.valueOf(name.replace("-", "_").toUpperCase());
String param = super.extractParameter(ctx.parameter());
return operation.test(event, param);
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class ClientAuthenticatedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "client-authenticated";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new ClientAuthenticatedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,24 @@
package org.keycloak.models.workflow.events;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
public class ClientAuthenticatedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public ClientAuthenticatedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.CLIENTS;
}
@Override
public boolean supports(Event event) {
return EventType.CLIENT_LOGIN.equals(event.getType());
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class ClientCreatedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "client-created";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new ClientCreatedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,32 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.provider.ProviderEvent;
public class ClientCreatedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public ClientCreatedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.CLIENTS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof ClientModel.ClientCreationEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof ClientModel.ClientCreationEvent cce) {
return cce.getCreatedClient().getId();
}
return null;
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserAuthenticatedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-authenticated";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserAuthenticatedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,40 @@
package org.keycloak.models.workflow.events;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
public class UserAuthenticatedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserAuthenticatedWorkflowEventProvider(KeycloakSession session, String configParameter, String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(Event event) {
return EventType.LOGIN.equals(event.getType());
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
// this is the case when the clientId is passed as a parameter to the event provider - like user-logged-in(account-console)
Event loginEvent = (Event) context.getEvent().getEvent();
return loginEvent != null && configParameter.equals(loginEvent.getClientId());
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserCreatedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-created";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserCreatedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,32 @@
package org.keycloak.models.workflow.events;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
public class UserCreatedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserCreatedWorkflowEventProvider(KeycloakSession session, String configParameter, String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(Event event) {
return EventType.REGISTER.equals(event.getType());
}
@Override
public boolean supports(AdminEvent adminEvent) {
return org.keycloak.events.admin.ResourceType.USER.equals(adminEvent.getResourceType())
&& OperationType.CREATE.equals(adminEvent.getOperationType());
}
}

View file

@ -0,0 +1,21 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserFedIdentityAddedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-federated-identity-added";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserFedIdentityAddedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,52 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityCreatedEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
public class UserFedIdentityAddedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserFedIdentityAddedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof FederatedIdentityCreatedEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof FederatedIdentityCreatedEvent fie) {
return fie.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
// this is the case when the idp alias is passed as a parameter to the event provider - like user-federated-identity-added(myidp)
ProviderEvent fedIdentityEvent = (ProviderEvent) context.getEvent().getEvent();
if (fedIdentityEvent instanceof FederatedIdentityCreatedEvent fie) {
return configParameter.equals(fie.getFederatedIdentity().getIdentityProvider());
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserFedIdentityRemovedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-federated-identity-removed";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserFedIdentityRemovedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,52 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityRemovedEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
public class UserFedIdentityRemovedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserFedIdentityRemovedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof FederatedIdentityRemovedEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof FederatedIdentityRemovedEvent fie) {
return fie.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
// this is the case when the idp alias is passed as a parameter to the event provider - like user-federated-identity-removed(myidp)
ProviderEvent fedIdentityEvent = (ProviderEvent) context.getEvent().getEvent();
if (fedIdentityEvent instanceof FederatedIdentityRemovedEvent fie) {
return configParameter.equals(fie.getFederatedIdentity().getIdentityProvider());
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserGroupMembershipAddedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-group-membership-added";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserGroupMembershipAddedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,58 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
import static org.keycloak.models.utils.KeycloakModelUtils.GROUP_PATH_SEPARATOR;
public class UserGroupMembershipAddedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserGroupMembershipAddedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof GroupMemberJoinEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof GroupMemberJoinEvent gme) {
return gme.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
String groupName = configParameter;
// this is the case when the group name is passed as a parameter to the event provider - like user-group-membership-added(mygroup)
if (!groupName.startsWith(GROUP_PATH_SEPARATOR))
groupName = GROUP_PATH_SEPARATOR + groupName;
ProviderEvent groupEvent = (ProviderEvent) context.getEvent().getEvent();
if (groupEvent instanceof GroupMemberJoinEvent joinEvent) {
return groupName.equals(KeycloakModelUtils.buildGroupPath(joinEvent.getGroup()));
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserGroupMembershipRemovedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-group-membership-removed";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserGroupMembershipRemovedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,58 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.GroupModel.GroupMemberLeaveEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
import static org.keycloak.models.utils.KeycloakModelUtils.GROUP_PATH_SEPARATOR;
public class UserGroupMembershipRemovedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserGroupMembershipRemovedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof GroupMemberLeaveEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof GroupMemberLeaveEvent gme) {
return gme.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
String groupName = configParameter;
// this is the case when the group name is passed as a parameter to the event provider - like user-group-membership-removed(mygroup)
if (!groupName.startsWith(GROUP_PATH_SEPARATOR))
groupName = GROUP_PATH_SEPARATOR + groupName;
ProviderEvent groupEvent = (ProviderEvent) context.getEvent().getEvent();
if (groupEvent instanceof GroupMemberLeaveEvent leaveEvent) {
return groupName.equals(KeycloakModelUtils.buildGroupPath(leaveEvent.getGroup()));
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserRoleGrantedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-role-granted";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserRoleGrantedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,52 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel.RoleGrantedEvent;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
public class UserRoleGrantedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserRoleGrantedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof RoleGrantedEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof RoleGrantedEvent rge) {
return rge.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
// this is the case when the role name is passed as a parameter to the event provider - like user-role-granted(myrole)
ProviderEvent roleEvent = (ProviderEvent) context.getEvent().getEvent();
if (roleEvent instanceof RoleGrantedEvent roleGrantedEvent) {
return configParameter.equals(roleGrantedEvent.getRole().getName());
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -0,0 +1,20 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowEventProviderFactory;
public class UserRoleRevokedWorkflowEventFactory implements WorkflowEventProviderFactory<WorkflowEventProvider> {
public static final String ID = "user-role-revoked";
@Override
public WorkflowEventProvider create(KeycloakSession session, String configParameter) {
return new UserRoleRevokedWorkflowEventProvider(session, configParameter, this.getId());
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,52 @@
package org.keycloak.models.workflow.events;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RoleModel.RoleRevokedEvent;
import org.keycloak.models.workflow.AbstractWorkflowEventProvider;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import org.keycloak.provider.ProviderEvent;
public class UserRoleRevokedWorkflowEventProvider extends AbstractWorkflowEventProvider {
public UserRoleRevokedWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
super(session, configParameter, providerId);
}
@Override
public ResourceType getSupportedResourceType() {
return ResourceType.USERS;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return providerEvent instanceof RoleRevokedEvent;
}
@Override
protected String resolveResourceId(ProviderEvent providerEvent) {
if (providerEvent instanceof RoleRevokedEvent rre) {
return rre.getUser().getId();
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
if (!super.evaluate(context)) {
return false;
}
if (super.configParameter != null) {
// this is the case when the role name is passed as a parameter to the event provider - like user-role-revoked(myrole)
ProviderEvent roleEvent = (ProviderEvent) context.getEvent().getEvent();
if (roleEvent instanceof RoleRevokedEvent roleRevokedEvent) {
return configParameter.equals(roleRevokedEvent.getRole().getName());
} else {
return false;
}
} else {
// nothing else to check
return true;
}
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
public abstract class AbstractBooleanEvaluator extends BooleanConditionParserBaseVisitor<Boolean> {

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowConditionProvider;
@ -19,7 +19,7 @@ public class ConditionEvaluator extends AbstractBooleanEvaluator {
@Override
public Boolean visitConditionCall(BooleanConditionParser.ConditionCallContext ctx) {
String conditionName = ctx.Identifier().getText();
WorkflowConditionProvider conditionProvider = getConditionProvider(session, conditionName, super.extractParameter(ctx.parameter()));
WorkflowConditionProvider conditionProvider = getConditionProvider(session, conditionName.replace("_", "-").toLowerCase(), super.extractParameter(ctx.parameter()));
return conditionProvider.evaluate(context);
}

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import java.util.ArrayList;
import java.util.List;

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
final class ConditionParserUtil {

View file

@ -1,7 +1,8 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.keycloak.models.KeycloakSession;
@ -9,7 +10,7 @@ import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.WorkflowConditionProvider;
import static org.keycloak.models.workflow.Workflows.getConditionProvider;
import static org.keycloak.models.workflow.conditions.expression.ConditionParserUtil.extractParameter;
import static org.keycloak.models.workflow.expression.ConditionParserUtil.extractParameter;
/**
* This visitor traverses the entire parse tree and collects the supported types of all conditionCalls.
@ -45,7 +46,7 @@ public class ConditionTypeCollector extends BooleanConditionParserBaseVisitor<Vo
String conditionName = ctx.Identifier().getText();
WorkflowConditionProvider conditionProvider = getConditionProvider(session, conditionName, extractParameter(ctx.parameter()));
resourceTypes.retainAll(conditionProvider.supportedTypes());
resourceTypes.retainAll(List.of(conditionProvider.getSupportedResourceType()));
// We don't need to visit children (like 'parameter')
return null;

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import java.util.ArrayList;
import java.util.List;

View file

@ -1,10 +1,10 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import java.util.stream.Collectors;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.workflow.WorkflowInvalidStateException;
import org.keycloak.models.workflow.conditions.expression.BooleanConditionParser.EvaluatorContext;
import org.keycloak.models.workflow.expression.BooleanConditionParser.EvaluatorContext;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;

View file

@ -0,0 +1,25 @@
package org.keycloak.models.workflow.expression;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.workflow.WorkflowEventProvider;
import org.keycloak.models.workflow.WorkflowExecutionContext;
import static org.keycloak.models.workflow.Workflows.getEventProvider;
public class EventEvaluator extends AbstractBooleanEvaluator {
private final WorkflowExecutionContext context;
private final KeycloakSession session;
public EventEvaluator(KeycloakSession session, WorkflowExecutionContext context) {
this.context = context;
this.session = session;
}
@Override
public Boolean visitConditionCall(BooleanConditionParser.ConditionCallContext ctx) {
String name = ctx.Identifier().getText();
WorkflowEventProvider provider = getEventProvider(session, name.replace("_", "-").toLowerCase(), super.extractParameter(ctx.parameter()));
return provider.evaluate(context);
}
}

View file

@ -1,4 +1,4 @@
package org.keycloak.models.workflow.conditions.expression;
package org.keycloak.models.workflow.expression;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;

View file

@ -0,0 +1,13 @@
# user event providers
org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory
org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory
org.keycloak.models.workflow.events.UserFedIdentityAddedWorkflowEventFactory
org.keycloak.models.workflow.events.UserFedIdentityRemovedWorkflowEventFactory
org.keycloak.models.workflow.events.UserGroupMembershipAddedWorkflowEventFactory
org.keycloak.models.workflow.events.UserGroupMembershipRemovedWorkflowEventFactory
org.keycloak.models.workflow.events.UserRoleGrantedWorkflowEventFactory
org.keycloak.models.workflow.events.UserRoleRevokedWorkflowEventFactory
# client event providers
org.keycloak.models.workflow.events.ClientAuthenticatedWorkflowEventFactory
org.keycloak.models.workflow.events.ClientCreatedWorkflowEventFactory

View file

@ -0,0 +1,73 @@
package org.keycloak.models.workflow;
import java.util.Objects;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderEvent;
public abstract class AbstractWorkflowEventProvider implements WorkflowEventProvider {
protected final String providerId;
protected final String configParameter;
protected final KeycloakSession session;
public AbstractWorkflowEventProvider(final KeycloakSession session, final String configParameter, final String providerId) {
this.providerId = providerId;
this.configParameter = configParameter;
this.session = session;
}
@Override
public WorkflowEvent create(Event event) {
if (supports(event)) {
ResourceType resourceType = getSupportedResourceType();
String resourceIdFromEvent = resourceType.resolveResourceId(session, event);
return resourceIdFromEvent != null ? new WorkflowEvent(resourceType, resourceIdFromEvent, event, providerId) : null;
}
return null;
}
@Override
public WorkflowEvent create(AdminEvent adminEvent) {
if (supports(adminEvent)) {
return new WorkflowEvent(getSupportedResourceType(), adminEvent.getResourceId(), adminEvent, providerId);
}
return null;
}
@Override
public WorkflowEvent create(ProviderEvent providerEvent) {
if (supports(providerEvent)) {
String resourceId = resolveResourceId(providerEvent);
return resourceId != null ? new WorkflowEvent(getSupportedResourceType(), resourceId, providerEvent, providerId) : null;
}
return null;
}
@Override
public boolean evaluate(WorkflowExecutionContext context) {
WorkflowEvent event = context.getEvent();
return event != null && Objects.equals(this.providerId, event.getEventProviderId());
}
@Override
public boolean supports(Event event) {
return false;
}
@Override
public boolean supports(AdminEvent adminEvent) {
return false;
}
@Override
public boolean supports(ProviderEvent providerEvent) {
return false;
}
protected String resolveResourceId(ProviderEvent providerEvent) {
return null;
}
}

View file

@ -1,8 +1,10 @@
package org.keycloak.models.workflow;
import org.keycloak.representations.workflows.WorkflowConstants;
final class AdhocWorkflowEvent extends WorkflowEvent {
AdhocWorkflowEvent(ResourceType type, String resourceId) {
super(type, ResourceOperationType.AD_HOC, resourceId, null);
super(type, resourceId, null, WorkflowConstants.AD_HOC);
}
}

View file

@ -1,185 +0,0 @@
package org.keycloak.models.workflow;
import java.util.List;
import java.util.function.BiPredicate;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel.ClientCreationEvent;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityCreatedEvent;
import org.keycloak.models.FederatedIdentityModel.FederatedIdentityRemovedEvent;
import org.keycloak.models.GroupModel;
import org.keycloak.models.GroupModel.GroupMemberJoinEvent;
import org.keycloak.models.GroupModel.GroupMemberLeaveEvent;
import org.keycloak.models.RoleModel;
import org.keycloak.models.RoleModel.RoleGrantedEvent;
import org.keycloak.models.RoleModel.RoleRevokedEvent;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderEvent;
import static org.keycloak.models.utils.KeycloakModelUtils.GROUP_PATH_SEPARATOR;
public enum ResourceOperationType {
USER_CREATED(ResourceType.USERS, List.of(OperationType.CREATE, EventType.REGISTER)),
USER_AUTHENTICATED(ResourceType.USERS,List.of(EventType.LOGIN), userLoginPredicate()),
USER_FEDERATED_IDENTITY_ADDED(ResourceType.USERS,List.of(FederatedIdentityCreatedEvent.class), fedIdentityPredicate()),
USER_FEDERATED_IDENTITY_REMOVED(ResourceType.USERS,List.of(FederatedIdentityRemovedEvent.class), fedIdentityPredicate()),
USER_GROUP_MEMBERSHIP_ADDED(ResourceType.USERS,List.of(GroupMemberJoinEvent.class), groupMembershipPredicate()),
USER_GROUP_MEMBERSHIP_REMOVED(ResourceType.USERS,List.of(GroupModel.GroupMemberLeaveEvent.class), groupMembershipPredicate()),
USER_ROLE_GRANTED(ResourceType.USERS,List.of(RoleGrantedEvent.class), roleMembershipPredicate()),
USER_ROLE_REVOKED(ResourceType.USERS,List.of(RoleModel.RoleRevokedEvent.class), roleMembershipPredicate()),
CLIENT_ADDED(ResourceType.CLIENTS, List.of(OperationType.CREATE, ClientCreationEvent.class)),
CLIENT_LOGGED_IN(ResourceType.CLIENTS, List.of(EventType.CLIENT_LOGIN)),
AD_HOC(ResourceType.USERS, List.of(new Class[] {}));
private final ResourceType resourceType;
private final List<Object> eventTypes;
private final List<Object> deactivationTypes;
private final BiPredicate<WorkflowEvent, String> conditionPredicate;
ResourceOperationType(ResourceType resourceType, List<Object> eventTypes) {
this.resourceType = resourceType;
this.eventTypes = eventTypes;
this.deactivationTypes = List.of();
this.conditionPredicate = defaultPredicate();
}
ResourceOperationType(ResourceType resourceType, List<Object> eventTypes, BiPredicate<WorkflowEvent, String> conditionPredicate) {
this.resourceType = resourceType;
this.eventTypes = eventTypes;
this.deactivationTypes = List.of();
this.conditionPredicate = defaultPredicate().and(conditionPredicate);
}
public ResourceType getResourceType() {
return resourceType;
}
public static ResourceOperationType toOperationType(Enum<?> from) {
return toOperationType(null, from);
}
public static ResourceOperationType toOperationType(Class<?> from) {
return toOperationType(null, from);
}
public static ResourceOperationType toOperationType(ResourceType resourceType, Object from) {
for (ResourceOperationType value : values()) {
if (resourceType != null && !resourceType.equals(value.resourceType)) {
continue;
}
if (value.eventTypes.contains(from)) {
return value;
}
for (Object type : value.eventTypes) {
Class<?> fromClass = from instanceof Class ? (Class<?>) from : from.getClass();
if (type instanceof Class<?> cls && cls.isAssignableFrom(fromClass)) {
return value;
}
}
}
return null;
}
public String getResourceId(ProviderEvent event) {
if (event instanceof GroupMemberJoinEvent gme) {
return gme.getUser().getId();
}
if (event instanceof GroupMemberLeaveEvent gme) {
return gme.getUser().getId();
}
if (event instanceof FederatedIdentityModel.FederatedIdentityCreatedEvent fie) {
return fie.getUser().getId();
}
if (event instanceof FederatedIdentityModel.FederatedIdentityRemovedEvent fie) {
return fie.getUser().getId();
}
if (event instanceof RoleGrantedEvent rge) {
return rge.getUser().getId();
}
if (event instanceof RoleRevokedEvent rre) {
return rre.getUser().getId();
}
return null;
}
public boolean test(WorkflowEvent event, String detail) {
return conditionPredicate.test(event, detail);
}
private BiPredicate<WorkflowEvent, String> defaultPredicate() {
return (event, detail) -> event.getOperation().equals(this);
}
private static BiPredicate<WorkflowEvent, String> userLoginPredicate() {
return (event, detail) -> {
if (detail != null) {
Event loginEvent = (Event) event.getEvent();
return detail.equals(loginEvent.getClientId());
} else {
return true;
}
};
}
private static BiPredicate<WorkflowEvent, String> groupMembershipPredicate() {
return (event, groupName) -> {
if (groupName != null) {
if (!groupName.startsWith(GROUP_PATH_SEPARATOR))
groupName = GROUP_PATH_SEPARATOR + groupName;
ProviderEvent groupEvent = (ProviderEvent) event.getEvent();
if (groupEvent instanceof GroupMemberJoinEvent joinEvent) {
return groupName.equals(KeycloakModelUtils.buildGroupPath(joinEvent.getGroup()));
} else if (groupEvent instanceof GroupModel.GroupMemberLeaveEvent leaveEvent) {
return groupName.equals(KeycloakModelUtils.buildGroupPath(leaveEvent.getGroup()));
} else {
return false;
}
} else {
return true;
}
};
}
private static BiPredicate<WorkflowEvent, String> roleMembershipPredicate() {
return (event, roleName) -> {
if (roleName != null) {
ProviderEvent roleEvent = (ProviderEvent) event.getEvent();
if (roleEvent instanceof RoleGrantedEvent roleGrantedEvent) {
return roleName.equals(roleGrantedEvent.getRole().getName());
} else if (roleEvent instanceof RoleModel.RoleRevokedEvent roleRevokedEvent) {
return roleName.equals(roleRevokedEvent.getRole().getName());
} else {
return false;
}
} else {
return true;
}
};
}
private static BiPredicate<WorkflowEvent, String> fedIdentityPredicate() {
return (event, idpAlias) -> {
if (idpAlias != null) {
ProviderEvent fedIdentityEvent = (ProviderEvent) event.getEvent();
if (fedIdentityEvent instanceof FederatedIdentityModel.FederatedIdentityCreatedEvent fedIdentityCreatedEvent) {
return idpAlias.equals(fedIdentityCreatedEvent.getFederatedIdentity().getIdentityProvider());
} else if (fedIdentityEvent instanceof FederatedIdentityRemovedEvent fedIdentityRemovedEvent) {
return idpAlias.equals(fedIdentityRemovedEvent.getFederatedIdentity().getIdentityProvider());
} else {
return false;
}
} else {
return true;
}
};
}
}

View file

@ -18,52 +18,52 @@
package org.keycloak.models.workflow;
import java.util.List;
import java.util.function.BiFunction;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.Event;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
public enum ResourceType {
USERS(
org.keycloak.events.admin.ResourceType.USER,
List.of(OperationType.CREATE),
List.of(EventType.LOGIN, EventType.REGISTER),
(session, id) -> session.users().getUserById(session.getContext().getRealm(), id)
(session, id) -> session.users().getUserById(session.getContext().getRealm(), id),
(session, event) -> event.getUserId()
),
CLIENTS(
org.keycloak.events.admin.ResourceType.CLIENT,
List.of(OperationType.CREATE),
List.of(EventType.CLIENT_LOGIN, EventType.CLIENT_REGISTER),
(session, id) -> session.clients().getClientById(session.getContext().getRealm(), id)
(session, id) -> session.clients().getClientById(session.getContext().getRealm(), id),
(session, event) -> findClientResourceId(session, event.getClientId())
);
private final org.keycloak.events.admin.ResourceType supportedAdminResourceType;
private final List<OperationType> supportedAdminOperationTypes;
private final List<EventType> supportedEventTypes;
private final BiFunction<KeycloakSession, String, ?> resourceResolver;
private final BiFunction<KeycloakSession, Event, String> resourceIdResolver;
ResourceType(org.keycloak.events.admin.ResourceType supportedAdminResourceType,
List<OperationType> supportedAdminOperationTypes,
List<EventType> supportedEventTypes,
BiFunction<KeycloakSession, String, ?> resourceResolver) {
this.supportedAdminResourceType = supportedAdminResourceType;
this.supportedAdminOperationTypes = supportedAdminOperationTypes;
this.supportedEventTypes = supportedEventTypes;
ResourceType(BiFunction<KeycloakSession, String, ?> resourceResolver,
BiFunction<KeycloakSession, Event, String> resourceIdResolver) {
this.resourceResolver = resourceResolver;
}
public boolean supportsEvent(EventType eventType) {
return supportedEventTypes.contains(eventType);
}
public boolean supportsAdminEvent(org.keycloak.events.admin.ResourceType resourceType, OperationType operationType) {
return supportedAdminResourceType.equals(resourceType) && supportedAdminOperationTypes.contains(operationType);
this.resourceIdResolver = resourceIdResolver;
}
public Object resolveResource(KeycloakSession session, String id) {
return resourceResolver.apply(session, id);
}
public String resolveResourceId(KeycloakSession session, Event event) {
return resourceIdResolver.apply(session, event);
}
private static String findClientResourceId(KeycloakSession session, String clientClientId) {
RealmModel realm = session.getContext().getRealm();
if (realm == null) {
return null;
}
ClientModel client = realm.getClientByClientId(clientClientId);
if (client == null) {
return null;
}
return client.getId();
}
}

View file

@ -180,8 +180,8 @@ public class Workflow {
addStep(step);
// update allowed types
WorkflowStepProviderFactory<WorkflowStepProvider> stepProvider = getStepProviderFactory(step);
allowedTypes.retainAll(stepProvider.getTypes());
WorkflowStepProviderFactory<WorkflowStepProvider> stepProvider = Workflows.getStepProviderFactory(session, step);
allowedTypes.retainAll(stepProvider.getSupportedResourceTypes());
}
if (allowedTypes.isEmpty()) {
@ -225,9 +225,4 @@ public class Workflow {
}
return component;
}
private WorkflowStepProviderFactory<WorkflowStepProvider> getStepProviderFactory(WorkflowStep step) {
return (WorkflowStepProviderFactory<WorkflowStepProvider>) session
.getKeycloakSessionFactory().getProviderFactory(WorkflowStepProvider.class, step.getProviderId());
}
}

View file

@ -1,6 +1,5 @@
package org.keycloak.models.workflow;
import java.util.Set;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
@ -11,7 +10,7 @@ import org.keycloak.provider.Provider;
public interface WorkflowConditionProvider extends Provider {
Set<ResourceType> supportedTypes();
ResourceType getSupportedResourceType();
boolean evaluate(WorkflowExecutionContext context);

View file

@ -13,7 +13,7 @@ public interface WorkflowConditionProviderFactory<P extends WorkflowConditionPro
@Override
default P create(KeycloakSession session) {
throw new IllegalStateException("Use create(KeycloakSession session, MultivaluedHashMap<String, String> config) instead.");
throw new IllegalStateException("Use create(KeycloakSession session, String configParameter) instead.");
}
@Override

View file

@ -3,29 +3,29 @@ package org.keycloak.models.workflow;
public class WorkflowEvent {
private final ResourceType type;
private final ResourceOperationType operation;
private final String resourceId;
private final Object event;
private final String eventProviderId;
public WorkflowEvent(ResourceType type, ResourceOperationType operation, String resourceId, Object event) {
public WorkflowEvent(ResourceType type, String resourceId, Object event, String eventProviderId) {
this.type = type;
this.operation = operation;
this.resourceId = resourceId;
this.event = event;
this.eventProviderId = eventProviderId;
}
public ResourceType getResourceType() {
return type;
}
public ResourceOperationType getOperation() {
return operation;
}
public String getResourceId() {
return resourceId;
}
public String getEventProviderId() {
return eventProviderId;
}
public Object getEvent() {
return event;
}

View file

@ -1,85 +0,0 @@
package org.keycloak.models.workflow;
import java.util.Arrays;
import java.util.Optional;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderEvent;
import static org.keycloak.models.workflow.ResourceOperationType.toOperationType;
public final class WorkflowEventConverter {
static Optional<WorkflowEvent> fromEvent(KeycloakSession session, Event event) {
return resourceOfEvent(event)
.map(r -> {
ResourceOperationType resourceOperationType = toOperationType(event.getType());
String resourceId = switch (r) {
case USERS -> event.getUserId();
case CLIENTS -> findClientResourceId(session, event.getClientId());
};
if (resourceOperationType != null && resourceId != null) {
return new WorkflowEvent(r, resourceOperationType, resourceId, event);
}
return null;
});
}
static Optional<WorkflowEvent> fromEvent(AdminEvent event) {
return resourceOfEvent(event)
.map(r -> {
ResourceOperationType resourceOperationType = toOperationType(r, event.getOperationType());
if (resourceOperationType != null) {
return new WorkflowEvent(r, resourceOperationType, event.getResourceId(), event);
}
return null;
});
}
static Optional<WorkflowEvent> fromEvent(ProviderEvent event) {
ResourceOperationType resourceOperationType = toOperationType(event.getClass());
if (resourceOperationType == null) {
return Optional.empty();
}
String resourceId = resourceOperationType.getResourceId(event);
if (resourceId == null) {
return Optional.empty();
}
WorkflowEvent workflowEvent = new WorkflowEvent(resourceOperationType.getResourceType(), resourceOperationType, resourceId, event);
return Optional.of(workflowEvent);
}
private static Optional<ResourceType> resourceOfEvent(Event event) {
// Is it possible for an event to have multiple resources, thus triggering multiple workflows?
return Arrays.stream(ResourceType.values())
.filter(r -> r.supportsEvent(event.getType()))
.findFirst();
}
private static Optional<ResourceType> resourceOfEvent(AdminEvent event) {
return Arrays.stream(ResourceType.values())
.filter(r -> r.supportsAdminEvent(event.getResourceType(), event.getOperationType()))
.findFirst();
}
private static String findClientResourceId(KeycloakSession session, String clientClientId) {
RealmModel realm = session.getContext().getRealm();
if (realm == null) {
return null;
}
ClientModel client = realm.getClientByClientId(clientClientId);
if (client == null) {
return null;
}
return client.getId();
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.models.workflow;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderEvent;
public interface WorkflowEventProvider extends Provider {
ResourceType getSupportedResourceType();
WorkflowEvent create(Event event);
WorkflowEvent create(AdminEvent adminEvent);
WorkflowEvent create(ProviderEvent providerEvent);
boolean supports(Event event);
boolean supports(AdminEvent adminEvent);
boolean supports(ProviderEvent providerEvent);
boolean evaluate(WorkflowExecutionContext context);
@Override
default void close() {
// no-op
}
}

View file

@ -0,0 +1,38 @@
package org.keycloak.models.workflow;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.ProviderFactory;
public interface WorkflowEventProviderFactory <P extends WorkflowEventProvider> extends ProviderFactory<P>, EnvironmentDependentProviderFactory {
P create(KeycloakSession session, String configParameter);
@Override
default P create(KeycloakSession session) {
return create(session, null);
}
@Override
default boolean isSupported(Config.Scope config) {
return Profile.isFeatureEnabled(Profile.Feature.WORKFLOWS);
}
@Override
default void init(Config.Scope config) {
// no-op default
}
@Override
default void postInit(KeycloakSessionFactory factory) {
// no-op default
}
@Override
default void close() {
// no-op default
}
}

View file

@ -0,0 +1,31 @@
package org.keycloak.models.workflow;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class WorkflowEventSpi implements Spi {
public static final String NAME = "workflow-event";
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return NAME;
}
@Override
public Class<? extends Provider> getProviderClass() {
return WorkflowEventProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return WorkflowEventProviderFactory.class;
}
}

View file

@ -22,7 +22,7 @@ import org.keycloak.provider.Provider;
public interface WorkflowStepProvider extends Provider {
/**
* Run this workflow step.
* Runs this workflow step.
*
* @param context the workflow execution context
*/

View file

@ -32,7 +32,7 @@ public interface WorkflowStepProviderFactory<P extends WorkflowStepProvider> ext
/**
* Supported types, usually one type but could be more (RestartStep for example)
*/
Set<ResourceType> getTypes();
Set<ResourceType> getSupportedResourceTypes();
@Override
default void init(Config.Scope config) {

View file

@ -3,22 +3,24 @@ package org.keycloak.models.workflow;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
public final class Workflows {
public static WorkflowConditionProvider getConditionProvider(KeycloakSession session, String name, String expression) {
return getConditionProviderFactory(session, name).create(session, expression);
public static WorkflowConditionProvider getConditionProvider(KeycloakSession session, String name, String configParameter) {
return getConditionProviderFactory(session, name).create(session, configParameter);
}
private static WorkflowConditionProviderFactory<WorkflowConditionProvider> getConditionProviderFactory(KeycloakSession session, String providerId) {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
WorkflowConditionProviderFactory<WorkflowConditionProvider> providerFactory = (WorkflowConditionProviderFactory<WorkflowConditionProvider>) sessionFactory.getProviderFactory(WorkflowConditionProvider.class, providerId);
public static WorkflowConditionProviderFactory<WorkflowConditionProvider> getConditionProviderFactory(KeycloakSession session, String providerId) {
return getProviderFactory(session, WorkflowConditionProvider.class, providerId);
}
if (providerFactory == null) {
throw new WorkflowInvalidStateException("Could not find condition provider: " + providerId);
}
public static WorkflowEventProvider getEventProvider(KeycloakSession session, String name, String configParameter) {
return getEventProviderFactory(session, name).create(session, configParameter);
}
return providerFactory;
public static WorkflowEventProviderFactory<WorkflowEventProvider> getEventProviderFactory(KeycloakSession session, String providerId) {
return getProviderFactory(session, WorkflowEventProvider.class, providerId);
}
public static WorkflowStepProvider getStepProvider(KeycloakSession session, WorkflowStep step) {
@ -27,13 +29,17 @@ public final class Workflows {
}
public static WorkflowStepProviderFactory<WorkflowStepProvider> getStepProviderFactory(KeycloakSession session, WorkflowStep step) {
WorkflowStepProviderFactory<WorkflowStepProvider> factory = (WorkflowStepProviderFactory<WorkflowStepProvider>) session
.getKeycloakSessionFactory().getProviderFactory(WorkflowStepProvider.class, step.getProviderId());
return getProviderFactory(session, WorkflowStepProvider.class, step.getProviderId());
}
if (factory == null) {
throw new WorkflowInvalidStateException("Step not found: " + step.getProviderId());
private static <P extends Provider, F> F getProviderFactory(KeycloakSession session, Class<P> providerClass, String providerId) {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
@SuppressWarnings("unchecked")
F providerFactory = (F) sessionFactory.getProviderFactory(providerClass, providerId);
if (providerFactory == null) {
throw new WorkflowInvalidStateException("Could not find provider factory with id: " + providerId);
}
return factory;
return providerFactory;
}
}

View file

@ -105,9 +105,10 @@ org.keycloak.cookie.CookieSpi
org.keycloak.organization.OrganizationSpi
org.keycloak.securityprofile.SecurityProfileSpi
org.keycloak.logging.MappedDiagnosticContextSpi
org.keycloak.models.workflow.WorkflowConditionSpi
org.keycloak.models.workflow.WorkflowEventSpi
org.keycloak.models.workflow.WorkflowStepSpi
org.keycloak.models.workflow.WorkflowSpi
org.keycloak.models.workflow.WorkflowConditionSpi
org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessorSpi
org.keycloak.cache.AlternativeLookupSPI
org.keycloak.cache.LocalCacheSPI

View file

@ -20,7 +20,7 @@ public class AddRequiredActionStepProviderFactory implements WorkflowStepProvide
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -37,7 +37,7 @@ public class DeleteUserStepProviderFactory implements WorkflowStepProviderFactor
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -37,7 +37,7 @@ public class DisableUserStepProviderFactory implements WorkflowStepProviderFacto
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -20,7 +20,7 @@ public class GrantRoleStepProviderFactory implements WorkflowStepProviderFactory
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -20,7 +20,7 @@ public class JoinGroupStepProviderFactory implements WorkflowStepProviderFactory
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -20,7 +20,7 @@ public class LeaveGroupStepProviderFactory implements WorkflowStepProviderFactor
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -37,7 +37,7 @@ public class NotifyUserStepProviderFactory implements WorkflowStepProviderFactor
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -20,7 +20,7 @@ public class RemoveRequiredActionStepProviderFactory implements WorkflowStepProv
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -39,7 +39,7 @@ public class RemoveUserAttributeStepProviderFactory implements WorkflowStepProvi
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -20,7 +20,7 @@ public class RevokeRoleStepProviderFactory implements WorkflowStepProviderFactor
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -37,7 +37,7 @@ public class SetUserAttributeStepProviderFactory implements WorkflowStepProvider
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.USERS);
}

View file

@ -10,6 +10,8 @@
package org.keycloak.models.workflow;
import java.util.Objects;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
@ -28,12 +30,18 @@ public class WorkflowEventListener implements EventListenerProvider, ProviderEve
@Override
public void onEvent(Event event) {
WorkflowEventConverter.fromEvent(session, event).ifPresent(this::trySchedule);
session.getAllProviders(WorkflowEventProvider.class).stream()
.map(provider -> provider.create(event))
.filter(Objects::nonNull)
.forEach(this::trySchedule);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
WorkflowEventConverter.fromEvent(event).ifPresent(this::trySchedule);
session.getAllProviders(WorkflowEventProvider.class).stream()
.map(provider -> provider.create(event))
.filter(Objects::nonNull)
.forEach(this::trySchedule);
}
@Override
@ -42,8 +50,10 @@ public class WorkflowEventListener implements EventListenerProvider, ProviderEve
if (realm == null) {
return;
}
WorkflowEventConverter.fromEvent(event).ifPresent(this::trySchedule);
session.getAllProviders(WorkflowEventProvider.class).stream()
.map(provider -> provider.create(event))
.filter(Objects::nonNull)
.forEach(this::trySchedule);
}
private void trySchedule(WorkflowEvent event) {

View file

@ -40,7 +40,7 @@ public class DeleteClientStepProviderFactory implements
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.CLIENTS);
}

View file

@ -40,7 +40,7 @@ public class DisableClientStepProviderFactory implements
}
@Override
public Set<ResourceType> getTypes() {
public Set<ResourceType> getSupportedResourceTypes() {
return Set.of(ResourceType.CLIENTS);
}

View file

@ -34,7 +34,6 @@ import org.keycloak.admin.client.resource.WorkflowsResource;
import org.keycloak.models.workflow.DeleteUserStepProviderFactory;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.RestartWorkflowStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
@ -42,6 +41,8 @@ import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.WorkflowStateProvider.ScheduledStep;
import org.keycloak.models.workflow.conditions.IdentityProviderWorkflowConditionFactory;
import org.keycloak.models.workflow.conditions.RoleWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.workflows.StepExecutionStatus;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -68,9 +69,6 @@ import com.fasterxml.jackson.jakarta.rs.yaml.JacksonYAMLProvider;
import com.fasterxml.jackson.jakarta.rs.yaml.YAMLMediaTypes;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_AUTHENTICATED;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
@ -103,7 +101,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testCreate() {
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -230,7 +228,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testFailCreateWorkflowWithNegativeTime() {
WorkflowRepresentation workflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(SetUserAttributeStepProviderFactory.ID)
.after(Duration.ofDays(-5))
@ -247,7 +245,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
public void testFailCreateWorkflowWithDuplicateName() {
// create first workflow
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(SetUserAttributeStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -257,7 +255,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
// try to create second workflow with same name
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.name())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(DisableUserStepProviderFactory.ID)
.after(Duration.ofDays(10))
@ -276,7 +274,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
String workflowId;
try (Response response = workflows.create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_CREATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -288,7 +286,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
}
workflows.create(WorkflowRepresentation.withName("another-workflow")
.onEvent(ResourceOperationType.USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -511,7 +509,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
String[] workflowNames = {"alpha-workflow", "beta-workflow", "gamma-workflow", "delta-workflow"};
for (String name : workflowNames) {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName(name)
.onEvent(ResourceOperationType.USER_CREATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))

View file

@ -13,6 +13,8 @@ import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.client.DeleteClientStepProviderFactory;
import org.keycloak.models.workflow.conditions.UserAttributeWorkflowConditionFactory;
import org.keycloak.models.workflow.events.ClientCreatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.workflows.StepExecutionStatus;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -25,9 +27,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.CLIENT_ADDED;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
@ -42,7 +41,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
public void testMigrationFailsIfWorkflowsNotCompatible() {
// create two incompatible workflows - one that operates on users, another on clients
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("client-workflow")
.onEvent(CLIENT_ADDED.name())
.onEvent(ClientCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DeleteClientStepProviderFactory.ID)
@ -54,7 +53,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
response.close();
response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("user-workflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(AddRequiredActionStepProviderFactory.ID)
@ -93,7 +92,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
public void testMigrationFailsIfResourcesDontMeetWorkflowConditions() {
// create two user workflows, the second having a condition that the users don't meet
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow-1")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DisableUserStepProviderFactory.ID)
@ -144,7 +143,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
public void testMigrateToScheduledStepInDifferentWorkflow() {
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow-1")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DisableUserStepProviderFactory.ID)
@ -210,7 +209,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
@Test
public void testMigrateToImmediateStepInDifferentWorkflow() {
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow-1")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DisableUserStepProviderFactory.ID)
@ -275,7 +274,7 @@ public class WorkflowMigrationTest extends AbstractWorkflowTest {
@Test
public void testMigrateToStepInSameWorkflow() {
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(AddRequiredActionStepProviderFactory.ID)

View file

@ -11,6 +11,7 @@ import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
@ -22,8 +23,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
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.hasItems;
import static org.hamcrest.Matchers.hasSize;
@ -160,7 +159,7 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
@Test
public void testDeactivateWorkflowForResource() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("One")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
@ -174,7 +173,7 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
)
.build()).close();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("Two")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -7,8 +7,9 @@ import jakarta.ws.rs.core.Response.Status;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.WorkflowsResource;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.events.UserGroupMembershipAddedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserGroupMembershipRemovedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
@ -52,7 +53,7 @@ public class GroupMembershipWorkflowTest extends AbstractWorkflowTest {
}
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_GROUP_MEMBERSHIP_ADDED.name() + "(" + GROUP_NAME + ")")
.onEvent(UserGroupMembershipAddedWorkflowEventFactory.ID + "(" + GROUP_NAME + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
@ -100,7 +101,7 @@ public class GroupMembershipWorkflowTest extends AbstractWorkflowTest {
}
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_GROUP_MEMBERSHIP_REMOVED.name() + "(" + GROUP_NAME + ")")
.onEvent(UserGroupMembershipRemovedWorkflowEventFactory.ID + "(" + GROUP_NAME + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -6,8 +6,9 @@ import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.WorkflowsResource;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.events.UserFedIdentityAddedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserFedIdentityRemovedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -42,7 +43,7 @@ public class IdpLinkingWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on IdP linking
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_FEDERATED_IDENTITY_ADDED.name() + "(" + IDP_ALIAS + ")")
.onEvent(UserFedIdentityAddedWorkflowEventFactory.ID + "(" + IDP_ALIAS + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
@ -81,7 +82,7 @@ public class IdpLinkingWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on IdP unlinking
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_FEDERATED_IDENTITY_REMOVED.name() + "(" + IDP_ALIAS + ")")
.onEvent(UserFedIdentityRemovedWorkflowEventFactory.ID + "(" + IDP_ALIAS + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -4,8 +4,9 @@ import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.WorkflowsResource;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.events.UserRoleGrantedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserRoleRevokedWorkflowEventFactory;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
@ -45,7 +46,7 @@ public class RoleMembershipWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on role grant
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ROLE_GRANTED.name() + "(" + ROLE_NAME + ")")
.onEvent(UserRoleGrantedWorkflowEventFactory.ID + "(" + ROLE_NAME + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
@ -85,7 +86,7 @@ public class RoleMembershipWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on role revoke
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ROLE_REVOKED.name() + "(" + ROLE_NAME + ")")
.onEvent(UserRoleRevokedWorkflowEventFactory.ID + "(" + ROLE_NAME + ")")
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -26,6 +26,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.InjectUser;
@ -41,8 +43,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_AUTHENTICATED;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.keycloak.tests.workflow.util.EmailTestUtils.findEmailByRecipient;
import static org.keycloak.tests.workflow.util.EmailTestUtils.findEmailsByRecipient;
import static org.keycloak.tests.workflow.util.EmailTestUtils.verifyEmailContent;
@ -68,7 +68,7 @@ public class UserAuthenticationWorkflowTest extends AbstractWorkflowTest {
@Test
public void testActivateWorkflowOnUserAuthentication() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().restartInProgress("true") // this setting enables restarting the workflow
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
@ -143,7 +143,7 @@ public class UserAuthenticationWorkflowTest extends AbstractWorkflowTest {
@Test
public void testMultipleWorkflows() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.toString(), USER_AUTHENTICATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID, UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -152,7 +152,7 @@ public class UserAuthenticationWorkflowTest extends AbstractWorkflowTest {
.build())
.build()).close();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow_2")
.onEvent(USER_CREATED.toString(), USER_AUTHENTICATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID, UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(10))

View file

@ -3,8 +3,8 @@ package org.keycloak.tests.workflow.activation;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -35,7 +35,7 @@ public class UserCreationWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on user creation
WorkflowRepresentation workflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -10,12 +10,12 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.conditions.GroupMembershipWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -56,7 +56,7 @@ public class GroupWorkflowConditionTest extends AbstractWorkflowTest {
// create workflow that activates on user creation with a group membership condition
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("group-membership-workflow")
.onEvent(ResourceOperationType.USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.onCondition(GROUP_CONDITION)
.withSteps(
WorkflowStepRepresentation.create()

View file

@ -28,7 +28,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
@ -36,6 +35,8 @@ import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.WorkflowStateProvider.ScheduledStep;
import org.keycloak.models.workflow.WorkflowStep;
import org.keycloak.models.workflow.conditions.IdentityProviderWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserFedIdentityAddedWorkflowEventFactory;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
@ -53,8 +54,6 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder;
import org.awaitility.Awaitility;
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;
import static org.hamcrest.Matchers.is;
@ -84,7 +83,7 @@ public class IdpLinkConditionWorkflowTest extends AbstractWorkflowTest {
// create the workflow that triggers on IdP linking with a condition for the specific IdP
WorkflowRepresentation workflow = WorkflowRepresentation.withName("idp-members-workflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.onCondition(IdentityProviderWorkflowConditionFactory.ID + "(" + IDP_OIDC_ALIAS + ")")
.withSteps(
WorkflowStepRepresentation.create()
@ -133,7 +132,7 @@ public class IdpLinkConditionWorkflowTest extends AbstractWorkflowTest {
setupIdentityProvider();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_FEDERATED_IDENTITY_ADDED.name())
.onEvent(UserFedIdentityAddedWorkflowEventFactory.ID)
.onCondition(IdentityProviderWorkflowConditionFactory.ID + "(" + IDP_OIDC_ALIAS + ")")
.schedule(WorkflowScheduleRepresentation.create().after("1s").build())
.withSteps(

View file

@ -12,7 +12,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.RestartWorkflowStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.Workflow;
@ -20,6 +19,7 @@ import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.WorkflowStateProvider.ScheduledStep;
import org.keycloak.models.workflow.conditions.RoleWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserRoleGrantedWorkflowEventFactory;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
@ -172,7 +172,7 @@ public class RoleWorkflowConditionTest extends AbstractWorkflowTest {
.reduce((a, b) -> a + " AND " + b).orElse(null);
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ROLE_GRANTED.name())
.onEvent(UserRoleGrantedWorkflowEventFactory.ID)
.onCondition(roleCondition)
.withSteps(
WorkflowStepRepresentation.create()

View file

@ -11,7 +11,6 @@ import jakarta.ws.rs.core.Response.Status;
import org.keycloak.admin.client.resource.WorkflowsResource;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.RestartWorkflowStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.Workflow;
@ -19,6 +18,7 @@ import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.WorkflowStateProvider.ScheduledStep;
import org.keycloak.models.workflow.conditions.UserAttributeWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -161,7 +161,7 @@ public class UserAttributeWorkflowConditionTest extends AbstractWorkflowTest {
.orElse(null);
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.schedule(WorkflowScheduleRepresentation.create().after("1s").build())
.onCondition(attributeCondition)
.withSteps(

View file

@ -13,8 +13,11 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DeleteUserStepProviderFactory;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.conditions.IdentityProviderWorkflowConditionFactory;
import org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserFedIdentityAddedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserFedIdentityRemovedWorkflowEventFactory;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -55,8 +58,6 @@ import org.awaitility.Awaitility;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_AUTHENTICATED;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.keycloak.tests.workflow.util.EmailTestUtils.findEmailByRecipient;
import static org.keycloak.tests.workflow.util.EmailTestUtils.verifyEmailContent;
@ -129,7 +130,7 @@ public class BrokeredUserLifecycleWorkflowTest extends AbstractWorkflowTest {
// create a workflow that notifies inactive users after 7 days, disables them 30 days after that if the user doesn't
// log back in, and finally deletes them also 30 days after being disabled.
consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.onCondition(IDP_CONDITION)
.concurrency().restartInProgress("true")
.withSteps(
@ -198,7 +199,7 @@ public class BrokeredUserLifecycleWorkflowTest extends AbstractWorkflowTest {
public void testNonBrokeredUserNotAffectedByWorkflow() {
// create a workflow that deletes inactive users after 10 days.
consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.onCondition(IDP_CONDITION)
.withSteps(
WorkflowStepRepresentation.create().of(DeleteUserStepProviderFactory.ID)
@ -221,7 +222,7 @@ public class BrokeredUserLifecycleWorkflowTest extends AbstractWorkflowTest {
public void testInvalidateWorkflowOnIdentityProviderRemoval() {
String workflowId;
try (Response response = consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.toString(), USER_AUTHENTICATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID, UserAuthenticatedWorkflowEventFactory.ID)
.onCondition(IDP_CONDITION)
.withSteps(
WorkflowStepRepresentation.create().of(DeleteUserStepProviderFactory.ID)
@ -266,8 +267,8 @@ public class BrokeredUserLifecycleWorkflowTest extends AbstractWorkflowTest {
// create a workflow that deletes users 1 day after a federated identity is added, and that is cancelled if the identity is removed
consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_FEDERATED_IDENTITY_ADDED.name() + "(" + IDP_OIDC_ALIAS + ")")
.concurrency().cancelInProgress(ResourceOperationType.USER_FEDERATED_IDENTITY_REMOVED.name() + "(" + IDP_OIDC_ALIAS + ")")
.onEvent(UserFedIdentityAddedWorkflowEventFactory.ID + "(" + IDP_OIDC_ALIAS + ")")
.concurrency().cancelInProgress(UserFedIdentityRemovedWorkflowEventFactory.ID + "(" + IDP_OIDC_ALIAS + ")")
.withSteps(
WorkflowStepRepresentation.create().of(DeleteUserStepProviderFactory.ID)
.after(Duration.ofDays(1))

View file

@ -11,10 +11,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@ -52,7 +52,7 @@ public class DisableActiveWorkflowTest extends AbstractWorkflowTest {
// create a test workflow
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("test-workflow")
.onEvent(ResourceOperationType.USER_CREATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))

View file

@ -3,6 +3,7 @@ package org.keycloak.tests.workflow.execution;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@ -12,8 +13,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
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.is;
import static org.hamcrest.Matchers.notNullValue;
@ -29,7 +28,7 @@ public class ImmediateWorkflowExecutionTest extends AbstractWorkflowTest {
public void testRunImmediateWorkflow() {
// create a test workflow with no time conditions - should run immediately when scheduled
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(SetUserAttributeStepProviderFactory.ID)
.withConfig("message", "message")

View file

@ -10,11 +10,11 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.WorkflowStep;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@ -44,7 +44,7 @@ public class ScheduledWorkflowExecutionTest extends AbstractWorkflowTest {
@Test
public void testWorkflowDoesNotFallThroughStepsInSingleRun() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_CREATED.toString())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))

View file

@ -32,6 +32,7 @@ import org.keycloak.models.UserProvider;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.WorkflowStepRunnerSuccessEvent;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -45,8 +46,6 @@ import org.keycloak.tests.workflow.config.WorkflowsScheduledTaskServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest(config = WorkflowsScheduledTaskServerConfig.class)
@ -73,7 +72,7 @@ public class StepRunnerScheduledTaskTest extends AbstractWorkflowTest {
RealmResource realm = adminClient.realm(realmName);
realm.workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(SetUserAttributeStepProviderFactory.ID)
.after(Duration.ofDays(5))

View file

@ -10,6 +10,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.InjectUser;
@ -25,8 +26,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_AUTHENTICATED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
@ -47,7 +46,7 @@ public class WorkflowConcurrencyTest extends AbstractWorkflowTest {
public void testWorkflowIsRestartedOnSameEvent() {
// create a workflow that can be restarted on the same event - i.e. has concurrency setting with restart-in-progress=true
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().restartInProgress("true")
.withSteps(
WorkflowStepRepresentation.create()
@ -89,7 +88,7 @@ public class WorkflowConcurrencyTest extends AbstractWorkflowTest {
// create a workflow that can be restarted on a different event - i.e. restart-in-progress is set to an event expression
// in this case we will use user-group-membership-added event to restart the workflow when user joins the group "testgroup"
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().restartInProgress("user-group-membership-added(testgroup)")
.withSteps(
WorkflowStepRepresentation.create()
@ -112,7 +111,7 @@ public class WorkflowConcurrencyTest extends AbstractWorkflowTest {
public void testWorkflowIsCancelledOnSameEvent() {
// create a workflow that can be cancelled on the same event - i.e. has concurrency setting with cancel-in-progress=true
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().cancelInProgress("true")
.withSteps(
WorkflowStepRepresentation.create()
@ -154,7 +153,7 @@ public class WorkflowConcurrencyTest extends AbstractWorkflowTest {
// create a workflow that can be cancelled on a different event - i.e. cancel-in-progress is set to an event expression
// in this case we will use user-group-membership-added event to cancel the workflow when user joins the group "testgroup"
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().cancelInProgress("user-group-membership-added(testgroup)")
.withSteps(
WorkflowStepRepresentation.create()
@ -189,7 +188,7 @@ public class WorkflowConcurrencyTest extends AbstractWorkflowTest {
// create workflow with both settings - restart-in-progress on same event, cancel-in-progress on different event
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.concurrency().restartInProgress("true")
.cancelInProgress("user-group-membership-added(testgroup)")
.withSteps(

View file

@ -5,6 +5,7 @@ import java.time.Duration;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.AddRequiredActionStepProvider;
import org.keycloak.models.workflow.AddRequiredActionStepProviderFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@ -16,8 +17,6 @@ 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;
import static org.hamcrest.Matchers.is;
@ -31,7 +30,7 @@ public class AddRequiredActionTest extends AbstractWorkflowTest {
@Test
public void testStepRun() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(AddRequiredActionStepProviderFactory.ID)

View file

@ -1,4 +1,4 @@
package org.keycloak.tests.admin.model.workflow;
package org.keycloak.tests.workflow.step;
import java.time.Duration;
@ -9,6 +9,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.workflow.RestartWorkflowStepProviderFactory;
import org.keycloak.models.workflow.client.DeleteClientStepProviderFactory;
import org.keycloak.models.workflow.client.DisableClientStepProviderFactory;
import org.keycloak.models.workflow.events.ClientAuthenticatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.ClientCreatedWorkflowEventFactory;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
@ -21,9 +23,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.CLIENT_ADDED;
import static org.keycloak.models.workflow.ResourceOperationType.CLIENT_LOGGED_IN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -42,7 +41,7 @@ public class DeleteClientStepTest extends AbstractWorkflowTest {
@Test
public void testStepRun() {
var response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(CLIENT_ADDED.name())
.onEvent(ClientCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DeleteClientStepProviderFactory.ID)
@ -75,7 +74,7 @@ public class DeleteClientStepTest extends AbstractWorkflowTest {
@Test
public void testDisabledClientAfterInactivityPeriod() {
WorkflowRepresentation workflowRepresentation = WorkflowRepresentation.withName("myworkflow")
.onEvent(CLIENT_ADDED.toString(), CLIENT_LOGGED_IN.toString())
.onEvent(ClientCreatedWorkflowEventFactory.ID, ClientAuthenticatedWorkflowEventFactory.ID)
.concurrency()
.withSteps(
WorkflowStepRepresentation.create().of(DisableClientStepProviderFactory.ID)

View file

@ -28,11 +28,11 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.workflow.DeleteUserStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.SetUserAttributeStepProviderFactory;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.models.workflow.WorkflowStateProvider;
import org.keycloak.models.workflow.events.UserAuthenticatedWorkflowEventFactory;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -52,7 +52,7 @@ import org.keycloak.testframework.ui.webdriver.ManagedWebDriver;
import org.keycloak.testframework.util.ApiUtil;
import org.keycloak.tests.workflow.AbstractWorkflowTest;
import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.keycloak.tests.workflow.step.DeleteUserWorkflowStepTest.DeleteUserWorkflowServerConf;
import org.keycloak.tests.workflow.step.DeleteUserStepTest.DeleteUserWorkflowServerConf;
import org.keycloak.testsuite.federation.DummyUserFederationProvider;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
@ -75,7 +75,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* Tests the execution of the 'delete-user' workflow step.
*/
@KeycloakIntegrationTest(config = DeleteUserWorkflowServerConf.class)
public class DeleteUserWorkflowStepTest extends AbstractWorkflowTest {
public class DeleteUserStepTest extends AbstractWorkflowTest {
@InjectWebDriver
ManagedWebDriver driver;
@ -110,7 +110,7 @@ public class DeleteUserWorkflowStepTest extends AbstractWorkflowTest {
}
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(builder.build()).build()).close();
String componentId = addDummyFederationProvider();
@ -170,7 +170,7 @@ public class DeleteUserWorkflowStepTest extends AbstractWorkflowTest {
// create a couple of workflows that will activate for the test user
// the first one will run the delete user step before the second one runs its first step
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow1")
.onEvent(ResourceOperationType.USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(DeleteUserStepProviderFactory.ID)
@ -178,7 +178,7 @@ public class DeleteUserWorkflowStepTest extends AbstractWorkflowTest {
.build()
).build()).close();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("workflow2")
.onEvent(ResourceOperationType.USER_AUTHENTICATED.toString())
.onEvent(UserAuthenticatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)

View file

@ -15,7 +15,8 @@ import org.keycloak.models.workflow.JoinGroupStepProvider;
import org.keycloak.models.workflow.JoinGroupStepProviderFactory;
import org.keycloak.models.workflow.LeaveGroupStepProvider;
import org.keycloak.models.workflow.LeaveGroupStepProviderFactory;
import org.keycloak.models.workflow.ResourceOperationType;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.models.workflow.events.UserGroupMembershipRemovedWorkflowEventFactory;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.workflows.WorkflowRepresentation;
@ -31,8 +32,6 @@ import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
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.containsInAnyOrder;
@ -62,7 +61,7 @@ public class GroupBasedStepTest extends AbstractWorkflowTest {
List<String> expectedGroups = List.of("/a", "/b/b1", "c");
create(WorkflowRepresentation.withName("join-group")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(JoinGroupStepProviderFactory.ID)
@ -95,7 +94,7 @@ public class GroupBasedStepTest extends AbstractWorkflowTest {
joinGroup(user, "a", "/a/a1", "b/b1", "b/b2", "/c");
create(WorkflowRepresentation.withName("leave-group")
.onEvent(ResourceOperationType.USER_GROUP_MEMBERSHIP_REMOVED.name())
.onEvent(UserGroupMembershipRemovedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(LeaveGroupStepProviderFactory.ID)

View file

@ -30,6 +30,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.DeleteUserStepProviderFactory;
import org.keycloak.models.workflow.DisableUserStepProviderFactory;
import org.keycloak.models.workflow.NotifyUserStepProviderFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.StepExecutionStatus;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
@ -47,7 +48,6 @@ import org.keycloak.tests.workflow.config.WorkflowsBlockingServerConfig;
import org.junit.jupiter.api.Test;
import static org.keycloak.models.workflow.ResourceOperationType.USER_CREATED;
import static org.keycloak.tests.workflow.util.EmailTestUtils.findEmailByRecipient;
import static org.keycloak.tests.workflow.util.EmailTestUtils.findEmailByRecipientContaining;
import static org.keycloak.tests.workflow.util.EmailTestUtils.verifyEmailContent;
@ -80,7 +80,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
public void testNotifyUserStepSendsEmailWithDefaultDisableMessage() {
// Create workflow: disable at 10 days, notify 3 days before (at day 7)
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(7))
@ -108,7 +108,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
public void testNotifyUserStepSendsEmailWithDefaultDeleteMessage() {
// Create workflow: delete at 30 days, notify 15 days before (at day 15)
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(15))
@ -135,7 +135,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
@Test
public void testNotifyUserStepSkipsUsersWithoutEmailButLogsWarning() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@ -169,7 +169,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
public void testCompleteUserLifecycleWithMultipleNotifications() {
// Create workflow: just disable at 30 days with one notification before
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(15))
@ -218,7 +218,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
public void testNotifyUserStepWithCustomMessageOverride() throws IOException {
// Create workflow: disable at 7 days, notify 2 days before (at day 5) with custom message
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.withConfig("message", "<p>Dear ${user.firstName} ${user.lastName}, </p>\n" +
@ -265,7 +265,7 @@ public class NotificationStepTest extends AbstractWorkflowTest {
public void testNotifyUserStepWithSendToConfiguration() throws Exception {
// Create workflow: notify immediately with send_to
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.withConfig("to", "admin@example.com")

View file

@ -5,6 +5,7 @@ import java.time.Duration;
import org.keycloak.models.UserModel;
import org.keycloak.models.workflow.RemoveRequiredActionStepProvider;
import org.keycloak.models.workflow.RemoveRequiredActionStepProviderFactory;
import org.keycloak.models.workflow.events.UserCreatedWorkflowEventFactory;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@ -16,8 +17,6 @@ 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;
@ -30,7 +29,7 @@ public class RemoveRequiredActionTest extends AbstractWorkflowTest {
@Test
public void testStepRun() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("remove-action-workflow")
.onEvent(USER_CREATED.name())
.onEvent(UserCreatedWorkflowEventFactory.ID)
.withSteps(
WorkflowStepRepresentation.create()
.of(RemoveRequiredActionStepProviderFactory.ID)

Some files were not shown because too many files have changed in this diff Show more