Add optional parameter in WorkflowResource.toRepresentation to allow retrieval of the rep without the ids

Closes #44183

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen
2025-11-12 18:51:53 -03:00
committed by Pedro Igor
parent 70e1dba2c3
commit 3319e8d9b5
18 changed files with 127 additions and 191 deletions

View File

@@ -82,24 +82,6 @@ public abstract class AbstractWorkflowComponentRepresentation {
}
}
protected <T> T getConfigValuesOrSingle(String key) {
if (config == null) {
return null;
}
List<String> values = config.get(key);
if (values == null || values.isEmpty()) {
return null;
}
if (values.size() == 1) {
return (T) values.get(0);
}
return (T) values;
}
protected void setConfigValue(String key, Object... values) {
if (values == null || values.length == 0) {
return;

View File

@@ -1,11 +1,7 @@
package org.keycloak.representations.workflows;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.keycloak.common.util.MultivaluedHashMap;
@@ -147,16 +143,10 @@ public final class WorkflowRepresentation extends AbstractWorkflowComponentRepre
public static class Builder {
private final Map<WorkflowRepresentation, List<WorkflowStepRepresentation>> steps = new HashMap<>();
private List<Builder> builders = new ArrayList<>();
private WorkflowRepresentation representation;
private Builder() {
}
private Builder(WorkflowRepresentation representation, List<Builder> builders) {
this.representation = representation;
this.builders = builders;
this.representation = new WorkflowRepresentation();
}
public Builder onEvent(String operation) {
@@ -188,7 +178,7 @@ public final class WorkflowRepresentation extends AbstractWorkflowComponentRepre
}
public Builder withSteps(WorkflowStepRepresentation... steps) {
this.steps.computeIfAbsent(representation, (k) -> new ArrayList<>()).addAll(Arrays.asList(steps));
representation.setSteps(Arrays.asList(steps));
return this;
}
@@ -203,30 +193,12 @@ public final class WorkflowRepresentation extends AbstractWorkflowComponentRepre
}
public Builder withName(String name) {
WorkflowRepresentation representation = new WorkflowRepresentation();
representation.setName(name);
Builder builder = new Builder(representation, builders);
builders.add(builder);
return builder;
return this;
}
public WorkflowSetRepresentation build() {
List<WorkflowRepresentation> workflows = new ArrayList<>();
for (Builder builder : builders) {
if (builder.steps.isEmpty()) {
continue;
}
for (Entry<WorkflowRepresentation, List<WorkflowStepRepresentation>> entry : builder.steps.entrySet()) {
WorkflowRepresentation workflow = entry.getKey();
workflow.setSteps(entry.getValue());
workflows.add(workflow);
}
}
return new WorkflowSetRepresentation(workflows);
public WorkflowRepresentation build() {
return representation;
}
}
}

View File

@@ -1,27 +0,0 @@
package org.keycloak.representations.workflows;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
public final class WorkflowSetRepresentation {
@JsonUnwrapped
private List<WorkflowRepresentation> workflows;
public WorkflowSetRepresentation() {
}
public WorkflowSetRepresentation(List<WorkflowRepresentation> workflows) {
this.workflows = workflows;
}
public void setWorkflows(List<WorkflowRepresentation> workflows) {
this.workflows = workflows;
}
public List<WorkflowRepresentation> getWorkflows() {
return workflows;
}
}

View File

@@ -6,6 +6,7 @@ import java.util.Objects;
import org.keycloak.common.util.MultivaluedHashMap;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -16,7 +17,7 @@ import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_PR
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_USES;
import static org.keycloak.representations.workflows.WorkflowConstants.CONFIG_WITH;
@JsonPropertyOrder({"id", CONFIG_USES, CONFIG_AFTER, CONFIG_PRIORITY, CONFIG_WITH})
@JsonPropertyOrder({CONFIG_USES, CONFIG_AFTER, CONFIG_PRIORITY, CONFIG_WITH})
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class WorkflowStepRepresentation extends AbstractWorkflowComponentRepresentation {
@@ -39,6 +40,11 @@ public final class WorkflowStepRepresentation extends AbstractWorkflowComponentR
this.uses = uses;
}
@JsonIgnore
public String getId() {
return super.getId();
}
public String getUses() {
return this.uses;
}
@@ -95,11 +101,6 @@ public final class WorkflowStepRepresentation extends AbstractWorkflowComponentR
return this;
}
public Builder id(String id) {
step.setId(id);
return this;
}
public Builder withConfig(String key, String value) {
step.setConfig(key, value);
return this;

View File

@@ -32,18 +32,15 @@ public class WorkflowDefinitionTest {
expected.setSteps(Arrays.asList(
WorkflowStepRepresentation.create()
.of("step-1")
.id("1")
.withConfig("key1", "v1")
.after(Duration.ofSeconds(10))
.build(),
WorkflowStepRepresentation.create()
.of("step-2")
.id("2")
.withConfig("key1", "v1", "v2")
.build(),
WorkflowStepRepresentation.create()
.of("step-1")
.id("3")
.withConfig("key1", "v1")
.build()));

View File

@@ -13,7 +13,6 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import com.fasterxml.jackson.jakarta.rs.yaml.YAMLMediaTypes;
@@ -29,11 +28,6 @@ public interface WorkflowsResource {
@Consumes({MediaType.APPLICATION_JSON, YAMLMediaTypes.APPLICATION_JACKSON_YAML})
Response create(WorkflowRepresentation representation);
@Path("set")
@POST
@Consumes({MediaType.APPLICATION_JSON, YAMLMediaTypes.APPLICATION_JACKSON_YAML})
Response create(WorkflowSetRepresentation representation);
@GET
@Produces(MediaType.APPLICATION_JSON)
List<WorkflowRepresentation> list();

View File

@@ -60,6 +60,7 @@ export default function WorkflowDetailForm() {
}
return adminClient.workflows.findOne({
id: id!,
includeId: false,
});
},
(workflow) => {

View File

@@ -19,12 +19,13 @@ export class Workflows extends Resource<{ realm?: string }> {
});
public findOne = this.makeRequest<
{ id: string },
{ id: string; includeId: boolean },
WorkflowRepresentation | undefined
>({
method: "GET",
path: "/{id}",
urlParamKeys: ["id"],
queryParamKeys: ["includeId"],
catchNotFound: true,
});

View File

@@ -9,6 +9,7 @@ import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@@ -20,6 +21,7 @@ import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.services.ErrorResponse;
import com.fasterxml.jackson.jakarta.rs.yaml.YAMLMediaTypes;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
public class WorkflowResource {
@@ -54,8 +56,14 @@ public class WorkflowResource {
@GET
@Produces({MediaType.APPLICATION_JSON, YAMLMediaTypes.APPLICATION_JACKSON_YAML})
public WorkflowRepresentation toRepresentation() {
return provider.toRepresentation(workflow);
public WorkflowRepresentation toRepresentation(
@Parameter(description = "Indicates whether the workflow id should be included in the representation or not - defaults to true") @QueryParam("includeId") Boolean includeId
) {
WorkflowRepresentation rep = provider.toRepresentation(workflow);
if (Boolean.FALSE.equals(includeId)) {
rep.setId(null);
}
return rep;
}
/**

View File

@@ -22,7 +22,6 @@ import org.keycloak.models.ModelException;
import org.keycloak.models.workflow.Workflow;
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;
@@ -57,18 +56,6 @@ public class WorkflowsResource {
}
}
@Path("set")
@POST
@Consumes({MediaType.APPLICATION_JSON, YAMLMediaTypes.APPLICATION_JACKSON_YAML})
public Response createAll(WorkflowSetRepresentation workflows) {
auth.realm().requireManageRealm();
for (WorkflowRepresentation workflow : Optional.ofNullable(workflows.getWorkflows()).orElse(List.of())) {
create(workflow).close();
}
return Response.created(session.getContext().getUri().getRequestUri()).build();
}
@Path("{id}")
public WorkflowResource get(@PathParam("id") String id) {
auth.realm().requireManageRealm();

View File

@@ -54,21 +54,23 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
@Test
public void testBindAdHocScheduledWithImmediateWorkflow() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.withSteps(WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
.withConfig("message", "message")
.build())
.build()).close();
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = managedRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflow = workflows.get(0);
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
String id = ApiUtil.getCreatedId(response);
try {
managedRealm.admin().workflows().workflow(workflow.getId()).activate(ResourceType.USERS.name(), id, "5D");
managedRealm.admin().workflows().workflow(workflowId).activate(ResourceType.USERS.name(), id, "5D");
} catch (Exception e) {
assertThat(e, instanceOf(BadRequestException.class));
}
@@ -77,40 +79,44 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
@Test
public void testRunAdHocScheduledWorkflow() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.withSteps(WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
.after(Duration.ofDays(5))
.withConfig("message", "message")
.build())
.build()).close();
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = managedRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflow = workflows.get(0);
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
String id = ApiUtil.getCreatedId(response);
managedRealm.admin().workflows().workflow(workflow.getId()).activate(ResourceType.USERS.name(), id);
managedRealm.admin().workflows().workflow(workflowId).activate(ResourceType.USERS.name(), id);
}
}
@Test
public void testRunAdHocImmediateWorkflow() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.withSteps(WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
.withConfig("message", "message")
.build())
.build()).close();
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = managedRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflow = workflows.get(0);
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
String id = ApiUtil.getCreatedId(response);
managedRealm.admin().workflows().workflow(workflow.getId()).activate(ResourceType.USERS.name(), id);
managedRealm.admin().workflows().workflow(workflowId).activate(ResourceType.USERS.name(), id);
}
runScheduledSteps(Duration.ZERO);
@@ -124,21 +130,23 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
@Test
public void testRunAdHocTimedWorkflow() {
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.withSteps(WorkflowStepRepresentation.create()
.of(SetUserAttributeStepProviderFactory.ID)
.withConfig("message", "message")
.build())
.build()).close();
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = managedRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflow = workflows.get(0);
String id;
String resourceId;
try (Response response = managedRealm.admin().users().create(getUserRepresentation("alice", "Alice", "Wonderland", "alice@wornderland.org"))) {
id = ApiUtil.getCreatedId(response);
managedRealm.admin().workflows().workflow(workflow.getId()).activate(ResourceType.USERS.name(), id, "5D");
resourceId = ApiUtil.getCreatedId(response);
managedRealm.admin().workflows().workflow(workflowId).activate(ResourceType.USERS.name(), resourceId, "5D");
}
runScheduledSteps(Duration.ZERO);
@@ -162,7 +170,7 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
}));
// using seconds as the notBefore parameter just to check if this format is also working properly
managedRealm.admin().workflows().workflow(workflow.getId()).activate(ResourceType.USERS.name(), id, String.valueOf(Duration.ofDays(10).toSeconds()));
managedRealm.admin().workflows().workflow(workflowId).activate(ResourceType.USERS.name(), resourceId, String.valueOf(Duration.ofDays(10).toSeconds()));
runScheduledSteps(Duration.ZERO);
@@ -204,7 +212,8 @@ public class AdhocWorkflowTest extends AbstractWorkflowTest {
.after(Duration.ofDays(5))
.build()
)
.withName("Two")
.build()).close();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("Two")
.onEvent(USER_ADDED.name())
.withSteps(
WorkflowStepRepresentation.create()

View File

@@ -21,6 +21,8 @@ import java.time.Duration;
import java.util.List;
import java.util.Map;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -58,6 +60,7 @@ import org.keycloak.testframework.ui.annotations.InjectPage;
import org.keycloak.testframework.ui.annotations.InjectWebDriver;
import org.keycloak.testframework.ui.page.ConsentPage;
import org.keycloak.testframework.ui.page.LoginPage;
import org.keycloak.testframework.util.ApiUtil;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Assertions;
@@ -127,19 +130,22 @@ public class BrokeredUserSessionRefreshTimeWorkflowTest extends AbstractWorkflow
@Test
public void testInvalidateWorkflowOnIdentityProviderRemoval() {
consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = consumerRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_ADDED.toString(), USER_LOGGED_IN.toString())
.onCondition(IDP_CONDITION)
.withSteps(
WorkflowStepRepresentation.create().of(DeleteUserStepProviderFactory.ID)
.after(Duration.ofDays(1))
.build()
).build()).close();
.after(Duration.ofDays(1))
.build())
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = consumerRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflowRep = consumerRealm.admin().workflows().workflow(workflows.get(0).getId()).toRepresentation();
WorkflowRepresentation workflowRep = consumerRealm.admin().workflows().workflow(workflowId).toRepresentation();
assertThat(workflowRep.getConfig().getFirst("enabled"), nullValue());
// remove IDP
@@ -152,7 +158,7 @@ public class BrokeredUserSessionRefreshTimeWorkflowTest extends AbstractWorkflow
.timeout(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
.untilAsserted(() -> {
var rep = consumerRealm.admin().workflows().workflow(workflows.get(0).getId()).toRepresentation();
var rep = consumerRealm.admin().workflows().workflow(workflowId).toRepresentation();
assertThat(rep.getEnabled(), allOf(notNullValue(), is(false)));
WorkflowStateRepresentation status = rep.getState();
assertThat(status, notNullValue());

View File

@@ -15,7 +15,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.GroupConfigBuilder;
@@ -175,7 +174,7 @@ public class ExpressionConditionWorkflowTest extends AbstractWorkflowTest {
}
private String createWorkflow(String expression) {
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent("user-logged-in(test-app)")
.onCondition(expression)
.withSteps(
@@ -188,7 +187,7 @@ public class ExpressionConditionWorkflowTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows.getWorkflows().get(0))) {
try (Response response = workflows.create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
return ApiUtil.getCreatedId(response);
}

View File

@@ -16,7 +16,6 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.representations.workflows.WorkflowStateRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
@@ -57,7 +56,7 @@ public class GroupMembershipJoinWorkflowTest extends AbstractWorkflowTest {
groupId = ApiUtil.getCreatedId(response);
}
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_GROUP_MEMBERSHIP_ADDED.name())
.onCondition(GROUP_CONDITION)
.withSteps(
@@ -70,7 +69,7 @@ public class GroupMembershipJoinWorkflowTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows)) {
try (Response response = workflows.create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
}
@@ -95,25 +94,27 @@ public class GroupMembershipJoinWorkflowTest extends AbstractWorkflowTest {
@Test
public void testRemoveAssociatedGroup() {
String groupId;
try (Response response = managedRealm.admin().groups().add(GroupConfigBuilder.create()
.name("generic-group").build())) {
groupId = ApiUtil.getCreatedId(response);
}
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_ADDED.toString(), USER_LOGGED_IN.toString())
.onCondition(GROUP_CONDITION)
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(1))
.build()
).build()).close();
.build())
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> workflows = managedRealm.admin().workflows().list();
assertThat(workflows, hasSize(1));
WorkflowRepresentation workflowRep = managedRealm.admin().workflows().workflow(workflows.get(0).getId()).toRepresentation();
WorkflowRepresentation workflowRep = managedRealm.admin().workflows().workflow(workflowId).toRepresentation();
assertThat(workflowRep.getConfig().getFirst("enabled"), nullValue());
// remove group
@@ -126,7 +127,7 @@ public class GroupMembershipJoinWorkflowTest extends AbstractWorkflowTest {
.timeout(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
.untilAsserted(() -> {
var rep = managedRealm.admin().workflows().workflow(workflows.get(0).getId()).toRepresentation();
var rep = managedRealm.admin().workflows().workflow(workflowId).toRepresentation();
assertThat(rep.getEnabled(), allOf(notNullValue(), is(false)));
WorkflowStateRepresentation status = rep.getState();
assertThat(status, notNullValue());

View File

@@ -19,7 +19,6 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.RoleConfigBuilder;
@@ -114,7 +113,7 @@ public class RoleWorkflowConditionTest extends AbstractWorkflowTest {
.map(role -> RoleWorkflowConditionFactory.ID + "(" + role + ")")
.reduce((a, b) -> a + " AND " + b).orElse(null);
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ROLE_ADDED.name())
.onCondition(roleCondition)
.withSteps(
@@ -130,7 +129,7 @@ public class RoleWorkflowConditionTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows)) {
try (Response response = workflows.create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
}
}

View File

@@ -18,7 +18,6 @@ import org.keycloak.models.workflow.conditions.UserAttributeWorkflowConditionFac
import org.keycloak.representations.userprofile.config.UPConfig;
import org.keycloak.representations.userprofile.config.UPConfig.UnmanagedAttributePolicy;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.realm.UserConfigBuilder;
@@ -117,7 +116,7 @@ public class UserAttributeWorkflowConditionTest extends AbstractWorkflowTest {
.reduce((a, b) -> a + " AND " + b)
.orElse(null);
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ADDED.name())
.onCondition(attributeCondition)
.withSteps(
@@ -133,7 +132,7 @@ public class UserAttributeWorkflowConditionTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows)) {
try (Response response = workflows.create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Status.CREATED.getStatusCode()));
}
}

View File

@@ -143,9 +143,9 @@ public class UserSessionRefreshTimeWorkflowTest extends AbstractWorkflowTest {
.after(Duration.ofDays(5))
.withConfig("custom_subject_key", "notifier1_subject")
.withConfig("custom_message", "notifier1_message")
.build()
)
.withName("myworkflow_2")
.build())
.build()).close();
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("myworkflow_2")
.onEvent(USER_ADDED.toString(), USER_LOGGED_IN.toString())
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)

View File

@@ -53,7 +53,6 @@ import org.keycloak.models.workflow.conditions.IdentityProviderWorkflowCondition
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.representations.workflows.WorkflowSetRepresentation;
import org.keycloak.representations.workflows.WorkflowStepRepresentation;
import org.keycloak.testframework.annotations.InjectAdminClient;
import org.keycloak.testframework.annotations.InjectKeycloakUrls;
@@ -63,6 +62,7 @@ import org.keycloak.testframework.mail.annotations.InjectMailServer;
import org.keycloak.testframework.realm.UserConfigBuilder;
import org.keycloak.testframework.remote.providers.runonserver.RunOnServer;
import org.keycloak.testframework.server.KeycloakUrls;
import org.keycloak.testframework.util.ApiUtil;
import org.keycloak.tests.utils.MailUtils;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -101,7 +101,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testCreate() {
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_ADDED.name())
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
@@ -114,7 +114,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows)) {
try (Response response = workflows.create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
}
@@ -129,7 +129,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testCreateWithNoConditions() {
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("myworkflow")
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@@ -139,16 +139,16 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
.build()
).build();
expectedWorkflows.getWorkflows().get(0).setConditions(null);
expectedWorkflow.setConditions(null);
try (Response response = managedRealm.admin().workflows().create(expectedWorkflows)) {
try (Response response = managedRealm.admin().workflows().create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
}
}
@Test
public void testCreateWithNoWorkflowSetDefaultWorkflow() {
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("default-workflow")
WorkflowRepresentation expectedWorkflow = WorkflowRepresentation.withName("default-workflow")
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@@ -158,9 +158,9 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
.build()
).build();
expectedWorkflows.getWorkflows().get(0).setConditions(null);
expectedWorkflow.setConditions(null);
try (Response response = managedRealm.admin().workflows().create(expectedWorkflows)) {
try (Response response = managedRealm.admin().workflows().create(expectedWorkflow)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
}
@@ -171,16 +171,20 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
public void testDelete() {
WorkflowsResource workflows = managedRealm.admin().workflows();
workflows.create(WorkflowRepresentation.withName("myworkflow")
String workflowId;
try (Response response = workflows.create(WorkflowRepresentation.withName("myworkflow")
.onEvent(ResourceOperationType.USER_ADDED.toString())
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
.build(),
WorkflowStepRepresentation.create().of(RestartWorkflowStepProviderFactory.ID)
.build()
)
.withName("another-workflow")
.build())
.build())) {
workflowId = ApiUtil.getCreatedId(response);
}
workflows.create(WorkflowRepresentation.withName("another-workflow")
.onEvent(ResourceOperationType.USER_LOGGED_IN.toString())
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
@@ -196,10 +200,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
List<WorkflowRepresentation> actualWorkflows = workflows.list();
assertThat(actualWorkflows, Matchers.hasSize(2));
WorkflowRepresentation workflow = actualWorkflows.stream().filter(p -> "myworkflow".equals(p.getName())).findAny().orElse(null);
assertThat(workflow, notNullValue());
String id = workflow.getId();
workflows.workflow(id).delete().close();
workflows.workflow(workflowId).delete().close();
actualWorkflows = workflows.list();
assertThat(actualWorkflows, Matchers.hasSize(1));
@@ -209,14 +210,14 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
List<Workflow> registeredWorkflows = provider.getWorkflows().toList();
assertEquals(1, registeredWorkflows.size());
WorkflowStateProvider stateProvider = session.getKeycloakSessionFactory().getProviderFactory(WorkflowStateProvider.class).create(session);
List<ScheduledStep> steps = stateProvider.getScheduledStepsByWorkflow(id);
List<ScheduledStep> steps = stateProvider.getScheduledStepsByWorkflow(workflowId);
assertTrue(steps.isEmpty());
});
}
@Test
public void testUpdate() {
WorkflowSetRepresentation expectedWorkflows = WorkflowRepresentation.withName("test-workflow")
WorkflowRepresentation workflowRep = WorkflowRepresentation.withName("test-workflow")
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
@@ -228,8 +229,10 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
WorkflowsResource workflows = managedRealm.admin().workflows();
try (Response response = workflows.create(expectedWorkflows)) {
String workflowId;
try (Response response = workflows.create(workflowRep)) {
assertThat(response.getStatus(), is(Response.Status.CREATED.getStatusCode()));
workflowId = ApiUtil.getCreatedId(response);
}
List<WorkflowRepresentation> actualWorkflows = workflows.list();
@@ -238,7 +241,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
assertThat(workflow.getName(), is("test-workflow"));
workflow.setName("changed");
managedRealm.admin().workflows().workflow(workflow.getId()).update(workflow).close();
managedRealm.admin().workflows().workflow(workflowId).update(workflow).close();
actualWorkflows = workflows.list();
workflow = actualWorkflows.get(0);
assertThat(workflow.getName(), is("changed"));
@@ -246,21 +249,21 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
// now let's try to update another property that we can't update
String previousOn = workflow.getOn();
workflow.setOn(ResourceOperationType.USER_LOGGED_IN.toString());
try (Response response = workflows.workflow(workflow.getId()).update(workflow)) {
try (Response response = workflows.workflow(workflowId).update(workflow)) {
assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode()));
}
// restore previous value, but change the conditions
workflow.setOn(previousOn);
workflow.setConditions(IdentityProviderWorkflowConditionFactory.ID + "(someidp)");
try (Response response = workflows.workflow(workflow.getId()).update(workflow)) {
try (Response response = workflows.workflow(workflowId).update(workflow)) {
assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode()));
}
// revert conditions, but change one of the steps
workflow.setConditions(null);
workflow.getSteps().get(0).setAfter("8D"); // 8 days
try (Response response = workflows.workflow(workflow.getId()).update(workflow)) {
workflow.getSteps().get(0).setAfter("8D");
try (Response response = workflows.workflow(workflowId).update(workflow)) {
assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode()));
}
@@ -510,7 +513,8 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testDisableWorkflow() {
// create a test workflow
managedRealm.admin().workflows().create(WorkflowRepresentation.withName("test-workflow")
String workflowId;
try (Response response = managedRealm.admin().workflows().create(WorkflowRepresentation.withName("test-workflow")
.onEvent(ResourceOperationType.USER_ADDED.toString())
.withSteps(
WorkflowStepRepresentation.create().of(NotifyUserStepProviderFactory.ID)
@@ -519,7 +523,9 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
WorkflowStepRepresentation.create().of(DisableUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
.build()
).build()).close();
).build())) {
workflowId = ApiUtil.getCreatedId(response);
}
WorkflowsResource workflows = managedRealm.admin().workflows();
List<WorkflowRepresentation> actualWorkflows = workflows.list();
@@ -547,7 +553,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
// disable the workflow - scheduled steps should be paused and workflow should not activate for new users
workflow.setEnabled(false);
managedRealm.admin().workflows().workflow(workflow.getId()).update(workflow).close();
managedRealm.admin().workflows().workflow(workflowId).update(workflow).close();
// create another user - should NOT bind the user to the workflow as it is disabled
managedRealm.admin().users().create(UserConfigBuilder.create().username("anotheruser").build()).close();
@@ -577,7 +583,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
// re-enable the workflow - scheduled steps should resume and new users should be bound to the workflow
workflow.getConfig().putSingle("enabled", "true");
managedRealm.admin().workflows().workflow(workflow.getId()).update(workflow).close();
managedRealm.admin().workflows().workflow(workflowId).update(workflow).close();
// create a third user - should bind the user to the workflow as it is enabled again
managedRealm.admin().users().create(UserConfigBuilder.create().username("thirduser").email("thirduser@example.com").build()).close();
@@ -671,7 +677,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
@Test
public void testFailCreateWorkflowWithNegativeTime() {
WorkflowSetRepresentation workflows = WorkflowRepresentation.withName("myworkflow")
WorkflowRepresentation workflow = WorkflowRepresentation.withName("myworkflow")
.onEvent(USER_ADDED.name())
.withSteps(
WorkflowStepRepresentation.create().of(SetUserAttributeStepProviderFactory.ID)
@@ -679,7 +685,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
.withConfig("key", "value")
.build())
.build();
try (Response response = managedRealm.admin().workflows().create(workflows)) {
try (Response response = managedRealm.admin().workflows().create(workflow)) {
assertThat(response.getStatus(), is(Response.Status.BAD_REQUEST.getStatusCode()));
assertThat(response.readEntity(ErrorRepresentation.class).getErrorMessage(), equalTo("Step 'after' configuration cannot be negative."));
}
@@ -861,7 +867,7 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
WorkflowStepRepresentation.create().of(DisableUserStepProviderFactory.ID)
.after(Duration.ofDays(5))
.build()
).build().getWorkflows().get(0);
).build();
try (Client httpClient = Keycloak.getClientProvider().newRestEasyClient(null, null, true)) {
httpClient.register(JacksonYAMLProvider.class);
@@ -872,9 +878,11 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
.path("workflows")
.register(new BearerAuthFilter(adminClient.tokenManager()));
String workflowId;
try (Response response = workflowsApi.request().post(Entity.entity(yamlMapper.writeValueAsString(expected),
YAMLMediaTypes.APPLICATION_JACKSON_YAML))) {
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
workflowId = ApiUtil.getCreatedId(response);
}
try (Response response = workflowsApi.request().accept(YAMLMediaTypes.APPLICATION_JACKSON_YAML).get()) {
@@ -886,15 +894,14 @@ public class WorkflowManagementTest extends AbstractWorkflowTest {
expected = workflows.get(0);
}
try (Response response = workflowsApi.path(expected.getId()).request()
try (Response response = workflowsApi.path(workflowId).request()
.accept(YAMLMediaTypes.APPLICATION_JACKSON_YAML).get()) {
assertEquals(Status.OK.getStatusCode(), response.getStatus());
WorkflowRepresentation actual = yamlMapper.readValue(response.readEntity(String.class), WorkflowRepresentation.class);
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
}
try (Response response = workflowsApi.path(expected.getId()).request()
try (Response response = workflowsApi.path(workflowId).request()
.put(Entity.entity(yamlMapper.writeValueAsString(expected), YAMLMediaTypes.APPLICATION_JACKSON_YAML))) {
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}