| | | 1 | | using System.Security.Claims; |
| | | 2 | | using Elsa.Expressions.Contracts; |
| | | 3 | | using Elsa.Extensions; |
| | | 4 | | using Elsa.Workflows.Activities; |
| | | 5 | | using Elsa.Workflows.Management.Models; |
| | | 6 | | |
| | | 7 | | namespace Elsa.Workflows.Api.Security; |
| | | 8 | | |
| | 16 | 9 | | internal class WorkflowDefinitionScriptAuthorizationService( |
| | 16 | 10 | | IActivityVisitor activityVisitor, |
| | 16 | 11 | | IExpressionDescriptorRegistry expressionDescriptorRegistry) |
| | | 12 | | { |
| | 1 | 13 | | private static readonly ScriptPolicy[] ScriptPolicies = |
| | 1 | 14 | | [ |
| | 1 | 15 | | new( |
| | 1 | 16 | | "CSharp", |
| | 1 | 17 | | WorkflowScriptActivityTypeNames.RunCSharp, |
| | 1 | 18 | | PermissionNames.ExecuteCSharpExpressions, |
| | 1 | 19 | | "C# workflow expression execution is disabled by the host. Set CSharpOptions.AllowHostCodeExecution to true |
| | 1 | 20 | | new( |
| | 1 | 21 | | "Python", |
| | 1 | 22 | | WorkflowScriptActivityTypeNames.RunPython, |
| | 1 | 23 | | PermissionNames.ExecutePythonExpressions, |
| | 1 | 24 | | "Python.NET workflow expression execution is disabled by the host. Set PythonOptions.AllowHostCodeExecution |
| | 1 | 25 | | ]; |
| | | 26 | | |
| | | 27 | | public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(WorkflowDefinitionModel model, ClaimsP |
| | | 28 | | { |
| | 3 | 29 | | if (model.Root == null) |
| | 3 | 30 | | return WorkflowDefinitionScriptAuthorizationResult.Allowed(); |
| | | 31 | | |
| | 0 | 32 | | return await AuthorizeAsync(model.Root, user, cancellationToken); |
| | 3 | 33 | | } |
| | | 34 | | |
| | | 35 | | public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(IActivity root, ClaimsPrincipal user, |
| | | 36 | | { |
| | 11 | 37 | | var scriptUsages = await GetUsedScriptPoliciesAsync(root, cancellationToken); |
| | | 38 | | |
| | 11 | 39 | | var failure = scriptUsages |
| | 0 | 40 | | .Select(policy => AuthorizeScriptUsage(policy, user)) |
| | 11 | 41 | | .FirstOrDefault(result => result is { Succeeded: false }); |
| | | 42 | | |
| | 11 | 43 | | if (failure.FailureReason.HasValue) |
| | 0 | 44 | | return failure; |
| | | 45 | | |
| | 11 | 46 | | return WorkflowDefinitionScriptAuthorizationResult.Allowed(); |
| | 11 | 47 | | } |
| | | 48 | | |
| | | 49 | | public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(Workflow workflow, ClaimsPrincipal use |
| | | 50 | | { |
| | 11 | 51 | | return await AuthorizeAsync((IActivity)workflow, user, cancellationToken); |
| | 11 | 52 | | } |
| | | 53 | | |
| | | 54 | | private WorkflowDefinitionScriptAuthorizationResult AuthorizeScriptUsage(ScriptPolicy policy, ClaimsPrincipal user) |
| | | 55 | | { |
| | | 56 | | // Language-specific options live in optional modules. Workflows.Api observes the descriptor state projected by |
| | 0 | 57 | | if (expressionDescriptorRegistry.Find(policy.ExpressionType)?.IsBrowsable != true) |
| | 0 | 58 | | return WorkflowDefinitionScriptAuthorizationResult.HostDisabled(policy.HostDisabledMessage); |
| | | 59 | | |
| | 0 | 60 | | return HasPermission(user, policy.Permission) |
| | 0 | 61 | | ? WorkflowDefinitionScriptAuthorizationResult.Allowed() |
| | 0 | 62 | | : WorkflowDefinitionScriptAuthorizationResult.MissingPermission(); |
| | | 63 | | } |
| | | 64 | | |
| | | 65 | | private async Task<IEnumerable<ScriptPolicy>> GetUsedScriptPoliciesAsync(IActivity root, CancellationToken cancellat |
| | | 66 | | { |
| | 11 | 67 | | var graph = await activityVisitor.VisitAsync(root, cancellationToken); |
| | 11 | 68 | | var nodes = new[] { graph }.Concat(graph.Descendants()).ToList(); |
| | 11 | 69 | | var policies = ScriptPolicies |
| | 114 | 70 | | .Where(policy => nodes.Any(x => IsRunActivity(x.Activity, policy) || HasExpression(x.Activity, policy))) |
| | 11 | 71 | | .ToList(); |
| | | 72 | | |
| | 11 | 73 | | return policies; |
| | 11 | 74 | | } |
| | | 75 | | |
| | | 76 | | private static bool IsRunActivity(IActivity activity, ScriptPolicy policy) => |
| | 92 | 77 | | string.Equals(activity.Type, policy.RunActivityType, StringComparison.Ordinal); |
| | | 78 | | |
| | | 79 | | private static bool HasExpression(IActivity activity, ScriptPolicy policy) => |
| | 138 | 80 | | activity.GetInputs().Any(x => string.Equals(x.Expression?.Type, policy.ExpressionType, StringComparison.Ordinal) |
| | | 81 | | |
| | | 82 | | private static bool HasPermission(ClaimsPrincipal user, string permission) |
| | | 83 | | { |
| | 0 | 84 | | return user.Claims.Any(x => |
| | 0 | 85 | | x.Type == PermissionNames.ClaimType && |
| | 0 | 86 | | (string.Equals(x.Value, PermissionNames.All, StringComparison.Ordinal) || |
| | 0 | 87 | | string.Equals(x.Value, permission, StringComparison.Ordinal))); |
| | | 88 | | } |
| | | 89 | | |
| | 140 | 90 | | private sealed record ScriptPolicy(string ExpressionType, string RunActivityType, string Permission, string HostDisa |
| | | 91 | | } |
| | | 92 | | |
| | | 93 | | internal readonly record struct WorkflowDefinitionScriptAuthorizationResult(bool Succeeded, WorkflowDefinitionScriptAuth |
| | | 94 | | { |
| | | 95 | | public static WorkflowDefinitionScriptAuthorizationResult Allowed() => new(true, null, null); |
| | | 96 | | |
| | | 97 | | public static WorkflowDefinitionScriptAuthorizationResult HostDisabled(string message) => new(false, WorkflowDefinit |
| | | 98 | | |
| | | 99 | | public static WorkflowDefinitionScriptAuthorizationResult MissingPermission() => new(false, WorkflowDefinitionScript |
| | | 100 | | } |
| | | 101 | | |
| | | 102 | | internal enum WorkflowDefinitionScriptAuthorizationFailureReason |
| | | 103 | | { |
| | | 104 | | HostDisabled, |
| | | 105 | | MissingPermission |
| | | 106 | | } |