< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Security.AuthRateLimitPartition
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Security/AuthRateLimitPartition.cs
Line coverage
100%
Covered lines: 9
Uncovered lines: 0
Coverable lines: 9
Total lines: 54
Line coverage: 100%
Branch coverage
92%
Covered branches: 13
Total branches: 14
Branch coverage: 92.8%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
KeyFor(...)100%1010100%
TryReadSingle(...)75%44100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Security/AuthRateLimitPartition.cs

#LineLine coverage
 1using System.Net;
 2
 3namespace ArturRios.Heimdall.WebApi.Security;
 4
 5/// <summary>
 6///     Chooses the rate-limit partition an anonymous-auth request is counted in: the address of the
 7///     connection, unless a trusted client system names the address of the end user it is acting for.
 8/// </summary>
 9/// <remarks>
 10///     The decision is a pure function of the request and <see cref="TrustedCallerOptions" /> so it can
 11///     be tested without a host. It never fails a request: a wrong or missing key, an unusable address,
 12///     or no configured keys all fall back to the connection address, so the headers cannot be used to
 13///     probe for the keys by their effect on anything but the caller's own bucket. A named address is
 14///     counted under <see cref="NamedAddressPrefix" /> so it can never be the bucket of a real
 15///     connection from that address. Neither header value is logged.
 16/// </remarks>
 17public static class AuthRateLimitPartition
 18{
 19    /// <summary>Header carrying one of the configured <see cref="TrustedCallerOptions" /> keys.</summary>
 20    public const string CallerKeyHeader = "X-Heimdall-Caller-Key";
 21
 22    /// <summary>Header carrying the address of the end user the trusted caller is acting for.</summary>
 23    public const string ClientAddressHeader = "X-Heimdall-Client-Address";
 24
 25    /// <summary>
 26    ///     Prefix of every partition a trusted caller names. A direct connection keeps the unprefixed
 27    ///     key, so the two never share a bucket.
 28    /// </summary>
 29    public const string NamedAddressPrefix = "caller:";
 30
 31    public static string KeyFor(HttpContext context, TrustedCallerOptions callers)
 32    {
 77333        if (callers.Configured &&
 77334            TryReadSingle(context.Request.Headers[CallerKeyHeader], out var key) &&
 77335            callers.Matches(key) &&
 77336            TryReadSingle(context.Request.Headers[ClientAddressHeader], out var address) &&
 77337            IPAddress.TryParse(address, out var clientAddress))
 38        {
 39            // Its own namespace: a named address is counted apart from a direct connection from the
 40            // same address, so a key holder cannot spend a real caller's budget by naming them.
 13841            return NamedAddressPrefix + Startup.RateLimitPartitionKey(clientAddress);
 42        }
 43
 63544        return Startup.RateLimitPartitionKey(context.Connection.RemoteIpAddress);
 45    }
 46
 47    // A header sent twice is ambiguous, and the safe reading of an ambiguous credential is none.
 48    private static bool TryReadSingle(Microsoft.Extensions.Primitives.StringValues values, out string value)
 49    {
 91650        value = values.Count == 1 ? values[0] ?? string.Empty : string.Empty;
 51
 91652        return value.Length > 0;
 53    }
 54}