< Summary

Information
Class: Elsa.Authorization.Permission
Assembly: Elsa.Api.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/common/Elsa.Api.Common/Authorization/Permission.cs
Line coverage
100%
Covered lines: 32
Uncovered lines: 0
Coverable lines: 32
Total lines: 115
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Resource()100%11100%
get_All()100%11100%
get_IsResourceWildcard()100%11100%
get_IsVerbWildcard()100%11100%
get_IsSubtree()100%22100%
get_HasWildcard()100%44100%
get_IsValidPattern()100%1010100%
TryParse(...)100%1414100%
Parse(...)100%22100%
ToString()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/common/Elsa.Api.Common/Authorization/Permission.cs

#LineLine coverage
 1namespace Elsa.Authorization;
 2
 3/// <summary>
 4/// A permission: a hierarchical resource path paired with a verb, written <c>{resource}:{verb}</c>.
 5/// Both axes are open and string-keyed, and both accept a wildcard.
 6/// </summary>
 7/// <remarks>
 8/// A trailing <c>*</c> on the resource axis matches the named node and every descendant at any depth,
 9/// so <c>workflows/definitions/*</c> covers <c>workflows/definitions</c> itself as well as
 10/// <c>workflows/definitions/versions</c>. A bare <c>*</c> resource matches everything, and <c>*</c> as a
 11/// verb matches any verb. Wildcards are the only construct with forward reach: they cover resources and
 12/// verbs registered in later releases.
 13/// </remarks>
 50514public readonly record struct Permission(string Resource, string Verb)
 15{
 16    /// <summary>Separates the resource from the verb.</summary>
 17    public const char Separator = ':';
 18
 19    /// <summary>Separates resource path segments.</summary>
 20    public const char PathSeparator = '/';
 21
 22    /// <summary>Matches any resource, or any verb, depending on the axis it appears on.</summary>
 23    public const string Wildcard = "*";
 24
 25    /// <summary>The whole vocabulary. Superuser is this grant, not a special case in the evaluator.</summary>
 4026    public static Permission All { get; } = new(Wildcard, Wildcard);
 27
 28    /// <summary>Whether the resource axis is the bare wildcard.</summary>
 3829    public bool IsResourceWildcard => Resource == Wildcard;
 30
 31    /// <summary>Whether the verb axis is the wildcard.</summary>
 4332    public bool IsVerbWildcard => Verb == Wildcard;
 33
 34    /// <summary>Whether the resource names a subtree, as in <c>workflows/*</c>.</summary>
 2535    public bool IsSubtree => Resource.Length > 2 && Resource.EndsWith($"{PathSeparator}{Wildcard}", StringComparison.Ord
 36
 37    /// <summary>Whether either axis carries a wildcard.</summary>
 438    public bool HasWildcard => IsResourceWildcard || IsVerbWildcard || IsSubtree;
 39
 40    /// <summary>
 41    /// Whether every <c>*</c> this permission carries sits where the matcher gives it meaning: the entire
 42    /// resource (<c>*</c>), a trailing <c>/*</c> subtree segment with no other <c>*</c>, or the entire verb.
 43    /// </summary>
 44    /// <remarks>
 45    /// <see cref="TryParse"/> stays lenient because stored roles may hold historical strings, so a stray
 46    /// wildcard such as <c>workflows*</c> or <c>work*/foo</c> parses yet can never match anything. Validation
 47    /// paths use this check to surface those entries instead of letting them silently match nothing — which
 48    /// in a deny list would mean silently not denying.
 49    /// </remarks>
 50    public bool IsValidPattern
 51    {
 52        get
 53        {
 2954            if (!IsVerbWildcard && Verb.Contains(Wildcard, StringComparison.Ordinal))
 255                return false;
 56
 2757            var first = Resource.IndexOf(Wildcard, StringComparison.Ordinal);
 58
 2759            if (first < 0)
 1160                return true;
 61
 1662            return first == Resource.LastIndexOf(Wildcard, StringComparison.Ordinal) && (IsResourceWildcard || IsSubtree
 63        }
 64    }
 65
 66    /// <summary>
 67    /// Parses <paramref name="value"/>, returning <c>false</c> when it is not a well-formed permission.
 68    /// </summary>
 69    /// <remarks>
 70    /// A bare <c>*</c> — no separator, the wildcard alone — normalizes to <see cref="All"/>. This is a
 71    /// parsing rule rather than an evaluation special case, so the evaluator never sees a sentinel. It is
 72    /// what lets a stored or seeded <c>*</c> keep authorizing across the vocabulary migration without a
 73    /// lock-out window.
 74    /// </remarks>
 75    public static bool TryParse(string? value, out Permission permission)
 76    {
 15677        permission = default;
 78
 15679        if (string.IsNullOrWhiteSpace(value))
 480            return false;
 81
 15282        var trimmed = value.Trim();
 83
 15284        if (trimmed == Wildcard)
 85        {
 3786            permission = All;
 3787            return true;
 88        }
 89
 90        // A permission may never contain a comma: the persistence converter joins collections with one.
 11591        if (trimmed.Contains(','))
 292            return false;
 93
 11394        var separator = trimmed.IndexOf(Separator);
 95
 11396        if (separator <= 0 || separator == trimmed.Length - 1)
 897            return false;
 98
 10599        var resource = trimmed[..separator];
 105100        var verb = trimmed[(separator + 1)..];
 101
 105102        if (verb.IndexOf(Separator) >= 0 || verb.IndexOf(PathSeparator) >= 0)
 2103            return false;
 104
 103105        permission = new(resource, verb);
 103106        return true;
 107    }
 108
 109    /// <summary>Parses <paramref name="value"/>, throwing when it is not a well-formed permission.</summary>
 110    public static Permission Parse(string value) =>
 62111        TryParse(value, out var permission) ? permission : throw new FormatException($"'{value}' is not a well-formed pe
 112
 113    /// <inheritdoc />
 7114    public override string ToString() => $"{Resource}{Separator}{Verb}";
 115}