< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Domain.Entities.GoogleIdTokenUse
Assembly: ArturRios.Heimdall.Domain
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Domain/ArturRios.Heimdall.Domain/Entities/GoogleIdTokenUse.cs
Line coverage
100%
Covered lines: 6
Uncovered lines: 0
Coverable lines: 6
Total lines: 59
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage
100%
Covered methods: 3
Fully covered methods: 3
Total methods: 3
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_ExpiresAt()100%11100%
KeyOf(...)100%22100%
SignedPartOf(...)100%22100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Domain/ArturRios.Heimdall.Domain/Entities/GoogleIdTokenUse.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3using ArturRios.Data.Relational.Core.Entities;
 4
 5namespace ArturRios.Heimdall.Domain.Entities;
 6
 7/// <summary>
 8///     A Google ID token the system has accepted (FR-GO-21), kept so that the same token is refused if
 9///     it is presented again. <see cref="Entity{TKey}.Id" /> is the token's key — see
 10///     <see cref="KeyOf" /> — and nothing else about the token, or about who presented it, is stored.
 11/// </summary>
 12/// <remarks>
 13///     Google signs a token for an hour, and until this table existed any copy of one — read from a
 14///     log, a proxy or a client's storage — could sign its holder in for that long. Recording the first
 15///     use, in the same statement that checks for an earlier one, makes every later presentation an
 16///     invalid token. The row is needed only until the token expires and so could no longer be accepted
 17///     anyway; it is purged with the single-use tokens after the same grace (NFR-19).
 18/// </remarks>
 19public class GoogleIdTokenUse : Entity<string>
 20{
 21    /// <summary>The token's own expiry, its <c>exp</c> claim, which the purge counts from.</summary>
 1022    public DateTime ExpiresAt { get; set; }
 23
 24    /// <summary>
 25    ///     The key a token is recorded under: a lower-case hex SHA-256 digest of its <c>jti</c> claim,
 26    ///     which Google sets on the tokens it issues, or, when it carries none, of its signed part —
 27    ///     the <c>header.payload</c> before the last dot, surrounding whitespace aside.
 28    /// </summary>
 29    /// <remarks>
 30    ///     <para>
 31    ///         The signed part rather than the whole token, because the signature segment can be spelled
 32    ///         more than one way. Its base64url text has unused trailing bits — four in an RS256
 33    ///         signature's 342 characters — which a lenient decoder ignores, so one token can verify
 34    ///         under several spellings, and a key taken over the whole text would count each as a new
 35    ///         token.
 36    ///         The signed part cannot be respelled without breaking the signature, which covers its
 37    ///         exact text, so every spelling of one token has one key.
 38    ///     </para>
 39    ///     <para>
 40    ///         A digest rather than the value: the key is what the row is looked up by, never what it is
 41    ///         read for, and a stored token, unlike its digest, would be a credential until it expired.
 42    ///     </para>
 43    /// </remarks>
 44    public static string KeyOf(string? tokenId, string idToken) =>
 10245        Convert.ToHexStringLower(SHA256.HashData(
 10246            Encoding.UTF8.GetBytes(string.IsNullOrEmpty(tokenId) ? SignedPartOf(idToken) : tokenId)));
 47
 48    /// <summary>
 49    ///     The part of a compact JWS its signature covers: everything before the last dot, or the whole
 50    ///     trimmed value when there is no dot.
 51    /// </summary>
 52    private static string SignedPartOf(string idToken)
 53    {
 2054        var token = idToken.Trim();
 2055        var signatureDot = token.LastIndexOf('.');
 56
 2057        return signatureDot < 0 ? token : token[..signatureDot];
 58    }
 59}