| | | 1 | | using ArturRios.Data.Relational.Core.Interfaces; |
| | | 2 | | using ArturRios.Heimdall.Domain.Entities; |
| | | 3 | | using ArturRios.Heimdall.Domain.Enums; |
| | | 4 | | using Microsoft.EntityFrameworkCore; |
| | | 5 | | |
| | | 6 | | namespace ArturRios.Heimdall.Command.Services; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// NFR-32: whether taking a System Admin out — deleting them (UC-09, UC-10) or suspending them for erasure |
| | | 10 | | /// (UC-42 and the anonymisation pass's retry) — would leave the system without an active System Admin. |
| | | 11 | | /// </summary> |
| | | 12 | | /// <remarks> |
| | | 13 | | /// <para> |
| | | 14 | | /// <b>Why.</b> Only a System Admin creates scopes, changes roles and reaches the retention tooling, and |
| | | 15 | | /// the seeder re-creates the master user only on the next start, and only when its variables are set. |
| | | 16 | | /// An administrator deleted or erased by mistake, or two deleting each other at once, would otherwise |
| | | 17 | | /// lock everyone out until then (#203). |
| | | 18 | | /// </para> |
| | | 19 | | /// <para> |
| | | 20 | | /// <b>Who counts.</b> An active System Admin (<see cref="Person.Active" />): not logically deleted, no |
| | | 21 | | /// erasure requested, processing not restricted — the same definition NFR-12 uses for owners. A |
| | | 22 | | /// restriction is the subject's right and is never refused (UC-44), so the rule is that no |
| | | 23 | | /// administrator's <em>removal</em> may leave none, not that one always exists. |
| | | 24 | | /// </para> |
| | | 25 | | /// <para> |
| | | 26 | | /// <b>Asked under the System Admin lock.</b> Every caller asks inside its transaction, once |
| | | 27 | | /// <c>IInvariantLocks</c> holds the System Admin set, and decides whether the guard applies from the |
| | | 28 | | /// person as read under it (<see cref="LockedPerson.IsLiveSystemAdmin" />), so two administrators |
| | | 29 | | /// removing each other at once run one after the other, and the second is refused. |
| | | 30 | | /// </para> |
| | | 31 | | /// </remarks> |
| | | 32 | | public static class LastSystemAdminGuard |
| | | 33 | | { |
| | | 34 | | /// <summary> |
| | | 35 | | /// Whether no System Admin but the person <paramref name="personId" /> is active, so taking them out |
| | | 36 | | /// would leave the system without one. |
| | | 37 | | /// </summary> |
| | | 38 | | public static async Task<bool> WouldLeaveNoneAsync( |
| | | 39 | | long personId, IAsyncReadOnlyRepository<Person, long> personReader) => |
| | 12 | 40 | | !await personReader.Query() |
| | 12 | 41 | | .Where(Person.Active) |
| | 12 | 42 | | .AnyAsync(other => other.Id != personId && other.RoleId == (long)Roles.SystemAdmin); |
| | | 43 | | } |