< 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
 188internal class WorkflowDefinitionScriptAuthorizationService(
 189    IActivityVisitor activityVisitor,
 1810    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    {
 1134        var scriptUsages = await GetUsedScriptPoliciesAsync(root, cancellationToken);
 35
 1136        var failure = scriptUsages
 1137            .Select(AuthorizeScriptUsage)
 1138            .FirstOrDefault(result => result is { Succeeded: false });
 39
 1140        if (failure.FailureReason.HasValue)
 041            return failure;
 42
 1143        return WorkflowDefinitionScriptAuthorizationResult.Allowed();
 1144    }
 45
 46    public async Task<WorkflowDefinitionScriptAuthorizationResult> AuthorizeAsync(Workflow workflow, CancellationToken c
 47    {
 1148        return await AuthorizeAsync((IActivity)workflow, cancellationToken);
 1149    }
 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    {
 1168        var graph = await activityVisitor.VisitAsync(root, cancellationToken);
 1169        var nodes = new[] { graph }.Concat(graph.Descendants()).ToList();
 1170        var policies = ScriptPolicies
 11471            .Where(policy => nodes.Any(x => IsRunActivity(x.Activity, policy) || HasExpression(x.Activity, policy)))
 1172            .ToList();
 73
 1174        return policies;
 1175    }
 76
 77    private static bool IsRunActivity(IActivity activity, ScriptPolicy policy) =>
 9278        string.Equals(activity.Type, policy.RunActivityType, StringComparison.Ordinal);
 79
 80    private static bool HasExpression(IActivity activity, ScriptPolicy policy) =>
 13881        activity.GetInputs().Any(x => string.Equals(x.Expression?.Type, policy.ExpressionType, StringComparison.Ordinal)
 82
 14083    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}