< Summary

Information
Class: Elsa.ExternalAuthentication.Services.DefaultIdentityProviderConnectionRegistry
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/DefaultIdentityProviderConnectionRegistry.cs
Line coverage
98%
Covered lines: 97
Uncovered lines: 1
Coverable lines: 98
Total lines: 156
Line coverage: 98.9%
Branch coverage
91%
Covered branches: 31
Total branches: 34
Branch coverage: 91.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetAsync()88.46%262697.43%
FindByKeyAsync()100%11100%
FindByIdAsync()100%11100%
CreateLoginMethods(...)100%44100%
ToLoginMethods(...)100%11100%
GetApplicableScopes()100%11100%
IsInScope(...)100%11100%
IsAvailableForAuthentication(...)100%66100%
GetOwnershipPriority(...)100%11100%
ToReference(...)100%11100%
get_Source()100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/DefaultIdentityProviderConnectionRegistry.cs

#LineLine coverage
 1using Elsa.ExternalAuthentication.Contracts;
 2using Elsa.ExternalAuthentication.Models;
 3
 4namespace Elsa.ExternalAuthentication.Services;
 5
 6/// <summary>
 7/// Builds the read-through effective connection registry from configuration and optional persisted sources.
 8/// </summary>
 109public sealed class DefaultIdentityProviderConnectionRegistry(
 1010    IEnumerable<IIdentityProviderConnectionSource> sources,
 1011    ConnectionRevisionCalculator revisionCalculator) : IIdentityProviderConnectionRegistry
 12{
 1013    private readonly IReadOnlyList<IIdentityProviderConnectionSource> _sources = sources
 1014        .OrderBy(x => GetOwnershipPriority(x.Ownership))
 1015        .ThenBy(x => x.Name, StringComparer.Ordinal)
 1016        .ToArray();
 17
 18    public async ValueTask<EffectiveConnectionRegistry> GetAsync(string targetTenantId, CancellationToken cancellationTo
 19    {
 1820        var scopes = GetApplicableScopes();
 1821        var snapshots = new List<(IIdentityProviderConnectionSource Source, ConnectionSourceSnapshot Snapshot)>();
 22
 7223        foreach (var scope in scopes)
 8224        foreach (var source in _sources)
 25        {
 2326            var snapshot = await source.GetSnapshotAsync(scope, cancellationToken);
 2327            if (snapshot.Scope != scope)
 028                throw new InvalidOperationException($"Connection source '{source.Name}' returned a snapshot for a scope 
 29
 2330            snapshots.Add((source, snapshot));
 2331        }
 32
 1833        var candidates = snapshots
 34            .SelectMany(x => x.Snapshot.Connections.Select(connection => new Candidate(x.Source, x.Snapshot.Scope, conne
 35            .Where(x => IsInScope(x.Connection, x.Scope))
 36            .OrderBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), StringComparer.Ordinal)
 37            .ThenBy(x => GetOwnershipPriority(x.Source.Ownership))
 38            .ThenBy(x => x.Source.Name, StringComparer.Ordinal)
 39            .ThenBy(x => x.Connection.Id, StringComparer.Ordinal)
 1840            .ToArray();
 41
 1842        var connections = new List<EffectiveIdentityProviderConnection>(candidates.Length);
 43        foreach (var group in candidates.GroupBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), Strin
 44        {
 4001845            var candidatesForKey = group.ToArray();
 46            var hasInheritedScopeCollision = candidatesForKey.Select(x => x.Scope).Distinct().Skip(1).Any();
 47
 48            var explicitOverride = candidatesForKey.FirstOrDefault(x => x.Source.Ownership == ConnectionSourceOwnership.
 49            var preferred = explicitOverride ?? candidatesForKey.FirstOrDefault(x => x.Source.Ownership == ConnectionSou
 4001850            var preferredReference = ToReference(preferred);
 4001851            var shadowedReferences = hasInheritedScopeCollision
 4001852                ? []
 4001853                : candidatesForKey
 4002254                    .Where(candidate => !ReferenceEquals(candidate, preferred) && !candidate.Connection.ArchivedAt.HasVa
 4001855                    .Select(ToReference)
 4001856                    .ToArray();
 57
 16008058            for (var index = 0; index < candidatesForKey.Length; index++)
 59            {
 4002260                var candidate = candidatesForKey[index];
 4002261                var isArchived = candidate.Connection.ArchivedAt.HasValue;
 4002262                var isShadowed = !isArchived && !hasInheritedScopeCollision && !ReferenceEquals(candidate, preferred);
 4002263                connections.Add(new EffectiveIdentityProviderConnection(
 4002264                    candidate.Connection,
 4002265                    candidate.Source.Ownership,
 4002266                    candidate.Scope,
 4002267                    hasInheritedScopeCollision ? ConnectionValidity.Invalid : ConnectionValidity.Unknown,
 4002268                    isShadowed,
 4002269                    candidate.Source.Name)
 4002270                {
 4002271                    ShadowedBy = isShadowed ? preferredReference : null,
 4002272                    Shadows = isShadowed || isArchived ? [] : shadowedReferences
 4002273                });
 74            }
 75        }
 76
 1877        var orderedConnections = connections
 78            .OrderBy(x => x.Connection.DisplayOrder)
 79            .ThenBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), StringComparer.Ordinal)
 80            .ThenBy(x => x.Connection.Id, StringComparer.Ordinal)
 1881            .ToArray();
 82
 83        var version = revisionCalculator.CalculateRegistryVersion(snapshots.Select(x => (x.Source.Name, x.Source.Ownersh
 1884        var loginMethods = CreateLoginMethods(orderedConnections);
 1885        return new EffectiveConnectionRegistry(orderedConnections, loginMethods, version);
 1886    }
 87
 88    public async ValueTask<EffectiveIdentityProviderConnection?> FindByKeyAsync(string targetTenantId, string key, Cance
 89    {
 390        var registry = await GetAsync(targetTenantId, cancellationToken);
 391        var normalizedKey = ConnectionRevisionCalculator.NormalizeKey(key);
 392        return registry.Connections.FirstOrDefault(x =>
 154793            IsAvailableForAuthentication(x) &&
 154794            string.Equals(ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), normalizedKey, StringComparison.O
 395    }
 96
 97    public async ValueTask<EffectiveIdentityProviderConnection?> FindByIdAsync(string targetTenantId, string connectionI
 98    {
 199        var registry = await GetAsync(targetTenantId, cancellationToken);
 1542100        return registry.Connections.FirstOrDefault(x => string.Equals(x.Connection.Id, connectionId, StringComparison.Or
 1101    }
 102
 103    private static IReadOnlyCollection<LoginMethod> CreateLoginMethods(IReadOnlyCollection<EffectiveIdentityProviderConn
 104    {
 18105        var available = connections
 18106            .Where(IsAvailableForAuthentication)
 18107            .ToArray();
 18108        var configuredPreferred = available
 40017109            .Where(x => x.Ownership == ConnectionSourceOwnership.Configuration && x.Connection.IsPreferred)
 6110            .OrderBy(x => x.Connection.DisplayOrder)
 6111            .ThenBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), StringComparer.Ordinal)
 6112            .ThenBy(x => x.Connection.Id, StringComparer.Ordinal)
 18113            .FirstOrDefault();
 18114        if (configuredPreferred is not null)
 6115            return ToLoginMethods(available, configuredPreferred.Connection.Id);
 116
 12117        var preferredConnectionId = available
 14118            .Where(x => x.Connection.IsPreferred)
 1119            .OrderBy(x => x.Connection.DisplayOrder)
 1120            .ThenBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), StringComparer.Ordinal)
 1121            .ThenBy(x => x.Connection.Id, StringComparer.Ordinal)
 1122            .Select(x => x.Connection.Id)
 12123            .FirstOrDefault();
 124
 12125        return ToLoginMethods(available, preferredConnectionId);
 126    }
 127
 18128    private static IReadOnlyCollection<LoginMethod> ToLoginMethods(IEnumerable<EffectiveIdentityProviderConnection> conn
 40017129            .OrderBy(x => x.Connection.DisplayOrder)
 40017130            .ThenBy(x => ConnectionRevisionCalculator.NormalizeKey(x.Connection.Key), StringComparer.Ordinal)
 40017131            .ThenBy(x => x.Connection.Id, StringComparer.Ordinal)
 40017132            .Select(x => new LoginMethod(
 40017133                x.Connection.Id,
 40017134                x.Connection.Key,
 40017135                LoginMethodKind.External,
 40017136                x.Connection.DisplayName,
 40017137                x.Connection.IconId,
 40017138                x.Connection.DisplayOrder,
 40017139                string.Equals(x.Connection.Id, preferredConnectionId, StringComparison.Ordinal),
 40017140                new Uri($"/external-authentication/authorize/{Uri.EscapeDataString(x.Connection.Key)}", UriKind.Relative
 18141            .ToArray();
 142
 18143    private static IReadOnlyList<ConnectionScope> GetApplicableScopes() => [ConnectionScope.Host];
 144
 40022145    private static bool IsInScope(IdentityProviderConnection connection, ConnectionScope scope) => string.Equals(connect
 146    private static bool IsAvailableForAuthentication(EffectiveIdentityProviderConnection connection) =>
 41566147        !connection.IsShadowed &&
 41566148        connection.Validity != ConnectionValidity.Invalid &&
 41566149        connection.Connection.IsEnabled &&
 41566150        !connection.Connection.ArchivedAt.HasValue;
 40024151    private static int GetOwnershipPriority(ConnectionSourceOwnership ownership) => ownership == ConnectionSourceOwnersh
 152    private static IdentityProviderConnectionReference ToReference(Candidate candidate) =>
 40020153        new(candidate.Connection.Id, candidate.Connection.DisplayName, candidate.Source.Ownership);
 154
 760394155    private sealed record Candidate(IIdentityProviderConnectionSource Source, ConnectionScope Scope, IdentityProviderCon
 156}