< Summary

Information
Class: Elsa.Workflows.Api.Security.WorkflowDefinitionScriptAuthorizationResult
Assembly: Elsa.Workflows.Api
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Security/WorkflowDefinitionScriptAuthorizationService.cs
Line coverage
66%
Covered lines: 2
Uncovered lines: 1
Coverable lines: 3
Total lines: 96
Line coverage: 66.6%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Succeeded()100%11100%
Allowed()100%11100%
HostDisabled(...)100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Security/WorkflowDefinitionScriptAuthorizationService.cs

#LineLine coverage
 1using Elsa.Expressions.Contracts;
 2using Elsa.Extensions;
 3using Elsa.Workflows.Activities;
 4using Elsa.Workflows.Management.Models;
 5
 6namespace Elsa.Workflows.Api.Security;
 7
 8internal class WorkflowDefinitionScriptAuthorizationService(
 9    IActivityVisitor activityVisitor,
 10    IExpressionDescriptorRegistry expressionDescriptorRegistry)
 11{
 12    private static readonly ScriptPolicy[] ScriptPolicies =
 13    [
 14        new(
 15            "CSharp",
 16            WorkflowScriptActivityTypeNames.RunCSharp,
 17            "C# workflow expression execution is disabled by the host. Set CSharpOptions.AllowHostCodeExecution to true 
 18        new(
 19            "Python",
 20            WorkflowScriptActivityTypeNames.RunPython,
 21            "Python.NET workflow expression execution is disabled by the host. Set PythonOptions.AllowHostCodeExecution 
 22    ];
 23
 24    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(WorkflowDefinitionModel model, Cancell
 25    {
 26        if (model.Root == null)
 27            return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 28
 29        return await AuthorizeAsync(model.Root, cancellationToken);
 30    }
 31
 32    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(IActivity root, CancellationToken canc
 33    {
 34        var scriptUsages = await GetUsedScriptPoliciesAsync(root, cancellationToken);
 35
 36        var failure = scriptUsages
 37            .Select(AuthorizeScriptUsage)
 38            .FirstOrDefault(result => result is { Succeeded: false });
 39
 40        if (failure.FailureReason.HasValue)
 41            return failure;
 42
 43        return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 44    }
 45
 46    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(Workflow workflow, CancellationToken c
 47    {
 48        return await AuthorizeAsync((IActivity)workflow, cancellationToken);
 49    }
 50
 51    private WorkflowDefinitionScriptAuthorizationResult AuthorizeScriptUsage(ScriptPolicy policy)
 52    {
 53        // Language-specific options live in optional modules. Workflows.Api observes the descriptor state projected by 
 54        if (expressionDescriptorRegistry.Find(policy.ExpressionType)?.IsBrowsable != true)
 55            return WorkflowDefinitionScriptAuthorizationResult.HostDisabled(policy.HostDisabledMessage);
 56
 57        // The host switch is the only control, so there is nothing left to decide once it is on. The former
 58        // per-author permission conflated an incoherent execution-side gate -- a workflow runs under the
 59        // server's authority, not the caller's, so the check never constrained what a script could do --
 60        // with a meaningful authoring-side one. Neither the caller nor a failure reason for a denied caller
 61        // is modelled here any more, because nothing produces one. Per-author script trust was considered and
 62        // declined in #7975, so this is the settled shape rather than a stop on the way to one.
 63        return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 64    }
 65
 66    private async Task<IEnumerable<ScriptPolicy>> GetUsedScriptPoliciesAsync(IActivity root, CancellationToken cancellat
 67    {
 68        var graph = await activityVisitor.VisitAsync(root, cancellationToken);
 69        var nodes = new[] { graph }.Concat(graph.Descendants()).ToList();
 70        var policies = ScriptPolicies
 71            .Where(policy => nodes.Any(x => IsRunActivity(x.Activity, policy) || HasExpression(x.Activity, policy)))
 72            .ToList();
 73
 74        return policies;
 75    }
 76
 77    private static bool IsRunActivity(IActivity activity, ScriptPolicy policy) =>
 78        string.Equals(activity.Type, policy.RunActivityType, StringComparison.Ordinal);
 79
 80    private static bool HasExpression(IActivity activity, ScriptPolicy policy) =>
 81        activity.GetInputs().Any(x => string.Equals(x.Expression?.Type, policy.ExpressionType, StringComparison.Ordinal)
 82
 83    private sealed record ScriptPolicy(string ExpressionType, string RunActivityType, string HostDisabledMessage);
 84}
 85
 2786internal readonly record struct WorkflowDefinitionScriptAuthorizationResult(bool Succeeded, WorkflowDefinitionScriptAuth
 87{
 1688    public static WorkflowDefinitionScriptAuthorizationResult Allowed() => new(true, null, null);
 89
 090    public static WorkflowDefinitionScriptAuthorizationResult HostDisabled(string message) => new(false, WorkflowDefinit
 91}
 92
 93internal enum WorkflowDefinitionScriptAuthorizationFailureReason
 94{
 95    HostDisabled
 96}