< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Authentication.AuthenticationRegistry
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Authentication/AuthenticationRegistry.cs
Tag: Kestrun/Kestrun@0d738bf294e6281b936d031e1979d928007495ff
Line coverage
11%
Covered lines: 10
Uncovered lines: 74
Coverable lines: 84
Total lines: 327
Line coverage: 11.9%
Branch coverage
2%
Covered branches: 2
Total branches: 84
Branch coverage: 2.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/16/2025 - 04:01:29 Line coverage: 32.5% (14/43) Branch coverage: 15.3% (4/26) Total lines: 201 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb11/19/2025 - 02:25:56 Line coverage: 25% (14/56) Branch coverage: 10% (4/40) Total lines: 230 Tag: Kestrun/Kestrun@98ff905e5605a920343154665980a71211a03c6d12/12/2025 - 17:27:19 Line coverage: 11.9% (10/84) Branch coverage: 2.3% (2/84) Total lines: 327 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd 09/16/2025 - 04:01:29 Line coverage: 32.5% (14/43) Branch coverage: 15.3% (4/26) Total lines: 201 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb11/19/2025 - 02:25:56 Line coverage: 25% (14/56) Branch coverage: 10% (4/40) Total lines: 230 Tag: Kestrun/Kestrun@98ff905e5605a920343154665980a71211a03c6d12/12/2025 - 17:27:19 Line coverage: 11.9% (10/84) Branch coverage: 2.3% (2/84) Total lines: 327 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
Register(...)100%11100%
Register(...)0%620%
Upsert(...)0%2040%
Upsert(...)0%620%
Exists(...)100%210%
TryGet(...)100%210%
TryGet(...)0%2040%
Get(...)0%620%
Get(...)0%620%
ResolveAuthenticationSchemeName(...)0%342180%
Remove(...)0%620%
Clear()100%210%
Items()100%210%
GetOidcOptions()0%620%
GetClaimPolicyConfigs()0%420200%
GetSchemesByPolicy(...)0%506220%
.ctor(...)100%11100%
Equals(...)0%620%
GetHashCode(...)100%11100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Authentication/AuthenticationRegistry.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Kestrun.Claims;
 3using Microsoft.AspNetCore.Authentication;
 4using Microsoft.AspNetCore.Authentication.Cookies;
 5using Microsoft.AspNetCore.Authentication.JwtBearer;
 6
 7namespace Kestrun.Authentication;
 8
 9/// <summary>
 10/// Registry of authentication options keyed by (schema, type).
 11/// Stores as base AuthenticationSchemeOptions, with typed helpers.
 12/// </summary>
 13public sealed class AuthenticationRegistry
 14{
 15    private readonly ConcurrentDictionary<(string schema, AuthenticationType type), AuthenticationSchemeOptions> _map;
 16    private readonly StringComparer _stringComparer;
 17
 18    /// <summary>
 19    /// Initializes a new instance of the <see cref="AuthenticationRegistry"/> class.
 20    /// </summary>
 21    /// <param name="comparer">The string comparer to use for matching schemas and types.</param>
 48622    public AuthenticationRegistry(StringComparer? comparer = null)
 23    {
 48624        _stringComparer = comparer ?? StringComparer.Ordinal;
 48625        _map = new ConcurrentDictionary<(string, AuthenticationType), AuthenticationSchemeOptions>(new TupleComparer(_st
 48626    }
 27
 28    // ---------- Register / Upsert ----------
 29
 30    /// <summary>
 31    /// Registers an authentication scheme with the specified options.
 32    /// </summary>
 33    /// <param name="schema">The schema to match for the authentication scheme.</param>
 34    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 35    /// <param name="options">The options to configure the authentication scheme.</param>
 36    /// <returns>True if the registration was successful; otherwise, false.</returns>
 37    public bool Register(string schema, AuthenticationType type, AuthenticationSchemeOptions options)
 38    {
 2039        ArgumentNullException.ThrowIfNull(schema);
 2040        ArgumentNullException.ThrowIfNull(options);
 2041        return _map.TryAdd((schema, type), options);
 42    }
 43
 44    /// <summary>
 45    /// Registers an authentication scheme with the specified options and configuration.
 46    /// </summary>
 47    /// <typeparam name="TOptions">The type of the options for the authentication scheme.</typeparam>
 48    /// <param name="schema">The schema to match for the authentication scheme.</param>
 49    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 50    /// <param name="configure">An optional action to configure the authentication options.</param>
 51    /// <returns>True if the registration was successful; otherwise, false.</returns>
 52    public bool Register<TOptions>(string schema, AuthenticationType type, Action<TOptions>? configure = null)
 53        where TOptions : AuthenticationSchemeOptions, new()
 54    {
 055        var opts = new TOptions();
 056        configure?.Invoke(opts);
 057        return Register(schema, type, opts);
 58    }
 59
 60    /// <summary>
 61    /// Upserts (adds or replaces) an entry.
 62    /// </summary>
 63    /// <param name="schema">The schema to match for the authentication scheme.</param>
 64    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 65    /// <param name="options">The options to configure the authentication scheme.</param>
 66    /// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
 67    public void Upsert(string schema, AuthenticationType type, AuthenticationSchemeOptions options)
 68    {
 069        _map[(schema ?? throw new ArgumentNullException(nameof(schema)), type)] =
 070            options ?? throw new ArgumentNullException(nameof(options));
 071    }
 72
 73    /// <summary>
 74    /// Upserts (adds or replaces) an entry.
 75    /// </summary>
 76    /// <typeparam name="TOptions">The type of the options for the authentication scheme.</typeparam>
 77    /// <param name="schema">The schema to match for the authentication scheme.</param>
 78    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 79    /// <param name="configure">An optional action to configure the authentication options.</param>
 80    public void Upsert<TOptions>(string schema, AuthenticationType type, Action<TOptions>? configure = null)
 81        where TOptions : AuthenticationSchemeOptions, new()
 82    {
 083        var opts = new TOptions();
 084        configure?.Invoke(opts);
 085        Upsert(schema, type, opts);
 086    }
 87
 88    // ---------- Exists / TryGet / Get ----------
 89
 90    /// <summary>
 91    /// Checks if an authentication scheme exists for the specified schema and type.
 92    /// </summary>
 93    /// <param name="schema">The schema to match for the authentication scheme.</param>
 94    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 95    /// <returns>True if an authentication scheme exists; otherwise, false.</returns>
 96    public bool Exists(string schema, AuthenticationType type)
 97    {
 098        ArgumentNullException.ThrowIfNull(schema);
 099        return _map.ContainsKey((schema, type));
 100    }
 101
 102    /// <summary>
 103    /// Tries to get the authentication options for the specified schema and type.
 104    /// </summary>
 105    /// <param name="schema">The schema to match for the authentication scheme.</param>
 106    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 107    /// <param name="options">The options for the authentication scheme.</param>
 108    /// <returns>True if the authentication options were found; otherwise, false.</returns>
 109    public bool TryGet(string schema, AuthenticationType type, out AuthenticationSchemeOptions? options)
 110    {
 0111        ArgumentNullException.ThrowIfNull(schema);
 0112        return _map.TryGetValue((schema, type), out options);
 113    }
 114
 115    /// <summary>
 116    /// Tries to get the authentication options of the specified type for the given schema and type.
 117    /// </summary>
 118    /// <typeparam name="TOptions">The type of the authentication options.</typeparam>
 119    /// <param name="schema">The schema to match for the authentication scheme.</param>
 120    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 121    /// <param name="options">The options for the authentication scheme.</param>
 122    /// <returns>True if the authentication options were found; otherwise, false.</returns>
 123    public bool TryGet<TOptions>(string schema, AuthenticationType type, out TOptions? options)
 124        where TOptions : AuthenticationSchemeOptions
 125    {
 0126        if (_map.TryGetValue((schema, type), out var baseOpts) && baseOpts is TOptions typed)
 127        {
 0128            options = typed;
 0129            return true;
 130        }
 0131        options = null;
 0132        return false;
 133    }
 134
 135    /// <summary>
 136    /// Gets the authentication options for the specified schema and type.
 137    /// </summary>
 138    /// <param name="schema">The schema to match for the authentication scheme.</param>
 139    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 140    /// <returns>The authentication options for the specified schema and type.</returns>
 141    /// <exception cref="KeyNotFoundException">Thrown when no authentication options are registered for the specified sc
 142    public AuthenticationSchemeOptions Get(string schema, AuthenticationType type)
 143    {
 0144        return !TryGet(schema, type, out var opts)
 0145            ? throw new KeyNotFoundException($"No authentication registered for schema='{schema}', type='{type}'.")
 0146            : opts!;
 147    }
 148
 149    /// <summary>
 150    /// Gets the authentication options of the specified type for the given schema and type.
 151    /// </summary>
 152    /// <typeparam name="TOptions">The type of the authentication options.</typeparam>
 153    /// <param name="schema">The schema to match for the authentication scheme.</param>
 154    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 155    /// <returns>The authentication options of the specified type for the given schema and type.</returns>
 156    /// <exception cref="KeyNotFoundException">Thrown when no authentication options of the specified type are registere
 157    public TOptions Get<TOptions>(string schema, AuthenticationType type)
 158        where TOptions : AuthenticationSchemeOptions
 159    {
 0160        return !TryGet<TOptions>(schema, type, out var opts)
 0161            ? throw new KeyNotFoundException($"No authentication of type {typeof(TOptions).Name} for schema='{schema}', 
 0162            : opts!;
 163    }
 164
 165    /// <summary>
 166    /// Gets the authentication scheme name for the specified schema and type.
 167    /// </summary>
 168    /// <param name="schema">The schema to match for the authentication scheme.</param>
 169    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 170    /// <param name="cookiesSchemeName">If true, returns the cookie scheme name for cookie-based options.</param>
 171    /// <returns>The authentication scheme name.</returns>
 172    /// <exception cref="KeyNotFoundException">Thrown when no authentication options are registered for the specified sc
 173    public string ResolveAuthenticationSchemeName(string schema, AuthenticationType type, bool cookiesSchemeName)
 174    {
 0175        if (!TryGet(schema, type, out var options))
 176        {
 0177            throw new KeyNotFoundException($"No authentication registered for schema='{schema}', type='{type}'.");
 178        }
 179        // determine scheme name based on options type
 0180        return options switch
 0181        {
 0182            OAuth2Options oauth2Opts => cookiesSchemeName ? oauth2Opts.CookieScheme : oauth2Opts.AuthenticationScheme,
 0183            OidcOptions oidcOpts => cookiesSchemeName ? oidcOpts.CookieScheme : oidcOpts.AuthenticationScheme,
 0184            BasicAuthenticationOptions => schema,
 0185            JwtBearerOptions => schema,
 0186            CookieAuthenticationOptions => schema,
 0187            ApiKeyAuthenticationOptions => schema,
 0188            // DigestAuthenticationOptions digestOpts => digestOpts.AuthenticationScheme,
 0189            _ => schema
 0190        };
 191    }
 192
 193    // ---------- Remove / Clear / Enumerate ----------
 194
 195    /// <summary>
 196    /// Removes the authentication scheme for the specified schema and type.
 197    /// </summary>
 198    /// <param name="schema">The schema to match for the authentication scheme.</param>
 199    /// <param name="type">The HTTP type to match for the authentication scheme.</param>
 200    /// <returns>True if the authentication scheme was removed; otherwise, false.</returns>
 201    /// <exception cref="ArgumentNullException">Thrown when either schema or type is null.</exception>
 202    public bool Remove(string schema, AuthenticationType type)
 0203        => _map.TryRemove((schema ?? throw new ArgumentNullException(nameof(schema)), type), out _);
 204
 205    /// <summary>
 206    /// Clears all registered authentication schemes.
 207    /// </summary>
 0208    public void Clear() => _map.Clear();
 209
 210    /// <summary>
 211    /// Enumerates all registered authentication schemes.
 212    /// </summary>
 213    /// <returns>A collection of key-value pairs representing the registered authentication schemes.</returns>
 214    public IEnumerable<KeyValuePair<(string schema, AuthenticationType type), AuthenticationSchemeOptions>> Items()
 0215        => _map;
 216
 217    /// <summary>
 218    /// Returns a dictionary of all registered OpenID Connect (OIDC) authentication options
 219    /// keyed by their schema. Only entries whose <see cref="AuthenticationType"/> is <see cref="AuthenticationType.Oidc
 220    /// and whose stored options are of type <see cref="OidcOptions"/> are included.
 221    /// </summary>
 222    /// <returns>A dictionary mapping schema names to their <see cref="OidcOptions"/>.</returns>
 223    public IDictionary<string, OidcOptions> GetOidcOptions()
 224    {
 0225        var result = new Dictionary<string, OidcOptions>(_stringComparer);
 0226        foreach (var kvp in _map.Where(kvp => kvp.Key.type == AuthenticationType.Oidc && kvp.Value is OidcOptions))
 227        {
 0228            result[kvp.Key.schema] = (OidcOptions)kvp.Value;
 229        }
 0230        return result;
 231    }
 232
 233    /// <summary>
 234    /// Returns a dictionary mapping authentication schema names to the array of claim policy names
 235    /// defined for that schema. It inspects registered option objects for a property named
 236    /// <c>ClaimPolicyConfig</c> or <c>ClaimPolicy</c> of type <see cref="ClaimPolicyConfig"/>.
 237    /// Only schemas with at least one defined policy are returned.
 238    /// Supports <see cref="OidcOptions"/>, <see cref="OAuth2Options"/>, and other options types.
 239    /// </summary>
 240    /// <returns>A dictionary of schema -> string[] (policy names).</returns>
 241    public IDictionary<string, string[]> GetClaimPolicyConfigs()
 242    {
 0243        var result = new Dictionary<string, string[]>(_stringComparer);
 0244        foreach (var kvp in _map)
 245        {
 0246            var options = kvp.Value;
 0247            ClaimPolicyConfig? cfg = null;
 248
 249            // Try explicit properties first (avoids reflection if we add strong-typed support later)
 0250            if (options is OidcOptions oidc && oidc.ClaimPolicy is { } oidcCfg)
 251            {
 0252                cfg = oidcCfg;
 253            }
 0254            else if (options is OAuth2Options oauth2)
 255            {
 256                // OAuth2Options uses reflection fallback (no ClaimPolicy property currently)
 0257                var prop = oauth2.GetType().GetProperty("ClaimPolicyConfig") ?? oauth2.GetType().GetProperty("ClaimPolic
 0258                if (prop != null && typeof(ClaimPolicyConfig).IsAssignableFrom(prop.PropertyType))
 259                {
 0260                    cfg = (ClaimPolicyConfig?)prop.GetValue(oauth2);
 261                }
 262            }
 263
 0264            if (cfg?.Policies is { Count: > 0 })
 265            {
 0266                result[kvp.Key.schema] = [.. cfg.Policies.Keys];
 267            }
 268        }
 0269        return result;
 270    }
 271
 272    /// <summary>
 273    /// Returns a list of authentication schema names that define the specified policy name.
 274    /// This method searches through all registered authentication options and returns the schemas
 275    /// that have a <see cref="ClaimPolicyConfig"/> containing a policy with the given name.
 276    /// </summary>
 277    /// <param name="policyName">The name of the policy to search for.</param>
 278    /// <returns>A list of schema names that own the specified policy.</returns>
 279    public IList<string> GetSchemesByPolicy(string policyName)
 280    {
 0281        ArgumentException.ThrowIfNullOrWhiteSpace(policyName);
 282
 0283        var result = new List<string>();
 0284        foreach (var kvp in _map)
 285        {
 0286            var options = kvp.Value;
 0287            ClaimPolicyConfig? cfg = null;
 288
 289            // Try explicit properties first
 0290            if (options is OidcOptions oidc && oidc.ClaimPolicy is { } oidcCfg)
 291            {
 0292                cfg = oidcCfg;
 293            }
 0294            else if (options is OAuth2Options oauth2 && oauth2.ClaimPolicy is { } oauth2Cfg)
 295            {
 0296                cfg = oauth2Cfg;
 297            }
 298            else
 299            {
 300                // Fallback: reflection search for ClaimPolicyConfig or ClaimPolicy
 0301                var prop = options.GetType().GetProperty("ClaimPolicyConfig") ?? options.GetType().GetProperty("ClaimPol
 0302                if (prop != null && typeof(ClaimPolicyConfig).IsAssignableFrom(prop.PropertyType))
 303                {
 0304                    cfg = (ClaimPolicyConfig?)prop.GetValue(options);
 305                }
 306            }
 307
 0308            if (cfg?.Policies != null && cfg.Policies.ContainsKey(policyName))
 309            {
 0310                result.Add(kvp.Key.schema);
 311            }
 312        }
 0313        return result;
 314    }
 315
 316    // ---------- Internal tuple comparer (case-insensitive support) ----------
 317
 486318    private sealed class TupleComparer(StringComparer cmp) : IEqualityComparer<(string schema, AuthenticationType type)>
 319    {
 486320        private readonly StringComparer _cmp = cmp;
 321
 322        public bool Equals((string schema, AuthenticationType type) x, (string schema, AuthenticationType type) y)
 0323            => _cmp.Equals(x.schema, y.schema) && x.type == y.type;
 324        public int GetHashCode((string schema, AuthenticationType type) obj)
 20325            => HashCode.Combine(_cmp.GetHashCode(obj.schema), obj.type.GetHashCode());
 326    }
 327}