| | | 1 | | using ArturRios.Heimdall.Domain.Entities; |
| | | 2 | | using ArturRios.Heimdall.Domain.Enums; |
| | | 3 | | |
| | | 4 | | namespace ArturRios.Heimdall.Command.Services; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Stamps the lawful basis and the privacy notice version onto an identity as it is created |
| | | 8 | | /// (NFR-23). |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// <para> |
| | | 12 | | /// GDPR Art. 5(2) and LGPD Art. 8 §2 put the burden of demonstrating the basis on the |
| | | 13 | | /// controller. Demonstrating it means being able to say, of a particular person, what the |
| | | 14 | | /// basis was and what they were told — which a system that derives the answer at read time |
| | | 15 | | /// cannot do, because it answers what the basis is *now*. |
| | | 16 | | /// </para> |
| | | 17 | | /// <para> |
| | | 18 | | /// The basis comes from the scope where the scope declares one, because the scope is the |
| | | 19 | | /// tenant boundary and the tenant is usually the controller for the identities inside it. |
| | | 20 | | /// Where it does not, contract performance applies: the identity exists to run a service the |
| | | 21 | | /// person is party to, which is what every other row of the record already says. |
| | | 22 | | /// </para> |
| | | 23 | | /// </remarks> |
| | | 24 | | public static class LegalBasisRecorder |
| | | 25 | | { |
| | | 26 | | /// <summary> |
| | | 27 | | /// The version of the privacy notice this build ships. Bumped whenever the notice changes |
| | | 28 | | /// materially, so an identity records the version actually in force when it was created. |
| | | 29 | | /// </summary> |
| | | 30 | | /// <remarks> |
| | | 31 | | /// Must match the version header of <c>docs/requirements/Privacy Notice.md</c>. |
| | | 32 | | /// <c>PrivacyNoticeVersionTests</c> fails the build if the two drift, because a recorded |
| | | 33 | | /// version that names a document nobody can produce is worse than no record: it looks like |
| | | 34 | | /// evidence of what somebody was told while pointing at the wrong text. |
| | | 35 | | /// </remarks> |
| | | 36 | | public const string CurrentPrivacyNoticeVersion = "1.2"; |
| | | 37 | | |
| | | 38 | | /// <summary>The basis applied when a scope declares none, and for identities outside any scope.</summary> |
| | | 39 | | public const LegalBases DefaultBasis = LegalBases.ContractPerformance; |
| | | 40 | | |
| | | 41 | | /// <summary>Records the basis and notice version on a person being created.</summary> |
| | | 42 | | public static void Record(Person person, Scope? scope, DateTime at) |
| | | 43 | | { |
| | 37 | 44 | | person.LegalBasis = (int)Resolve(scope); |
| | 37 | 45 | | person.PrivacyNoticeVersion = CurrentPrivacyNoticeVersion; |
| | 37 | 46 | | person.BasisRecordedAt = at; |
| | 37 | 47 | | } |
| | | 48 | | |
| | | 49 | | /// <summary>Records the basis and notice version on a Google User being created.</summary> |
| | | 50 | | public static void Record(GoogleUser googleUser, Scope? scope, DateTime at) |
| | | 51 | | { |
| | 20 | 52 | | googleUser.LegalBasis = (int)Resolve(scope); |
| | 20 | 53 | | googleUser.PrivacyNoticeVersion = CurrentPrivacyNoticeVersion; |
| | 20 | 54 | | googleUser.BasisRecordedAt = at; |
| | 20 | 55 | | } |
| | | 56 | | |
| | | 57 | | /// <summary> |
| | | 58 | | /// The basis a scope declares, or the default. <see cref="LegalBases.Consent" /> is never |
| | | 59 | | /// returned even if somehow stored: it needs a withdrawal path that does not exist, and |
| | | 60 | | /// honouring a stored value here would let a deployment record a basis it could not honour. |
| | | 61 | | /// The validators refuse it on the way in; this refuses it on the way out too, because a |
| | | 62 | | /// guard that exists in one direction only is one migration away from being bypassed. |
| | | 63 | | /// </summary> |
| | | 64 | | public static LegalBases Resolve(Scope? scope) |
| | | 65 | | { |
| | 57 | 66 | | if (scope?.DefaultLegalBasis is not { } declared) |
| | | 67 | | { |
| | 52 | 68 | | return DefaultBasis; |
| | | 69 | | } |
| | | 70 | | |
| | 5 | 71 | | var basis = (LegalBases)declared; |
| | | 72 | | |
| | 5 | 73 | | return basis is LegalBases.Consent or LegalBases.Unrecorded || !Enum.IsDefined(basis) |
| | 5 | 74 | | ? DefaultBasis |
| | 5 | 75 | | : basis; |
| | | 76 | | } |
| | | 77 | | } |