< Summary

Information
Class: Elsa.ExternalAuthentication.Policies.MatchExternalUserUnlinkedIdentityPolicy
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Policies/MatchExternalUserUnlinkedIdentityPolicy.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 41
Coverable lines: 41
Total lines: 63
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 46
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
get_Type()100%210%
Describe()100%210%
EvaluateAsync()0%600240%
TryReadMatcher(...)0%272160%
ReadString(...)0%4260%
get_Type()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Policies/MatchExternalUserUnlinkedIdentityPolicy.cs

#LineLine coverage
 1using System.Text.Json;
 2using Elsa.ExternalAuthentication.Contracts;
 3using Elsa.ExternalAuthentication.Models;
 4
 5namespace Elsa.ExternalAuthentication.Policies;
 6
 7/// <summary>Delegates external-to-Elsa identity matching to one trusted matcher and applies a safe configured no-match 
 08public sealed class MatchExternalUserUnlinkedIdentityPolicy(IExternalUserMatcherRegistry matchers) : IUnlinkedIdentityPo
 9{
 10    public const string PolicyType = "match-user";
 011    public string Type => PolicyType;
 12
 013    public UnlinkedIdentityPolicyDescriptor Describe() => new(
 014        Type,
 015        "Match an existing user",
 016        "Uses one deployment-installed user matcher, then rejects or creates a user when no match is returned.",
 017        1,
 018        [
 019            new("matcher", "User matcher", "Versioned matcher selection and settings.", "json", true, "json", null, [], 
 020            new("noMatchAction", "No-match action", "Reject access or create a credential-less user.", "string", true, "
 021            new("defaultRoleIds", "Default roles", "Role IDs applied only when the no-match action creates a user.", "st
 022        ],
 023        null);
 24
 25    public async ValueTask<UnlinkedIdentityDecision> EvaluateAsync(UnlinkedIdentityContext context, CancellationToken ca
 26    {
 027        if (!TryReadMatcher(context.Settings, out var selection) || !matchers.TryGet(selection.Type, out var matcher))
 028            return new UnlinkedIdentityDecision.Reject("identity_unlinked");
 029        var descriptor = matchers.ListDescriptors().FirstOrDefault(x => string.Equals(x.Type, selection.Type, StringComp
 030        if (descriptor is null || descriptor.SettingsVersion != selection.SettingsVersion)
 031            return new UnlinkedIdentityDecision.Reject("identity_unlinked");
 32
 033        var requiredClaims = (descriptor.RequiredClaimTypes ?? []).ToHashSet(StringComparer.Ordinal);
 034        var claims = context.ProjectedClaims.Where(x => requiredClaims.Contains(x.Key)).ToDictionary(x => x.Key, x => x.
 035        var match = await matcher.MatchAsync(new ExternalUserMatcherContext(context.TargetTenantId, context.Connection, 
 036        if (match is ExternalUserMatchResult.Match { UserId: { Length: > 0 } userId, AuthorizationBasis: { Length: > 0 }
 037            return new UnlinkedIdentityDecision.LinkExistingUser(userId, basis);
 38
 039        if (match is not ExternalUserMatchResult.NoMatch)
 040            return new UnlinkedIdentityDecision.Reject("identity_unlinked");
 41
 042        return string.Equals(ReadString(context.Settings, "noMatchAction"), "create-user", StringComparison.OrdinalIgnor
 043            ? new UnlinkedIdentityDecision.CreateUser(new UserCreationProposal("external", DefaultRoleIds: CreateUserUnl
 044            : new UnlinkedIdentityDecision.Reject("identity_unlinked");
 045    }
 46
 47    private static bool TryReadMatcher(JsonElement settings, out MatcherSelection selection)
 48    {
 049        selection = default!;
 050        if (settings.ValueKind != JsonValueKind.Object || !settings.TryGetProperty("matcher", out var value) || value.Va
 051            return false;
 052        var type = ReadString(value, "type");
 053        var version = value.TryGetProperty("settingsVersion", out var versionValue) && versionValue.TryGetInt32(out var 
 054        var matcherSettings = value.TryGetProperty("settings", out var matcherSettingsValue) ? matcherSettingsValue.Clon
 055        if (string.IsNullOrWhiteSpace(type) || version <= 0)
 056            return false;
 057        selection = new MatcherSelection(type, version, matcherSettings);
 058        return true;
 59    }
 60
 061    private static string? ReadString(JsonElement value, string name) => value.ValueKind == JsonValueKind.Object && valu
 062    private sealed record MatcherSelection(string Type, int SettingsVersion, JsonElement Settings);
 63}