Kein Modul, sondern die drei Software-Voraussetzungen fuer 1.0.0: Auskunft nach Art. 15 DSGVO, ein geprueftes Sicherungskonzept und eine Pipeline. Das Auskunftsrecht war ein Versprechen ohne Umsetzung: `person.export` stand seit Phase 0 im Katalog und war dem Kommandanten zugewiesen, implementiert war nichts. Ohne Auskunftsseite ist ein Loeschkonzept nicht abnehmbar. Jetzt sammelt FLORIAN dreizehn Abschnitte ueber eine Person, mit dem Zweck ueber jedem - Art. 15 verlangt nicht nur die Daten, sondern auch Zwecke und Rechte, und eine blosse Liste erfuellt die Pflicht nicht. Ein Durchgang liefert Anzeige, PDF und JSON nach Art. 20. Die Trennlinie ist "ueber mich" gegen "von mir": was ueber die Person gespeichert ist, steht vollstaendig drin, auch das Unangenehme. Was sie getan hat, steht als Zahl drin - die Pruefprotokolle gehoeren zum Nachweisbestand der Wehr, und vollstaendig ausgeliefert kaeme jeder ueber eine Selbstauskunft an die Geraeteakten. Die Rueckspielprobe hat sich sofort bezahlt gemacht: der erste Versuch lief sauber durch und hatte dabei saemtliche Unveraenderlichkeitsgarantien der Datenbank entfernt. Die Vorgabeprivilegierung aus dem Init-Skript gibt jeder von pg_restore neu angelegten Tabelle wieder alle Rechte, und GRANT kann nur hinzufuegen. Die Anwendungsrolle durfte danach das Audit-Log aendern, Wiki-Fassungen loeschen, Kiosk-Vorgaenge umschreiben - und nichts haette darauf hingewiesen. Gefunden haben es vier Datenbanktests unmittelbar nach der Probe, also genau die Probe, wegen der 1.0.0 sie verlangt. Die Pipeline ist geschrieben, aber NICHT erprobt: in Gitea ist kein act_runner registriert. Das steht so im README und im Devlog, damit sich niemand auf eine Pruefung verlaesst, die nicht stattfindet. 601 Unit-Tests gruen; Sicherung und Wiederherstellung einmal vollstaendig durchgefuehrt und die Tests danach erneut gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
/**
|
|
* Die Sperren der Anwendungsrolle — nach einer Wiederherstellung.
|
|
*
|
|
* Hintergrund, und er ist unerwartet:
|
|
*
|
|
* Beim ersten Start richtet `docker/postgres/10-init-app-role.sh` eine
|
|
* Vorgabeprivilegierung ein — jede künftig angelegte Tabelle bekommt
|
|
* automatisch alle Rechte für `florian_app`. Das ist im Betrieb genau richtig:
|
|
* eine neue Migration soll nicht daran scheitern, dass jemand vergessen hat,
|
|
* Rechte zu vergeben.
|
|
*
|
|
* `pg_restore` legt die Tabellen aber ebenfalls neu an. Die Vorgabe greift
|
|
* also auch dort — und die `GRANT`-Anweisungen aus dem Dump können nur
|
|
* hinzufügen, nie wegnehmen. Nach einer Wiederherstellung stünde die
|
|
* Anwendungsrolle damit wieder mit allen Rechten da: sie dürfte das Audit-Log
|
|
* ändern, Wiki-Fassungen löschen, Kiosk-Vorgänge umschreiben.
|
|
*
|
|
* Nichts an der Anwendung würde darauf hinweisen. Die Unveränderlichkeit, auf
|
|
* der jeder Nachweis gegenüber der Unfallkasse beruht, wäre stillschweigend
|
|
* weg — bis jemand danach fragt.
|
|
*
|
|
* Gefunden haben das die Datenbanktests nach der ersten Rückspielprobe.
|
|
* Deshalb gibt es diese Datei: **eine** Liste, aus der sowohl das
|
|
* Wiederherstellen als auch das Prüfen der Sperren entsteht. Zwei Listen
|
|
* wären zwei Wahrheiten.
|
|
*/
|
|
|
|
/** Tabelle → Rechte, die `florian_app` NICHT haben darf, mit Begründung. */
|
|
export const SPERREN: Array<{ tabelle: string; rechte: string[]; grund: string }> = [
|
|
{
|
|
tabelle: "berechtigung",
|
|
rechte: ["INSERT", "UPDATE", "DELETE"],
|
|
grund: "systemweiter Rechtekatalog — nur lesbar",
|
|
},
|
|
{
|
|
tabelle: "audit_log",
|
|
rechte: ["UPDATE", "DELETE"],
|
|
grund: "append-only; das Löschen geht nur im Aufbewahrungslauf",
|
|
},
|
|
{
|
|
tabelle: "kiosk_vorgang",
|
|
rechte: ["UPDATE", "DELETE"],
|
|
grund: "übermittelte Terminalvorgänge sind unveränderlich",
|
|
},
|
|
{
|
|
tabelle: "druckmessung",
|
|
rechte: ["UPDATE", "DELETE"],
|
|
grund: "Atemschutzüberwachung — Druckwerte werden nie korrigiert",
|
|
},
|
|
{
|
|
tabelle: "atemschutz_ereignis",
|
|
rechte: ["UPDATE", "DELETE"],
|
|
grund: "wie oben: der Verlauf einer Überwachung bleibt, wie er war",
|
|
},
|
|
{
|
|
tabelle: "benachrichtigung",
|
|
rechte: ["DELETE"],
|
|
grund: "was zugestellt wurde, bleibt nachweisbar",
|
|
},
|
|
{
|
|
tabelle: "ki_vorgang",
|
|
rechte: ["DELETE"],
|
|
grund: "das KI-Protokoll belegt, was die Maschine getan hat",
|
|
},
|
|
{
|
|
tabelle: "wikiversion",
|
|
rechte: ["UPDATE", "DELETE"],
|
|
grund: "jede Fassung einer Wiki-Seite bleibt erhalten",
|
|
},
|
|
{
|
|
tabelle: "kassenabschluss",
|
|
rechte: ["DELETE"],
|
|
grund: "ein festgeschriebener Abschluss lässt sich nicht wegräumen",
|
|
},
|
|
{
|
|
tabelle: "benutzer",
|
|
rechte: ["INSERT", "DELETE"],
|
|
grund: "Konten entstehen und vergehen über den Systemzugang",
|
|
},
|
|
];
|
|
|
|
/** Die SQL-Anweisungen, die die Sperren wiederherstellen. */
|
|
export function sperrenSql(): string[] {
|
|
return SPERREN.map(
|
|
({ tabelle, rechte }) => `REVOKE ${rechte.join(", ")} ON "${tabelle}" FROM florian_app`,
|
|
);
|
|
}
|
|
|
|
/** Alle Kombinationen, die nach der Wiederherstellung fehlen müssen. */
|
|
export function verboteneRechte(): Array<{ tabelle: string; recht: string }> {
|
|
return SPERREN.flatMap(({ tabelle, rechte }) =>
|
|
rechte.map((recht) => ({ tabelle, recht })),
|
|
);
|
|
}
|