< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Services.DataExportDisclosure
Assembly: ArturRios.Heimdall.Command
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Application/ArturRios.Heimdall.Command/Services/DataExportDisclosure.cs
Line coverage
100%
Covered lines: 72
Uncovered lines: 0
Coverable lines: 72
Total lines: 134
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 4
Fully covered methods: 4
Total methods: 4
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Recipients()100%11100%
get_Sources()100%11100%
get_Retention()100%11100%
get_Withheld()100%11100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Application/ArturRios.Heimdall.Command/Services/DataExportDisclosure.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Output;
 2
 3namespace ArturRios.Heimdall.Command.Services;
 4
 5/// <summary>
 6///     The context Art. 15 requires an export to carry alongside the data: who else receives it,
 7///     how long each category is kept, and what is held but not reproduced.
 8/// </summary>
 9/// <remarks>
 10///     <para>
 11///         This duplicates the Data Protection Document and the Data Retention Schedule, which is a
 12///         real cost and is accepted deliberately: the subject receives one document, and a copy
 13///         that told them to go and read a repository would not be the "concise, transparent,
 14///         intelligible" form Art. 12(1) asks for.
 15///     </para>
 16///     <para>
 17///         The duplication is guarded rather than trusted. <c>DataExportDisclosureTests</c> asserts
 18///         that every recipient named here appears in the record of processing, so the two cannot
 19///         drift apart silently — which is the only failure mode duplication of this kind has.
 20///     </para>
 21/// </remarks>
 22public static class DataExportDisclosure
 23{
 24    /// <summary>Most audit entries a single export reproduces.</summary>
 25    /// <remarks>
 26    ///     A bound is needed — an old, busy account could otherwise produce a response measured in
 27    ///     megabytes — but a silently truncated copy is not the copy Art. 15 asks for, so reaching
 28    ///     it is reported on the document itself.
 29    /// </remarks>
 30    public const int MaximumAuditEntries = 5000;
 31
 32    /// <summary>Who else the subject's data reaches (GDPR Art. 15(1)(c), LGPD Art. 18 VII).</summary>
 33    public static IEnumerable<DataExportRecipient> Recipients =>
 2634    [
 2635        new()
 2636        {
 2637            Name = "Mailgun",
 2638            Purpose = "Delivering verification, password reset and two-factor emails",
 2639            Location = "United States"
 2640        },
 2641        new()
 2642        {
 2643            Name = "The organisation whose system this account belongs to",
 2644            Purpose = "Administering the identities within its own scope",
 2645            Location = "Varies by tenant"
 2646        },
 2647        new()
 2648        {
 2649            Name = "Hosting provider",
 2650            Purpose = "Storing the database",
 2651            Location = "Brazil"
 2652        }
 2653    ];
 54
 55    /// <summary>
 56    ///     Where data that did not come from the subject came from (GDPR Art. 14(2)(f) and Art.
 57    ///     15(1)(g), LGPD Art. 9).
 58    /// </summary>
 59    /// <remarks>
 60    ///     Google was listed as a <em>recipient</em> until the transfer assessment was corrected,
 61    ///     and it is not one: the ID token is validated offline against cached public certificates
 62    ///     and nothing about the person is ever sent to Google. It belongs here, where the direction
 63    ///     is right — and telling the subject where their data came from is an obligation in its own
 64    ///     right when it did not come from them.
 65    /// </remarks>
 66    public static IEnumerable<DataExportSource> Sources =>
 2667    [
 2668        new()
 2669        {
 2670            Name = "Google",
 2671            Provides =
 2672                "Your name, email address, profile picture and Google account identifier, if you "
 2673                + "signed in with Google. They come from the token Google issues when you sign in.",
 2674            DataSentThere =
 2675                "Nothing. The token is checked against Google's published certificates on this "
 2676                + "server; it is never sent to Google."
 2677        }
 2678    ];
 79
 80    /// <summary>How long each category is kept (GDPR Art. 15(1)(d)).</summary>
 81    public static IEnumerable<DataExportRetention> Retention =>
 2682    [
 2683        new() { Category = "Account", Period = "While the account exists" },
 2684        new() { Category = "Account, after you ask to be erased", Period = "Anonymised within 30 days" },
 2685        new()
 2686        {
 2687            Category = "Account, after an administrator deletes it",
 2688            Period = "Anonymised after 90 days, so a mistake can be undone"
 2689        },
 2690        new() { Category = "Password reset and verification links", Period = "A week after they expire" },
 2691        new()
 2692        {
 2693            Category = "Records of actions on the account",
 2694            Period = "18 months, then stripped of anything identifying you"
 2695        },
 2696        new() { Category = "Application logs", Period = "12 months" }
 2697    ];
 98
 99    /// <summary>
 100    ///     Values held about the subject that this document names but does not reproduce.
 101    /// </summary>
 102    /// <remarks>
 103    ///     Art. 15(4): the right to a copy must not adversely affect the rights of others — and
 104    ///     reproducing a password hash or a TOTP secret would hand whoever holds the token the
 105    ///     material to attack the account offline, which adversely affects the subject most of all.
 106    ///     Naming them is the compromise: the subject learns what exists without the export becoming
 107    ///     the best thing to steal.
 108    /// </remarks>
 109    public static IEnumerable<DataExportWithheld> Withheld =>
 26110    [
 26111        new()
 26112        {
 26113            Value = "Password hash and salt",
 26114            Reason = "Reproducing them would let anyone holding this document attack the password offline. "
 26115                     + "The password itself is not stored and cannot be recovered from what is."
 26116        },
 26117        new()
 26118        {
 26119            Value = "Two-factor secret",
 26120            Reason = "Encrypted at rest and never returned after the response that first generated it. "
 26121                     + "Reproducing it would defeat the second factor entirely."
 26122        },
 26123        new()
 26124        {
 26125            Value = "Two-factor recovery codes",
 26126            Reason = "Stored only as hashes, so the codes themselves no longer exist to reproduce."
 26127        },
 26128        new()
 26129        {
 26130            Value = "Password reset and email verification tokens",
 26131            Reason = "Stored only as digests, and a live one would let this document be used to take over the account."
 26132        }
 26133    ];
 134}