< Summary

Information
Class: Elsa.ExternalAuthentication.Services.IdentityProviderConnectionValidityAssessor
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/IdentityProviderConnectionValidityAssessor.cs
Line coverage
88%
Covered lines: 46
Uncovered lines: 6
Coverable lines: 52
Total lines: 99
Line coverage: 88.4%
Branch coverage
75%
Covered branches: 12
Total branches: 16
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AssessAsync()50%2262.5%
AssessCoreAsync()87.5%8888%
GetSecretStatesAsync()75%44100%

File(s)

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

#LineLine coverage
 1using Elsa.Common;
 2using Elsa.ExternalAuthentication.Contracts;
 3using Elsa.ExternalAuthentication.Models;
 4
 5namespace Elsa.ExternalAuthentication.Services;
 6
 7/// <summary>
 8/// Resolves adapter-specific structural validity without invoking provider test endpoints.
 9/// </summary>
 2310public sealed class IdentityProviderConnectionValidityAssessor(
 2311    IExternalAuthenticationAdapterRegistry adapters,
 2312    IAdapterSettingsMigrationService settingsMigrations,
 2313    IEnumerable<ISecretBindingResolver> secretBindingResolvers,
 2314    ISystemClock clock) : IIdentityProviderConnectionValidityAssessor
 15{
 2316    private readonly IReadOnlyDictionary<string, ISecretBindingResolver> _secretBindingResolvers =
 4517        secretBindingResolvers.ToDictionary(x => x.Type, StringComparer.Ordinal);
 18
 19    public async ValueTask<EffectiveIdentityProviderConnection> AssessAsync(
 20        EffectiveIdentityProviderConnection effective,
 21        CancellationToken cancellationToken = default)
 22    {
 4423        if (effective.Validity == ConnectionValidity.Invalid)
 024            return effective;
 25
 26        try
 27        {
 4428            return await AssessCoreAsync(effective, cancellationToken);
 29        }
 030        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 31        {
 032            throw;
 33        }
 134        catch
 35        {
 36            // A broken adapter or secret backend must not prevent every other sign-in method from being discovered.
 137            return effective with { Validity = ConnectionValidity.Invalid };
 38        }
 4439    }
 40
 41    private async ValueTask<EffectiveIdentityProviderConnection> AssessCoreAsync(
 42        EffectiveIdentityProviderConnection effective,
 43        CancellationToken cancellationToken)
 44    {
 45
 4446        var connection = IdentityProviderConnectionCloner.Clone(effective.Connection);
 4447        if (!adapters.TryGet(connection.AdapterType, out var adapter))
 148            return effective with { Validity = ConnectionValidity.Invalid };
 49
 50        try
 51        {
 4352            var migration = await settingsMigrations.MigrateAsync(
 4353                connection.AdapterType,
 4354                connection.AdapterSettingsVersion,
 4355                connection.AdapterSettings,
 4356                cancellationToken);
 4357            connection.AdapterSettingsVersion = migration.SettingsVersion;
 4358            connection.AdapterSettings = migration.Settings;
 4359        }
 060        catch (InvalidOperationException)
 61        {
 062            return effective with { Validity = ConnectionValidity.Invalid };
 63        }
 64
 4365        var descriptor = adapter.Describe();
 4366        var declaredSecrets = descriptor.Fields
 8467            .Where(x => x.IsSecretBinding)
 8668            .ToDictionary(x => x.Name, StringComparer.Ordinal);
 4769        if (connection.SecretBindings.Keys.Any(x => !declaredSecrets.ContainsKey(x)))
 070            return effective with { Validity = ConnectionValidity.Invalid };
 71
 4372        var secretStates = await GetSecretStatesAsync(connection, cancellationToken);
 4273        if (declaredSecrets.Values
 4274            .Where(x => x.IsRequired)
 4475            .Any(field => !secretStates.TryGetValue(field.Name, out var state) || !state.IsConfigured || !state.IsResolv
 276            return effective with { Validity = ConnectionValidity.Invalid };
 77
 4078        var validation = await adapter.ValidateAsync(new ConnectionValidationContext(
 4079            effective with { Connection = connection, Validity = ConnectionValidity.Unknown },
 4080            new Dictionary<string, ResolvedSecretBinding>(),
 4081            clock), cancellationToken);
 4082        return effective with { Validity = validation.IsValid ? ConnectionValidity.Valid : ConnectionValidity.Invalid };
 4383    }
 84
 85    private async ValueTask<IReadOnlyDictionary<string, SecretBindingState>> GetSecretStatesAsync(
 86        IdentityProviderConnection connection,
 87        CancellationToken cancellationToken)
 88    {
 4389        var states = new Dictionary<string, SecretBindingState>(StringComparer.Ordinal);
 9390        foreach (var (name, binding) in connection.SecretBindings)
 91        {
 492            states[name] = _secretBindingResolvers.TryGetValue(binding.ResolverType, out var resolver)
 493                ? await resolver.GetStateAsync(binding, cancellationToken)
 494                : new SecretBindingState(false, false);
 95        }
 96
 4297        return states;
 4298    }
 99}