< Summary

Information
Class: Elsa.ExternalAuthentication.Services.ConnectionRevisionCalculator
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Services/ConnectionRevisionCalculator.cs
Line coverage
94%
Covered lines: 124
Uncovered lines: 7
Coverable lines: 131
Total lines: 202
Line coverage: 94.6%
Branch coverage
78%
Covered branches: 25
Total branches: 32
Branch coverage: 78.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CalculateMaterialRevision(...)100%11100%
CalculateSourceVersion(...)100%22100%
CalculateRegistryVersion(...)100%22100%
CalculateConfigurationConnectionId(...)100%11100%
NormalizeKey(...)50%44100%
CalculateHash(...)100%11100%
WriteMaterial(...)62.5%8882.05%
WriteScope(...)100%11100%
WritePolicy(...)100%22100%
WriteClaimProjection(...)50%22100%
WriteStringSet(...)75%44100%
WriteJson(...)87.5%8893.75%

File(s)

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

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using System.Text.Json;
 4using Elsa.ExternalAuthentication.Contracts;
 5using Elsa.ExternalAuthentication.Models;
 6
 7namespace Elsa.ExternalAuthentication.Services;
 8
 9/// <summary>
 10/// Produces deterministic, non-secret fingerprints for connection material and source snapshots.
 11/// </summary>
 12public sealed class ConnectionRevisionCalculator
 13{
 14    public string CalculateMaterialRevision(IdentityProviderConnection connection)
 15    {
 11016        return CalculateHash(writer => WriteMaterial(writer, connection), "m-");
 17    }
 18
 19    public string CalculateSourceVersion(ConnectionScope scope, IEnumerable<IdentityProviderConnection> connections)
 20    {
 321        return CalculateHash(writer =>
 322        {
 323            writer.WriteStartObject();
 324            WriteScope(writer, scope);
 325            writer.WritePropertyName("connections");
 326            writer.WriteStartArray();
 1827            foreach (var connection in connections.OrderBy(x => NormalizeKey(x.Key), StringComparer.Ordinal).ThenBy(x =>
 328            {
 329                writer.WriteStartObject();
 330                writer.WriteString("id", connection.Id);
 331                writer.WriteString("key", NormalizeKey(connection.Key));
 332                writer.WriteString("displayName", connection.DisplayName);
 333                writer.WriteString("iconId", connection.IconId);
 334                writer.WriteNumber("displayOrder", connection.DisplayOrder);
 335                writer.WriteBoolean("isPreferred", connection.IsPreferred);
 336                writer.WriteString("materialRevision", CalculateMaterialRevision(connection));
 337                writer.WriteEndObject();
 338            }
 339
 340            writer.WriteEndArray();
 341            writer.WriteEndObject();
 642        }, "s-");
 43    }
 44
 45    public string CalculateRegistryVersion(IEnumerable<(string SourceName, ConnectionSourceOwnership Ownership, Connecti
 46    {
 1847        return CalculateHash(writer =>
 1848        {
 1849            writer.WriteStartArray();
 17450            foreach (var (sourceName, ownership, snapshot) in snapshots.OrderBy(x => x.SourceName, StringComparer.Ordina
 1851            {
 2352                writer.WriteStartObject();
 2353                writer.WriteString("source", sourceName);
 2354                writer.WriteString("ownership", ownership.ToString());
 2355                WriteScope(writer, snapshot.Scope);
 2356                writer.WriteString("version", snapshot.Version);
 2357                writer.WriteEndObject();
 1858            }
 1859
 1860            writer.WriteEndArray();
 3661        }, "r-");
 62    }
 63
 64    public static string CalculateConfigurationConnectionId(ConnectionScope scope, string key)
 65    {
 666        var payload = $"{scope.Kind}\n{scope.TenantId}\n{NormalizeKey(key)}";
 667        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(payload));
 668        return $"configuration-{Convert.ToHexString(hash).ToLowerInvariant()}";
 69    }
 70
 16219371    public static string NormalizeKey(string? key) => key?.Trim().ToLowerInvariant() ?? string.Empty;
 72
 73    private static string CalculateHash(Action<Utf8JsonWriter> write, string prefix)
 74    {
 7675        using var stream = new MemoryStream();
 7676        using (var writer = new Utf8JsonWriter(stream))
 7677            write(writer);
 78
 7679        var hash = SHA256.HashData(stream.ToArray());
 7680        return prefix + Convert.ToHexString(hash).ToLowerInvariant();
 7681    }
 82
 83    private static void WriteMaterial(Utf8JsonWriter writer, IdentityProviderConnection connection)
 84    {
 5585        writer.WriteStartObject();
 5586        writer.WriteString("tenantId", connection.TenantId);
 5587        writer.WriteString("key", NormalizeKey(connection.Key));
 5588        writer.WriteString("adapterType", connection.AdapterType);
 5589        writer.WriteNumber("adapterSettingsVersion", connection.AdapterSettingsVersion);
 5590        writer.WritePropertyName("adapterSettings");
 5591        WriteJson(writer, connection.AdapterSettings);
 5592        writer.WritePropertyName("secretBindings");
 5593        writer.WriteStartArray();
 14094        foreach (var binding in (connection.SecretBindings ?? new Dictionary<string, SecretBinding>()).OrderBy(x => x.Ke
 95        {
 1096            writer.WriteStartObject();
 1097            writer.WriteString("name", binding.Key);
 1098            writer.WriteString("resolverType", binding.Value.ResolverType);
 1099            writer.WriteString("reference", binding.Value.Reference);
 10100            writer.WriteString("expectedType", binding.Value.ExpectedType);
 10101            writer.WriteString("expectedScope", binding.Value.ExpectedScope);
 10102            writer.WriteEndObject();
 103        }
 104
 55105        writer.WriteEndArray();
 55106        writer.WriteBoolean("isEnabled", connection.IsEnabled);
 55107        writer.WriteBoolean("overridesConfigurationConnection", connection.OverridesConfigurationConnection);
 55108        writer.WriteBoolean("isArchived", connection.ArchivedAt.HasValue);
 55109        writer.WriteString("upstreamLogoutMode", connection.UpstreamLogoutMode.ToString());
 55110        writer.WritePropertyName("unlinkedPolicy");
 55111        WritePolicy(writer, connection.UnlinkedPolicy);
 55112        writer.WritePropertyName("permissionGrantSources");
 55113        writer.WriteStartArray();
 110114        foreach (var grantSource in (connection.PermissionGrantSources ?? []).OrderBy(x => x.Order).ThenBy(x => x.Type, 
 115        {
 0116            writer.WriteStartObject();
 0117            writer.WriteString("type", grantSource.Type);
 0118            writer.WriteNumber("settingsVersion", grantSource.SettingsVersion);
 0119            writer.WriteNumber("order", grantSource.Order);
 0120            writer.WritePropertyName("settings");
 0121            WriteJson(writer, grantSource.Settings);
 0122            writer.WriteEndObject();
 123        }
 124
 55125        writer.WriteEndArray();
 55126        writer.WritePropertyName("claimProjection");
 55127        WriteClaimProjection(writer, connection.ClaimProjection);
 55128        writer.WriteEndObject();
 55129    }
 130
 131    private static void WriteScope(Utf8JsonWriter writer, ConnectionScope scope)
 132    {
 26133        writer.WriteString("scopeKind", scope.Kind.ToString());
 26134        writer.WriteString("scopeTenantId", scope.TenantId);
 26135    }
 136
 137    private static void WritePolicy(Utf8JsonWriter writer, PolicySelection? policy)
 138    {
 55139        if (policy is null)
 140        {
 50141            writer.WriteNullValue();
 50142            return;
 143        }
 144
 5145        writer.WriteStartObject();
 5146        writer.WriteString("type", policy.Type);
 5147        writer.WriteNumber("settingsVersion", policy.SettingsVersion);
 5148        writer.WritePropertyName("settings");
 5149        WriteJson(writer, policy.Settings);
 5150        writer.WriteEndObject();
 5151    }
 152
 153    private static void WriteClaimProjection(Utf8JsonWriter writer, ClaimProjection? projection)
 154    {
 55155        projection ??= ClaimProjection.Empty;
 55156        writer.WriteStartObject();
 55157        WriteStringSet(writer, "allowedClaimTypes", projection.AllowedClaimTypes);
 55158        WriteStringSet(writer, "redactedClaimTypes", projection.RedactedClaimTypes);
 55159        writer.WriteNumber("maximumClaimCount", projection.MaximumClaimCount);
 55160        writer.WriteNumber("maximumValueLength", projection.MaximumValueLength);
 55161        writer.WriteNumber("maximumTotalBytes", projection.MaximumTotalBytes);
 55162        writer.WriteEndObject();
 55163    }
 164
 165    private static void WriteStringSet(Utf8JsonWriter writer, string propertyName, IReadOnlySet<string>? values)
 166    {
 110167        writer.WritePropertyName(propertyName);
 110168        writer.WriteStartArray();
 256169        foreach (var value in values is null ? Enumerable.Empty<string>() : values.OrderBy(x => x, StringComparer.Ordina
 12170            writer.WriteStringValue(value);
 110171        writer.WriteEndArray();
 110172    }
 173
 174    private static void WriteJson(Utf8JsonWriter writer, JsonElement value)
 175    {
 146176        switch (value.ValueKind)
 177        {
 178            case JsonValueKind.Undefined:
 7179                writer.WriteNullValue();
 7180                break;
 181            case JsonValueKind.Object:
 63182                writer.WriteStartObject();
 372183                foreach (var property in value.EnumerateObject().OrderBy(x => x.Name, StringComparer.Ordinal))
 184                {
 82185                    writer.WritePropertyName(property.Name);
 82186                    WriteJson(writer, property.Value);
 187                }
 188
 63189                writer.WriteEndObject();
 63190                break;
 191            case JsonValueKind.Array:
 5192                writer.WriteStartArray();
 18193                foreach (var item in value.EnumerateArray())
 4194                    WriteJson(writer, item);
 5195                writer.WriteEndArray();
 5196                break;
 197            default:
 71198                value.WriteTo(writer);
 199                break;
 200        }
 71201    }
 202}