Merge pull request #7729 from elsa-workflows/codex/runtime-status-read-permission
Add read-only workflow runtime status permission
This commit is contained in:
commit
116381e543
|
|
@ -123,10 +123,10 @@ See [Program.cs](../../src/apps/Elsa.Server.Web/Program.cs).
|
|||
|
||||
With default route prefix `elsa/api`, runtime admin endpoints include:
|
||||
|
||||
- `GET /elsa/api/admin/workflow-runtime/status`
|
||||
- `POST /elsa/api/admin/workflow-runtime/pause`
|
||||
- `POST /elsa/api/admin/workflow-runtime/resume`
|
||||
- `POST /elsa/api/admin/workflow-runtime/force-drain`
|
||||
- `GET /elsa/api/admin/workflow-runtime/status`: requires `read:workflow-runtime`; `ManageWorkflowRuntime` is also accepted for backward compatibility.
|
||||
- `POST /elsa/api/admin/workflow-runtime/pause`: requires `ManageWorkflowRuntime`.
|
||||
- `POST /elsa/api/admin/workflow-runtime/resume`: requires `ManageWorkflowRuntime`.
|
||||
- `POST /elsa/api/admin/workflow-runtime/force-drain`: requires `ManageWorkflowRuntime`.
|
||||
|
||||
Structured log diagnostics endpoints include:
|
||||
|
||||
|
|
|
|||
|
|
@ -159,10 +159,10 @@ Ingress source adapters are currently registered by modules such as HTTP and Sch
|
|||
|
||||
The workflow API includes runtime admin endpoints:
|
||||
|
||||
- `GET /elsa/api/admin/workflow-runtime/status`
|
||||
- `POST /elsa/api/admin/workflow-runtime/pause`
|
||||
- `POST /elsa/api/admin/workflow-runtime/resume`
|
||||
- `POST /elsa/api/admin/workflow-runtime/force-drain`
|
||||
- `GET /elsa/api/admin/workflow-runtime/status`: requires `read:workflow-runtime`; `ManageWorkflowRuntime` is also accepted for backward compatibility.
|
||||
- `POST /elsa/api/admin/workflow-runtime/pause`: requires `ManageWorkflowRuntime`.
|
||||
- `POST /elsa/api/admin/workflow-runtime/resume`: requires `ManageWorkflowRuntime`.
|
||||
- `POST /elsa/api/admin/workflow-runtime/force-drain`: requires `ManageWorkflowRuntime`.
|
||||
|
||||
Endpoint code lives under [Elsa.Workflows.Api/Endpoints/RuntimeAdmin](../../src/modules/Elsa.Workflows.Api/Endpoints/RuntimeAdmin). The service behind these endpoints is [WorkflowRuntimeAdminService](../../src/modules/Elsa.Workflows.Runtime/Services/WorkflowRuntimeAdminService.cs).
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@ public static class PermissionNames
|
|||
public const string ExecutePythonExpressions = "exec:python-expressions";
|
||||
|
||||
/// <summary>
|
||||
/// Permission required to pause, resume, force-drain, or query the workflow runtime's graceful-shutdown status.
|
||||
/// Permission required to pause, resume, or force-drain the workflow runtime.
|
||||
/// </summary>
|
||||
public const string ManageWorkflowRuntime = "ManageWorkflowRuntime";
|
||||
|
||||
/// <summary>
|
||||
/// Permission required to query workflow runtime status.
|
||||
/// </summary>
|
||||
public const string ReadWorkflowRuntime = "read:workflow-runtime";
|
||||
|
||||
/// <summary>
|
||||
/// Permission required to list or inspect bookmark queue dead-letter items.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ internal sealed class StatusEndpoint(IWorkflowRuntimeAdminService admin) : ElsaE
|
|||
public override void Configure()
|
||||
{
|
||||
Get("/admin/workflow-runtime/status");
|
||||
ConfigurePermissions(PermissionNames.ManageWorkflowRuntime);
|
||||
ConfigurePermissions(PermissionNames.ReadWorkflowRuntime, PermissionNames.ManageWorkflowRuntime);
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
using System.Reflection;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using FastEndpoints;
|
||||
using NSubstitute;
|
||||
using WorkflowsApiFeature = Elsa.Workflows.Api.Features.WorkflowsApiFeature;
|
||||
|
||||
namespace Elsa.Workflows.Api.UnitTests.Endpoints.RuntimeAdmin;
|
||||
|
||||
public class RuntimeAdminAuthorizationTests
|
||||
{
|
||||
[Fact]
|
||||
public void StatusEndpoint_AllowsReadWorkflowRuntimePermission()
|
||||
{
|
||||
var permissions = GetConfiguredPermissions("Elsa.Workflows.Api.Endpoints.RuntimeAdmin.Status.StatusEndpoint");
|
||||
|
||||
Assert.Contains(PermissionNames.All, permissions);
|
||||
Assert.Contains(PermissionNames.ReadWorkflowRuntime, permissions);
|
||||
Assert.Contains(PermissionNames.ManageWorkflowRuntime, permissions);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Elsa.Workflows.Api.Endpoints.RuntimeAdmin.Pause.PauseEndpoint")]
|
||||
[InlineData("Elsa.Workflows.Api.Endpoints.RuntimeAdmin.Resume.ResumeEndpoint")]
|
||||
[InlineData("Elsa.Workflows.Api.Endpoints.RuntimeAdmin.ForceDrain.ForceDrainEndpoint")]
|
||||
public void MutatingEndpoints_RequireManageWorkflowRuntimePermission(string endpointTypeName)
|
||||
{
|
||||
var permissions = GetConfiguredPermissions(endpointTypeName);
|
||||
|
||||
Assert.Contains(PermissionNames.ManageWorkflowRuntime, permissions);
|
||||
Assert.DoesNotContain(PermissionNames.ReadWorkflowRuntime, permissions);
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<string> GetConfiguredPermissions(string endpointTypeName)
|
||||
{
|
||||
var endpointType = typeof(WorkflowsApiFeature).Assembly.GetType(endpointTypeName, throwOnError: true)!;
|
||||
var endpoint = Activator.CreateInstance(
|
||||
endpointType,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
null,
|
||||
[Substitute.For<IWorkflowRuntimeAdminService>()],
|
||||
null)!;
|
||||
var (requestDtoType, responseDtoType) = GetEndpointDtoTypes(endpointType);
|
||||
var definition = new EndpointDefinition(endpointType, requestDtoType, responseDtoType);
|
||||
|
||||
endpointType
|
||||
.GetProperty("Definition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
|
||||
.SetValue(endpoint, definition);
|
||||
|
||||
endpointType.GetMethod("Configure")!.Invoke(endpoint, null);
|
||||
|
||||
var permissions = definition
|
||||
.GetType()
|
||||
.GetProperty("AllowedPermissions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
|
||||
.GetValue(definition);
|
||||
|
||||
return Assert.IsAssignableFrom<IEnumerable<string>>(permissions).ToArray();
|
||||
}
|
||||
|
||||
private static (Type RequestDtoType, Type ResponseDtoType) GetEndpointDtoTypes(Type endpointType)
|
||||
{
|
||||
var type = endpointType;
|
||||
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
|
||||
if (!type.IsGenericType)
|
||||
continue;
|
||||
|
||||
var genericTypeDefinition = type.GetGenericTypeDefinition();
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
|
||||
if (genericTypeDefinition == typeof(Abstractions.ElsaEndpoint<,>))
|
||||
return (genericArguments[0], genericArguments[1]);
|
||||
|
||||
if (genericTypeDefinition == typeof(Abstractions.ElsaEndpointWithoutRequest<>))
|
||||
return (typeof(EmptyRequest), genericArguments[0]);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unsupported endpoint type '{endpointType.FullName}'.");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue