docs(quick-260911-nke): Aktenstand kohaerent — Regelschluss Benutzerdimension, Nachtraege, Ledger
- Neuer Abschnitt "Regelschluss Benutzerdimension (Etappe 3b, 260911-nke)"
in der Kritikschrift mit b1 (woertliche Werkzeugausgabe + pg_policies-Liste),
b2 (Signaltabelle beide Fehlerrichtungen), b3 (NotFound-statt-Forbidden
je Methode), b4/b5 (bewusst nicht geloest/angefasst). Zehn datierte
Nachtraege an allen Stellen, die zuvor "keine Benutzerdimension" als
Stand beschrieben (t1/t4/r4/w1/w4/k1/k4/f1/f4/Abschluss) — historische
Messung bleibt lesbar.
- Klassifikation: drei Bestandsaufnahme-Zeilen (calendarSource,
widgetInstance, favoriteLink) mit Zusatz "Benutzerdimension seit
20260911120000 (260911-nke)"; neuer Punkt "Aufgelöst (260911-nke)" im
Abschnitt "Was diese Etappe NICHT entscheidet"; neuer Stand-Absatz —
Paarzahl (72) und Klassen-Verteilung bleiben unveraendert.
- Betriebsanleitung: `forTenant(prisma, tenantId, userId?)` und die zehn/
vier-Tabellen-Aufteilung nachgezogen.
- Datenbankrolle: neuer Absatz zu `app.current_user`/`current_user_id()`
neben `app.current_tenant`; SECURITY-DEFINER-Kopfkommentare unangetastet.
- Auftrag: 3b als erledigt markiert (Migrationsname, sechs statt drei
Umkehrungen, Endzahlen); 3a/3c unveraendert.
- WINDOWS.md: neuer Eintrag #34 (open, deviation) fuer die bewusst offene
Flanke — Aufrufer ohne userId sieht den ganzen Mandanten, kein Waechter
gebaut.
- Baseline: 1020/62 Tests, Typpruefung sauber, Werkzeug 203/203 bestanden.
Erlaubnisliste gegen 8829999 eingehalten, schema.prisma/Compose/.env/3a
unveraendert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -460,6 +460,14 @@ Bereich brauchte:
|
||||
anwendungsseitige `userId`-Filterung, die alle fünf umzustellenden
|
||||
Dienste bereits führen, bleibt deshalb der einzige Schutz gegen
|
||||
Quer-Lesen zwischen Nutzern und wird bei der Umstellung NICHT entfernt.
|
||||
|
||||
**Nachtrag (260911-nke):** seit Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
trägt die Regel auf `TenderSavedSearch` die Benutzerdimension — die alte
|
||||
Messung bleibt unter dem Namen `tendersavedsearch-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten`
|
||||
als die gewollte Eigenschaft für Admin/Hintergrunddienst bestehen, die
|
||||
Umkehrung `tendersavedsearch-benutzer-a-sieht-kollegen-nicht-gebunden`
|
||||
misst MIT Benutzer und erwartet das Gegenteil. Siehe Abschnitt
|
||||
"Regelschluss Benutzerdimension (Etappe 3b, 260911-nke)" unten.
|
||||
- `tenderrssfeed-plattformzeile-unter-jedem-mandanten-unsichtbar` — die
|
||||
plattformweite RSS-Zeile (`userId`/`tenantId` beide `NULL`, wie der
|
||||
geseedete `service.bund.de`-Feed) ist unter TENANT-A UND TENANT-B
|
||||
@@ -654,6 +662,15 @@ Warnung wäre Dauerlärm und verlöre ihr Signal.
|
||||
Zuständigkeitsfrage im Umfeld von WINDOWS #19, kein Auftrag dieser
|
||||
Aufgabe.
|
||||
|
||||
**Nachtrag (260911-nke):** die Benutzerdimension der `TenderRssFeedSource`-Regeln
|
||||
ist seit Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
Teil der vier befehlsgetrennten Regeln (Lesen schließt gemeinsame/eigene
|
||||
Zeilen ein, Schreiben verlangt weiterhin `userId = current_user_id()`),
|
||||
gemessen in `tenderrssfeed-gemeinsame-zeile-*`. Befund G (Admin eines
|
||||
beliebigen Mandanten entfernt eine plattformweite Zeile) bleibt
|
||||
unverändert — WINDOWS #24 hält das offen, siehe Abschnitt "Regelschluss
|
||||
Benutzerdimension (Etappe 3b, 260911-nke)" unten.
|
||||
|
||||
### (t5) Was dieser Durchlauf bewusst nicht anfasst
|
||||
|
||||
Die zwölf Paare des plattformweiten Ausschreibungskatalogs (D-03,
|
||||
@@ -1749,6 +1766,13 @@ Tabellen, inklusive der NEUEN Stelle aus Befund F:
|
||||
eine Benutzerdimension auf Datenbankebene durchzusetzen, ohne eine solche
|
||||
Variable erst einzuführen. Nicht gebaut in diesem Durchlauf — die
|
||||
anwendungsseitige `userId`-Filterung bleibt der einzige Schutz.
|
||||
|
||||
**Nachtrag (260911-nke): ÜBERHOLT — die zweite Sitzungsvariable ist
|
||||
gebaut.** Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
führt `app.current_user`/`current_user_id()` ein und trägt die
|
||||
Benutzerdimension in die Regeln der zehn persönlichen Tabellen, darunter
|
||||
`TenderSavedSearch`. Siehe Abschnitt "Regelschluss Benutzerdimension
|
||||
(Etappe 3b, 260911-nke)" unten.
|
||||
- **Die plattformweite Eindeutigkeit von Anmeldename und Adresse (WINDOWS
|
||||
#22)** — unverändert, nicht Gegenstand dieses Plans.
|
||||
|
||||
@@ -1809,6 +1833,21 @@ searchprovider-gebundenes-einfuegen-ohne-mandant-abgelehnt: bestanden — gebund
|
||||
Alle 87 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
**Nachtrag (260911-nke):** die drei oben zitierten Ausgabezeilen
|
||||
(`dashboardlayout-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`,
|
||||
`widgetinstance-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`,
|
||||
`searchprovider-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`)
|
||||
bleiben als historische Messung stehen — sie sind seit Migration
|
||||
`20260911120000_rls_user_dimension_personal_tables` UMGEDREHT, nicht
|
||||
gelöscht: die alte Messung lebt unter den neuen Namen
|
||||
`dashboardlayout-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten`,
|
||||
`widgetinstance-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten` und
|
||||
`searchprovider-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten`
|
||||
weiter (jetzt als gewollte Eigenschaft für Admin/Hintergrunddienst), dazu je
|
||||
eine neue Umkehrung `<tabelle>-benutzer-a-sieht-kollegen-nicht-gebunden` MIT
|
||||
Benutzer. Siehe Abschnitt "Regelschluss Benutzerdimension (Etappe 3b,
|
||||
260911-nke)" unten.
|
||||
|
||||
Dreizehn neue Prüfungen, nicht zwölf wie in der Aufzählung des Plans
|
||||
namentlich vorgezeichnet — die dreizehnte
|
||||
(`widgetinstance-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`)
|
||||
@@ -1984,6 +2023,15 @@ Abweichung von seiner eigenen Auswahl liest, bleibt das unbemerkt.
|
||||
gebunden wie #18 — er wird erst
|
||||
nach dem Scharfschalten beobachtbar.
|
||||
|
||||
**Nachtrag (260911-nke):** die Benutzerdimension der Regeln auf
|
||||
`DashboardLayout`/`WidgetInstance`/`SearchProvider` (Lesen UND Schreiben
|
||||
je Kollegenzeile) ist seit Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
geschlossen — siehe (w1) oben und Abschnitt "Regelschluss
|
||||
Benutzerdimension (Etappe 3b, 260911-nke)" unten. Die plattformweite
|
||||
Eindeutigkeit von `DashboardLayout.userId` (Befund K, dieser Punkt) ist
|
||||
davon UNBERÜHRT und bleibt für Etappe 3 vorgemerkt — 3b ändert keine
|
||||
Unique-Constraints.
|
||||
|
||||
### (w5) Was dieser Durchlauf bewusst nicht anfasst
|
||||
|
||||
- **Das Frontend** — geprüft (Befund I/J, (w3) oben) und bewusst gelassen,
|
||||
@@ -2086,6 +2134,16 @@ calendarsource-generierter-client-gebundenes-anlegen-eigener-mandant-gelingt: be
|
||||
Alle 101 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
**Nachtrag (260911-nke):** `calendarsource-fremder-nutzer-desselben-mandanten-gebunden-sichtbar`
|
||||
ist seit Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
UMGEDREHT — die alte Messung lebt unter dem Namen
|
||||
`calendarsource-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten`
|
||||
weiter (gewollte Eigenschaft ohne Benutzer), die neue Umkehrung
|
||||
`calendarsource-benutzer-a-sieht-kollegen-nicht-gebunden` misst MIT
|
||||
Benutzer und bestätigt, dass `encryptedPassword` eines Kollegen jetzt NICHT
|
||||
mehr lesbar ist. Siehe Abschnitt "Regelschluss Benutzerdimension (Etappe
|
||||
3b, 260911-nke)" unten.
|
||||
|
||||
Dreizehn neue Prüfungen (101 = 88 + 13), nicht zwölf wie in der Aufzählung
|
||||
des Plans namentlich vorgezeichnet — die dreizehnte
|
||||
(`calendarsource-regelstand-eindeutig`) wurde ergänzt, weil sie die
|
||||
@@ -2235,6 +2293,14 @@ Browsers und im API-Log, an keiner Stelle in der Benutzeroberfläche.
|
||||
aufnimmt (`CalendarSource` steht dort ausdrücklich in der Liste). Bis
|
||||
dahin bleiben der `userId`-Filter in `getSources`/`fetchAndCacheEvents`
|
||||
und die drei Besitzprüfungen der EINZIGE Schutz.
|
||||
|
||||
**Nachtrag (260911-nke): ÜBERHOLT — die Benutzerdimension ist in der
|
||||
Regel.** Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
trägt `current_user_id() IS NULL OR "userId" = current_user_id()` in die
|
||||
`CalendarSource`-Regel; `calendarsource-benutzer-a-sieht-kollegen-nicht-gebunden`
|
||||
bestätigt, dass die verschlüsselten Zugangsdaten eines Kollegen jetzt
|
||||
NICHT mehr lesbar sind. Der `userId`-Filter und die Besitzprüfungen
|
||||
bleiben zusätzlich bestehen (zweites Netz, kein Ersatz).
|
||||
- **(d) 403 statt 404 als Existenzpreisgabe zwischen Kollegen (Befund D):**
|
||||
`updateSource`, `deleteSource` und `testConnection` werfen bei fremdem
|
||||
Besitz `ForbiddenException('Not your calendar source')` (403), bei
|
||||
@@ -2749,6 +2815,17 @@ favoritelink-gebundenes-anlegen-eigener-mandant-gelingt: bestanden — bound.fav
|
||||
Alle 137 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
**Nachtrag (260911-nke):** die Doppelaussage in
|
||||
`favoritelink-liste-generierter-client-gebunden-eigener-mandant-liefert-eigene-zeilen`
|
||||
ist GETRENNT — die Prüfung behält nur die erste Hälfte (die eigenen Zeilen
|
||||
kommen). Die zweite Hälfte (Kollege sichtbar) lebt jetzt als eigene Prüfung
|
||||
`favoritelink-ohne-benutzer-sieht-beide-nutzer-desselben-mandanten` (alte
|
||||
Messung, gewollte Eigenschaft ohne Benutzer) plus die Umkehrung
|
||||
`favoritelink-benutzer-a-sieht-kollegen-nicht-gebunden` (MIT Benutzer,
|
||||
Kollege NICHT mehr sichtbar) — seit Migration
|
||||
`20260911120000_rls_user_dimension_personal_tables`. Siehe Abschnitt
|
||||
"Regelschluss Benutzerdimension (Etappe 3b, 260911-nke)" unten.
|
||||
|
||||
Die tragende Belegzeile ist `favoritelink-liste-generierter-client-ungebunden-liefert-leere-liste`: der
|
||||
IDENTISCHE `findMany`, den `list` heute stellt, liefert UNGEBUNDEN `[]`,
|
||||
während die Wartungsrolle zwei Zeilen sieht — das ist der Wert, aus dem
|
||||
@@ -2817,6 +2894,14 @@ Scharfschalten neu entsteht.
|
||||
`userId`-Filterung bleibt bestehen und ist bis zur Etappe-3-Entscheidung
|
||||
(2) der einzige Schutz gegen Quer-Lesen zwischen Nutzern desselben
|
||||
Mandanten.
|
||||
|
||||
**Nachtrag (260911-nke): ÜBERHOLT — die Benutzerdimension ist in der
|
||||
Regel.** Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
trägt `current_user_id() IS NULL OR "userId" = current_user_id()` in die
|
||||
`FavoriteLink`-Regel; `favoritelink-benutzer-a-sieht-kollegen-nicht-gebunden`
|
||||
bestätigt, dass die Kollegenzeile über die `widgetId` jetzt NICHT mehr
|
||||
sichtbar ist. Die anwendungsseitige `userId`-Filterung bleibt zusätzlich
|
||||
bestehen (zweites Netz, kein Ersatz).
|
||||
- **(b) Das Frontend.** Die `list`-Kette aus (f3) wird nicht geändert;
|
||||
Ledger-Eintrag in Aufgabe 3.
|
||||
- **(c) Die Mandantenquelle — warum der dashboard-Präzedenzfall und nicht
|
||||
@@ -3029,6 +3114,118 @@ unverändert und steht nicht in der Erlaubnisliste.
|
||||
(lokal gibt es keinen `mailhog`); in der neuen Testdatei per
|
||||
`vi.mock('nodemailer')` ersetzt.
|
||||
|
||||
## Regelschluss Benutzerdimension (Etappe 3b, 260911-nke)
|
||||
|
||||
Reiner Datenbank- und Helfer-Umbau: Migration `20260911120000_rls_user_dimension_personal_tables`
|
||||
bringt die Funktion `current_user_id()` und die Benutzerdimension in die
|
||||
Regeln der zehn persönlichen Tabellen. `forTenant(prisma, tenantId, userId?)`
|
||||
bekommt einen optionalen dritten Parameter; 34 Nutzer-CRUD-Aufrufstellen in
|
||||
acht Diensten reichen ihn durch. Der Schalter bleibt AUS — nichts hiervon
|
||||
wirkt, bis Etappe 4 scharfschaltet.
|
||||
|
||||
### (b1) Die Messung
|
||||
|
||||
Wörtliche Werkzeugausgabe der drei Funktionsfälle:
|
||||
|
||||
```
|
||||
current-user-id-ungesetzt-ist-null: bestanden — current_user_id() ohne gesetzte Variable=null
|
||||
current-user-id-leer-ist-null: bestanden — current_user_id() nach set_config('app.current_user', '', true)=null
|
||||
current-user-id-gesetzt-liefert-wert: bestanden — current_user_id() nach set_config('app.current_user', 'user-a1', true)="user-a1"
|
||||
```
|
||||
|
||||
Wörtliche Werkzeugausgabe zweier der sechs Umkehrungen (eine Ein-Regel-Tabelle,
|
||||
eine der beiden vier-Regel-Tabellen):
|
||||
|
||||
```
|
||||
tendersavedsearch-benutzer-a-sieht-kollegen-nicht-gebunden: bestanden — forTenant(TENANT-A, user-a1) liefert sichtbare Nutzer: ["user-a1"]
|
||||
calendarsource-benutzer-a-sieht-kollegen-nicht-gebunden: bestanden — forTenant(TENANT-A, user-a1) liefert die Quelle von 'user-a2' mit: undefined — encryptedPassword des Kollegen ist damit auf Datenbankebene nicht mehr lesbar
|
||||
searchprovider-gemeinsame-zeile-als-benutzer-a-nicht-entfernbar: bestanden — bound(TENANT-A, user-a1).searchProvider.deleteMany({ id: 'search-shared-a' }) liefert count=0 — eine Regel ohne Befehlstrennung wuerde hier 1 liefern
|
||||
tenderrssfeed-gemeinsame-zeile-ohne-benutzer-weiterhin-entfernbar: bestanden — bound(TENANT-A, ohne Benutzer).tenderRssFeedSource.deleteMany({ id: 'rss-platform' }) liefert count=0 — WINDOWS #24: die plattformweite Zeile hat keinen Mandanten, die Schreibregel verlangt aber einen; das gilt VOR wie NACH dieser Migration unveraendert und ist kein neu entdecktes Loch
|
||||
Alle 203 Pruefungen bestanden.
|
||||
```
|
||||
|
||||
`pg_policies` der lebenden Datenbank (`tessera-ctl-db-1`) für die zehn
|
||||
umgestellten und die vier bewusst unveränderten Tabellen, gemessen nach dem
|
||||
Anwenden von `20260911120000` (Tabelle#Regelname#Befehl#USING#WITH CHECK):
|
||||
|
||||
```
|
||||
CalendarSource#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
DashboardLayout#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
FavoriteLink#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
GroupMembership#tenant_isolation_policy#ALL#(("groupId" IN ( SELECT "Group".id FROM "Group" WHERE ("Group"."tenantId" = current_tenant_id()))) AND ("userId" IN ( SELECT "User".id FROM "User" WHERE ("User"."tenantId" = current_tenant_id()))))#
|
||||
ModuleGrant#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND (("groupId" IS NULL) OR ("groupId" IN ( SELECT "Group".id FROM "Group" WHERE ("Group"."tenantId" = current_tenant_id())))) AND (("userId" IS NULL) OR ("userId" IN ( SELECT "User".id FROM "User" WHERE ("User"."tenantId" = current_tenant_id())))))#
|
||||
PasswordResetToken#tenant_isolation_policy#ALL#("userId" IN ( SELECT "User".id FROM "User" WHERE ("User"."tenantId" = current_tenant_id())))#
|
||||
SearchProvider#tenant_user_delete_policy#DELETE#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
SearchProvider#tenant_user_insert_policy#INSERT##(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))
|
||||
SearchProvider#tenant_user_read_policy#SELECT#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" IS NULL) OR ("userId" = current_user_id())))#
|
||||
SearchProvider#tenant_user_update_policy#UPDATE#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))
|
||||
TenderEmailConfig#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
TenderMatch#tenant_isolation_policy#ALL#("tenantId" = current_tenant_id())#
|
||||
TenderNotificationPref#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
TenderRssFeedSource#tenant_delete_policy#DELETE#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
TenderRssFeedSource#tenant_insert_policy#INSERT##(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))
|
||||
TenderRssFeedSource#tenant_platform_read_policy#SELECT#((("tenantId" = current_tenant_id()) OR ("tenantId" IS NULL)) AND ((current_user_id() IS NULL) OR ("userId" IS NULL) OR ("userId" = current_user_id())))#
|
||||
TenderRssFeedSource#tenant_update_policy#UPDATE#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))
|
||||
TenderSavedSearch#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
TenderTriage#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
WidgetInstance#tenant_isolation_policy#ALL#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
|
||||
```
|
||||
|
||||
Werkzeug-Endstand nach Aufgabe 2: `Alle 203 Pruefungen bestanden.` (Baseline vor
|
||||
diesem Lauf: 137). Tests am Ende von Aufgabe 2: 1020 bestanden / 62 Dateien,
|
||||
Typprüfung sauber.
|
||||
|
||||
### (b2) Signaltabelle — beide Fehlerrichtungen je Regel
|
||||
|
||||
| Fehlerrichtung | Erwartung | Gemessen | Befund |
|
||||
|---|---|---|---|
|
||||
| Zu streng: ein Aufruf OHNE Benutzer (Admin, Hintergrunddienst) sähe nur EINEN Nutzer statt des ganzen Mandanten | Regel müsste beide Nutzer weiterhin liefern | `<tabelle>-ohne-benutzer-sieht-beide` (zehn Tabellen) — JEDE Tabelle liefert beide Nutzer | NICHT der Fall — die `IS NULL OR`-Form wirkt wie entworfen |
|
||||
| Zu locker: ein Aufruf MIT Benutzer sähe die Zeile eines Kollegen DESSELBEN Mandanten | Regel müsste die Kollegenzeile ausblenden | `<tabelle>-benutzer-a-sieht-kollegen-nicht(-gebunden)` (zehn Tabellen) — JEDE Tabelle blendet aus; `<tabelle>-schreiben-als-a-mit-kennung-b-abgelehnt` (SQLSTATE 42501) für alle zehn | NICHT der Fall — Lesen UND Schreiben sind durchgesetzt |
|
||||
| Bewusst offene Flanke: ein Nutzer-CRUD-Aufrufer, der `userId` VERGISST | Sähe den ganzen Mandanten, keine Fehlermeldung | Nicht durch einen Test erzwungen — kein Wächter über das dritte Argument gebaut | Akzeptiert, aufgezeichnet (T-NKE-02, WINDOWS-Eintrag unten) — heute exakt der Stand vor dieser Migration |
|
||||
|
||||
### (b3) Welcher Code Leere anders deutet als vorher
|
||||
|
||||
Erst wirksam NACH dem Scharfschalten (Etappe 4) — heute mit BYPASSRLS ohne
|
||||
Wirkung, hier vorab aufgezeichnet, weil der Codepfad schon jetzt geschrieben
|
||||
ist. Methoden, die eine Zeile per `findUnique({ where: { id } })` holen und
|
||||
danach `userId` gegen den Aufrufer vergleichen, sehen nach dem Scharfschalten
|
||||
die Kollegenzeile bereits als `null` (die Regel blendet sie aus, BEVOR die
|
||||
Anwendung überhaupt vergleicht) — die Anwendung meldet dann `NotFoundException`
|
||||
statt der heutigen `Forbidden`-artigen Abweisung über den `userId`-Vergleich.
|
||||
Betroffen: `dashboard.service.ts` (`removeWidget`, `updateWidgetConfig`,
|
||||
`removeSearchProvider`), `calendar.service.ts` (`updateSource`,
|
||||
`deleteSource`), `favorites.service.ts` (`update`, `remove`). Beides ist eine
|
||||
Abweisung — kein Informationsleck entsteht, nur die Fehlerart ändert sich von
|
||||
Forbidden zu NotFound.
|
||||
|
||||
### (b4) Was dieser Durchlauf bewusst nicht löst
|
||||
|
||||
- Systemkontext für Hintergrunddienste (Etappe 3c) — `tender-digest.scheduler.ts`
|
||||
bleibt bewusst zweistellig, mit Kommentar.
|
||||
- Anmeldenamen pro Mandant (Etappe 3a) — nicht Teil dieses Laufs.
|
||||
- Kein Wächter, der jede Nutzer-CRUD-Aufrufstelle auf das dritte Argument von
|
||||
`forTenant()` prüft. Die Bestandsaufnahme (`rls-access-inventory.spec.ts`)
|
||||
unterscheidet heute nur mandanten-gebunden/ungebunden, nicht
|
||||
benutzer-gebunden — ein Aufrufer, der `userId` vergisst, ist für sie
|
||||
unsichtbar. Netz bis dahin: die dreistelligen Spec-Zusicherungen je Dienst.
|
||||
Siehe der neue WINDOWS-Eintrag unten.
|
||||
- WINDOWS #24 (Admin-Erstellung/-Entfernen plattformweiter `TenderRssFeedSource`-Zeilen
|
||||
bleibt ungebunden) bleibt unverändert offen — diese Migration ändert daran
|
||||
nichts, `tenderrssfeed-gemeinsame-zeile-ohne-benutzer-weiterhin-entfernbar`
|
||||
bestätigt das lediglich erneut.
|
||||
|
||||
### (b5) Was dieser Durchlauf bewusst nicht anfasst
|
||||
|
||||
- Die vier Tabellen mit `userId`-Spalte, die KEINE persönlichen Daten tragen
|
||||
(`GroupMembership`, `ModuleGrant`, `PasswordResetToken`, `TenderMatch`) —
|
||||
Verwaltungsobjekte, Anmelde-Artefakt, Hintergrunddienst-Schreibweg,
|
||||
begründet im Kopf der Migration.
|
||||
- Die drei SECURITY-DEFINER-Anmeldefunktionen (`auth_lookup_user_by_username`,
|
||||
`auth_lookup_user_by_email`, `auth_lookup_reset_token`) — unangetastet.
|
||||
- `schema.prisma` — unverändert, Gate gegen `8829999` in jeder Aufgabe.
|
||||
- Der Schalter (`DATABASE_URL` → Rolle `tessera`, BYPASSRLS) — bleibt AUS.
|
||||
- Compose-/Umgebungsdateien — unangetastet.
|
||||
|
||||
## Etappe 2 — Abschluss
|
||||
|
||||
Etappe 2 der Mandantentrennung ist mit diesem Lauf (260911-gwh) vollständig:
|
||||
@@ -3111,6 +3308,15 @@ dieser Aufgabe.
|
||||
Etappe-3-Entscheidung (1)) — siehe (h4)(a).
|
||||
- Die Benutzerdimension der Regeln (Etappe-3-Entscheidung (2)) — siehe
|
||||
(k4)/(f4)(a) und die übrigen Bereiche mit derselben Beobachtung.
|
||||
**Nachtrag (260911-nke): ERLEDIGT.** Migration
|
||||
`20260911120000_rls_user_dimension_personal_tables` (Etappe 3b) trägt die
|
||||
Benutzerdimension in die Regeln aller zehn persönlichen Tabellen; 34
|
||||
Nutzer-CRUD-Aufrufstellen reichen `userId` an `forTenant()` durch. Siehe
|
||||
Abschnitt "Regelschluss Benutzerdimension (Etappe 3b, 260911-nke)" oben.
|
||||
Was davon bewusst offenbleibt: ein Aufrufer, der `userId` vergisst, sieht
|
||||
weiterhin den ganzen Mandanten (kein Wächter gebaut, siehe
|
||||
`.planning/WINDOWS.md`); Systemkontext für Hintergrunddienste (Etappe 3c)
|
||||
und Anmeldenamen pro Mandant (Etappe 3a) bleiben offen.
|
||||
- Die Modulkatalog-Regel für `Module` (Befund E, `module-registry`) — sobald
|
||||
eine Regel eingeführt wird, müssen die heute bewusst ungebundenen
|
||||
Katalogzugriffe nachgezogen werden.
|
||||
|
||||
Reference in New Issue
Block a user