< Summary

Information
Class: Elsa.Workflows.Api.Security.WorkflowDefinitionScriptAuthorizationService
Assembly: Elsa.Workflows.Api
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Api/Security/WorkflowDefinitionScriptAuthorizationService.cs
Line coverage
87%
Covered lines: 36
Uncovered lines: 5
Coverable lines: 41
Total lines: 96
Line coverage: 87.8%
Branch coverage
33%
Covered branches: 4
Total branches: 12
Branch coverage: 33.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
AuthorizeAsync()50%2275%
AuthorizeAsync()50%2285.71%
AuthorizeAsync()100%11100%
AuthorizeScriptUsage(...)0%2040%
GetUsedScriptPoliciesAsync()100%11100%
IsRunActivity(...)100%11100%
HasExpression(...)50%22100%
get_ExpressionType()100%11100%

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
 218internal class WorkflowDefinitionScriptAuthorizationService(
 219    IActivityVisitor activityVisitor,
 2110    IExpressionDescriptorRegistry expressionDescriptorRegistry)
 11{
 112    private static readonly ScriptPolicy[] ScriptPolicies =
 113    [
 114        new(
 115            "CSharp",
 116            WorkflowScriptActivityTypeNames.RunCSharp,
 117            "C# workflow expression execution is disabled by the host. Set CSharpOptions.AllowHostCodeExecution to true 
 118        new(
 119            "Python",
 120            WorkflowScriptActivityTypeNames.RunPython,
 121            "Python.NET workflow expression execution is disabled by the host. Set PythonOptions.AllowHostCodeExecution 
 122    ];
 23
 24    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(WorkflowDefinitionModel model, Cancell
 25    {
 526        if (model.Root == null)
 527            return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 28
 029        return await AuthorizeAsync(model.Root, cancellationToken);
 530    }
 31
 32    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(IActivity root, CancellationToken canc
 33    {
 1434        var scriptUsages = await GetUsedScriptPoliciesAsync(root, cancellationToken);
 35
 1436        var failure = scriptUsages
 1437            .Select(AuthorizeScriptUsage)
 1438            .FirstOrDefault(result => result is { Succeeded: false });
 39
 1440        if (failure.FailureReason.HasValue)
 041            return failure;
 42
 1443        return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 1444    }
 45
 46    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(Workflow workflow, CancellationToken c
 47    {
 1448        return await AuthorizeAsync((IActivity)workflow, cancellationToken);
 1449    }
 50
 51    private WorkflowDefinitionScriptAuthorizationResult AuthorizeScriptUsage(ScriptPolicy policy)
 52    {
 53        // Language-specific options live in optional modules. Workflows.Api observes the descriptor state projected by 
 054        if (expressionDescriptorRegistry.Find(policy.ExpressionType)?.IsBrowsable != true)
 055            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.
 063        return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 64    }
 65
 66    private async Task<IEnumerable<ScriptPolicy>> GetUsedScriptPoliciesAsync(IActivity root, CancellationToken cancellat
 67    {
 1468        var graph = await activityVisitor.VisitAsync(root, cancellationToken);
 1469        var nodes = new[] { graph }.Concat(graph.Descendants()).ToList();
 1470        var policies = ScriptPolicies
 15071            .Where(policy => nodes.Any(x => IsRunActivity(x.Activity, policy) || HasExpression(x.Activity, policy)))
 1472            .ToList();
 73
 1474        return policies;
 1475    }
 76
 77    private static bool IsRunActivity(IActivity activity, ScriptPolicy policy) =>
 12278        string.Equals(activity.Type, policy.RunActivityType, StringComparison.Ordinal);
 79
 80    private static bool HasExpression(IActivity activity, ScriptPolicy policy) =>
 18481        activity.GetInputs().Any(x => string.Equals(x.Expression?.Type, policy.ExpressionType, StringComparison.Ordinal)
 82
 18683    private sealed record ScriptPolicy(string ExpressionType, string RunActivityType, string HostDisabledMessage);
 84}
 85
 86internal readonly record struct WorkflowDefinitionScriptAuthorizationResult(bool Succeeded, WorkflowDefinitionScriptAuth
 87{
 88    public static WorkflowDefinitionScriptAuthorizationResult Allowed() => new(true, null, null);
 89
 90    public static WorkflowDefinitionScriptAuthorizationResult HostDisabled(string message) => new(false, WorkflowDefinit
 91}
 92
 93internal enum WorkflowDefinitionScriptAuthorizationFailureReason
 94{
 95    HostDisabled
 96}