Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static class Tags {
public static final String SCOPE_MAPPINGS = "Scope Mappings";
public static final String USERS = "Users";
public static final String ORGANIZATIONS = "Organizations";
public static final String WORKFLOWS = "Workflows";
private Tags() { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.KeycloakOpenAPI;

import com.fasterxml.jackson.jakarta.rs.yaml.YAMLMediaTypes;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
public class WorkflowResource {

private final WorkflowProvider provider;
Expand All @@ -34,6 +43,15 @@ public WorkflowResource(WorkflowProvider provider, Workflow workflow) {
}

@DELETE
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Delete workflow",
description = "Delete the workflow and its configuration."
)
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "No Content"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public void delete() {
try {
provider.removeWorkflow(workflow);
Expand All @@ -47,6 +65,15 @@ public void delete() {
*/
@PUT
@Consumes({YAMLMediaTypes.APPLICATION_JACKSON_YAML, MediaType.APPLICATION_JSON})
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Update workflow",
description = "Update the workflow configuration. This method does not update the workflow steps."
)
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "No Content"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public void update(WorkflowRepresentation rep) {
try {
provider.updateWorkflow(workflow, rep);
Expand All @@ -57,8 +84,20 @@ public void update(WorkflowRepresentation rep) {

@GET
@Produces({YAMLMediaTypes.APPLICATION_JACKSON_YAML, MediaType.APPLICATION_JSON})
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Get workflow",
description = "Get the workflow representation. Optionally exclude the workflow id from the response."
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkflowRepresentation.class))),
@APIResponse(responseCode = "400", description = "Bad Request")
})
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
@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)) {
Expand All @@ -73,12 +112,33 @@ public WorkflowRepresentation toRepresentation(
* @param type the resource type
* @param resourceId the resource id
* @param notBefore optional value representing the time to schedule the first workflow step, overriding the first
* step time configuration (after). The value is either an integer representing the seconds from now,
* an integer followed by 'ms' representing milliseconds from now, or an ISO-8601 date string.
* step time configuration (after). The value is either an integer representing the seconds from now,
* an integer followed by 'ms' representing milliseconds from now, or an ISO-8601 date string.
*/
@POST
@Path("activate/{type}/{resourceId}")
public void activate(@PathParam("type") ResourceType type, @PathParam("resourceId") String resourceId, @QueryParam("notBefore") String notBefore) {
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Activate workflow for resource",
description = "Activate the workflow for the given resource type and identifier. Optionally schedule the first step using the notBefore parameter."
)
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "No Content"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public void activate(
@Parameter(description = "Resource type")
@PathParam("type") ResourceType type,
@Parameter(description = "Resource identifier")
@PathParam("resourceId") String resourceId,
@Parameter(
description = "Optional value representing the time to schedule the first workflow step. " +
"The value is either an integer representing the seconds from now, " +
"an integer followed by 'ms' representing milliseconds from now, " +
"or an ISO-8601 date string."
)
@QueryParam("notBefore") String notBefore
) {
Object resource = provider.getResourceTypeSelector(type).resolveResource(resourceId);

if (resource == null) {
Expand All @@ -94,6 +154,12 @@ public void activate(@PathParam("type") ResourceType type, @PathParam("resourceI

@POST
@Path("activate-all")
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(summary = "Activate workflow for all eligible resources", description = "Activate the workflow for all eligible resources; an optional notBefore may schedule the first step for all activations.")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "No Content"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public void activateAll(@QueryParam("notBefore") String notBefore) {

if (notBefore != null) {
Expand All @@ -112,7 +178,21 @@ public void activateAll(@QueryParam("notBefore") String notBefore) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Path("deactivate/{type}/{resourceId}")
public void deactivate(@PathParam("type") ResourceType type, @PathParam("resourceId") String resourceId) {
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Deactivate workflow for resource",
description = "Deactivate the workflow for the given resource type and identifier."
)
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "No Content"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public void deactivate(
@Parameter(description = "Resource type")
@PathParam("type") ResourceType type,
@Parameter(description = "Resource identifier")
@PathParam("resourceId") String resourceId
) {
Object resource = provider.getResourceTypeSelector(type).resolveResource(resourceId);

if (resource == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@
import org.keycloak.models.workflow.WorkflowProvider;
import org.keycloak.representations.workflows.WorkflowRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.fgap.AdminPermissionEvaluator;

import com.fasterxml.jackson.jakarta.rs.yaml.YAMLMediaTypes;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
public class WorkflowsResource {

private final KeycloakSession session;
Expand All @@ -45,6 +54,15 @@ public WorkflowsResource(KeycloakSession session, AdminPermissionEvaluator auth)

@POST
@Consumes({YAMLMediaTypes.APPLICATION_JACKSON_YAML, MediaType.APPLICATION_JSON})
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Create workflow",
description = "Create a new workflow from the provided representation."
)
@APIResponses(value = {
@APIResponse(responseCode = "201", description = "Created"),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public Response create(WorkflowRepresentation rep) {
auth.realm().requireManageRealm();

Expand All @@ -57,7 +75,19 @@ public Response create(WorkflowRepresentation rep) {
}

@Path("{id}")
public WorkflowResource get(@PathParam("id") String id) {
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "Get workflow sub-resource",
description = "Locate the workflow sub-resource for the given identifier."
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Workflow sub-resource located"),
@APIResponse(responseCode = "404", description = "Not Found")
})
public WorkflowResource get(
@Parameter(description = "Workflow identifier")
@PathParam("id") String id
) {
auth.realm().requireManageRealm();

Workflow workflow = provider.getWorkflow(id);
Expand All @@ -71,11 +101,24 @@ public WorkflowResource get(@PathParam("id") String id) {

@GET
@Produces({YAMLMediaTypes.APPLICATION_JACKSON_YAML, MediaType.APPLICATION_JSON})
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "List workflows",
description = "List workflows filtered by name and paginated using first and max parameters."
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkflowRepresentation.class))),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public List<WorkflowRepresentation> list(
@Parameter(description = "A String representing the workflow name - either partial or exact") @QueryParam("search") String search,
@Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not") @QueryParam("exact") Boolean exact,
@Parameter(description = "The position of the first result to be processed (pagination offset)") @QueryParam("first") @DefaultValue("0") Integer firstResult,
@Parameter(description = "The maximum number of results to be returned - defaults to 10") @QueryParam("max") @DefaultValue("10") Integer maxResults
@Parameter(description = "A String representing the workflow name - either partial or exact")
@QueryParam("search") String search,
@Parameter(description = "Boolean which defines whether the param 'search' must match exactly or not")
@QueryParam("exact") Boolean exact,
@Parameter(description = "The position of the first result to be processed (pagination offset)")
@QueryParam("first") @DefaultValue("0") Integer firstResult,
@Parameter(description = "The maximum number of results to be returned - defaults to 10")
@QueryParam("max") @DefaultValue("10") Integer maxResults
) {
auth.realm().requireManageRealm();

Expand All @@ -87,7 +130,17 @@ public List<WorkflowRepresentation> list(
@Path("scheduled/{resource-id}")
@GET
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = KeycloakOpenAPI.Admin.Tags.WORKFLOWS)
@Operation(
summary = "List scheduled workflows for resource",
description = "Return workflows that have scheduled steps for the given resource identifier."
)
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = WorkflowRepresentation.class))),
@APIResponse(responseCode = "400", description = "Bad Request")
})
public List<WorkflowRepresentation> getScheduledSteps(
@Parameter(description = "Identifier of the resource associated with the scheduled workflows")
@PathParam("resource-id") String resourceId
) {
auth.realm().requireManageRealm();
Expand Down
Loading