docs(quick-260909-jts): Plan fuer Etappe 2, Bereich groups
This commit is contained in:
+775
@@ -0,0 +1,775 @@
|
|||||||
|
---
|
||||||
|
phase: quick-260909-jts
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
autonomous: true
|
||||||
|
requirements: [WINDOWS-20, ETAPPE-2-GROUPS]
|
||||||
|
|
||||||
|
files_modified:
|
||||||
|
- docs/mandantentrennung-etappe2-fehlerrichtung.md
|
||||||
|
- apps/api/scripts/rls-scratch-check.mjs
|
||||||
|
- apps/api/src/prisma/prisma-tenant.extension.ts
|
||||||
|
- apps/api/src/prisma/rls-access-inventory.spec.ts
|
||||||
|
- apps/api/src/groups/groups.service.ts
|
||||||
|
- apps/api/src/groups/groups.service.spec.ts
|
||||||
|
- apps/api/src/groups/module-grants.service.ts
|
||||||
|
- apps/api/src/groups/module-grants.service.spec.ts
|
||||||
|
- docs/mandantentrennung-zugriffsklassifikation.md
|
||||||
|
|
||||||
|
estimate:
|
||||||
|
tokens: 110000
|
||||||
|
raw_tokens: 110000
|
||||||
|
tasks: 3
|
||||||
|
confidence: low
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Jeder mandantengebundene Datenbankzugriff des Bereichs groups laeuft ueber einen gebundenen Client — einschliesslich der fuenf Zugriffe, die heute nur ueber den Rueckgabeparameter einer interaktiven Transaktion erreichbar sind und die von keiner Pruefung dieses Projekts je gesehen wurden."
|
||||||
|
- "Welche Transaktionsform den Mandantenkontext auf DERSELBEN Verbindung traegt, ist unter einer Rolle ohne BYPASSRLS gemessen, BEVOR die Umstellung der drei Transaktionsstellen darauf aufsetzt."
|
||||||
|
- "Die Uebergabe der Standardgruppe unmittelbar vor einer Gruppenloeschung (reassignDefaultBeforeDelete, danach ensureDefaultGroup) ist gebunden; Befund D aus der ldap-Kritik ist damit erledigt und als erledigt vermerkt."
|
||||||
|
- "Es existiert eine schriftliche Kritik fuer den Bereich groups, die je Pfad das konkrete Signal nennt und benennt, welcher Code Leere als Abwesenheit deutet — einschliesslich der einen Stelle, an der ein zu kleines Leseergebnis nicht zu wenig, sondern ZU VIEL bewirkt."
|
||||||
|
- "Die maschinelle Absicherung sieht Modellzugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion; ein dort fehlender Mandantenkontext kann nicht mehr unentdeckt bleiben."
|
||||||
|
- "Beide Testdateien des Bereichs koennen rot werden, wenn eine Fundstelle ungebunden bleibt — nachgewiesen ueber zwei unterscheidbare Clients, nicht behauptet."
|
||||||
|
- "Die Mitgliedschaftsanlage in der Standardgruppe prueft den Mandanten des Zielbenutzers; dass die Policy auf GroupMembership das NICHT tut, ist gemessen."
|
||||||
|
- "Klassifikationsdokument und maschinelle Absicherung zeigen fuer alle Paare des Bereichs denselben, gemessenen Stand `gebunden`."
|
||||||
|
- "719+ Tests und die Typpruefung sind gruen; Schema, Migrationen und alle vier Compose-Dateien sind unveraendert; der Schalter bleibt AUS."
|
||||||
|
artifacts:
|
||||||
|
- docs/mandantentrennung-etappe2-fehlerrichtung.md
|
||||||
|
- apps/api/scripts/rls-scratch-check.mjs
|
||||||
|
- apps/api/src/prisma/prisma-tenant.extension.ts
|
||||||
|
- apps/api/src/prisma/rls-access-inventory.spec.ts
|
||||||
|
- apps/api/src/groups/groups.service.ts
|
||||||
|
- apps/api/src/groups/module-grants.service.ts
|
||||||
|
- docs/mandantentrennung-zugriffsklassifikation.md
|
||||||
|
key_links:
|
||||||
|
- "gebundener Client <-> Policies tenant_isolation_policy auf Group/GroupMembership/ModuleGrant/TenantModuleActivation (wortgleich aus den ausgelieferten Migrationen extrahiert, nicht im Werkzeug nachgetippt)"
|
||||||
|
- "interaktive Transaktion in ensureDefaultGroup <-> set_config auf derselben Verbindung — die eine Stelle, an der die Umstellung scheitern kann, ohne dass ein Test es merkt"
|
||||||
|
- "reassignDefaultBeforeDelete <-> Loeschzweig in ldap.service.ts — die Uebergabe, deren stilles false eine Gruppe ohne Standardnachfolger zuruecklaesst (Befund D)"
|
||||||
|
- "ensureDefaultGroup <-> seine vier Aufrufer (ldap.service.ts, tenant.service.ts, admin-seed.service.ts zweimal) — der Start darf nicht brechen"
|
||||||
|
- "rls-access-inventory.spec.ts <-> Stand-Spalte des Klassifikationsdokuments, jetzt auch fuer Zugriffe ueber den Transaktionsparameter"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Der Bereich `groups` (37 Rohtreffer in zwei Dateien, dazu fuenf bisher fuer jede
|
||||||
|
Pruefung unsichtbare Zugriffe) wird auf einen gebundenen Prisma-Client umgestellt —
|
||||||
|
als zweiter Bereich der Etappe 2 und als Reihenfolgebedingung fuer Etappe 4.
|
||||||
|
|
||||||
|
Zweck: Dieser Bereich IST die Berechtigungsschicht. Gruppenmitgliedschaft und
|
||||||
|
Modulfreigaben entscheiden, wer welches Modul sehen darf. Ein zu kleines
|
||||||
|
Leseergebnis fuehrt hier nicht nur zu einer leeren Liste, sondern an mindestens
|
||||||
|
vier Stellen zu einer Handlung: eine Gruppe wird ohne Standardnachfolger geloescht,
|
||||||
|
ein Loeschdialog meldet "keine Mitglieder, keine Freigaben" ueber eine volle Gruppe,
|
||||||
|
ein neuer Benutzer bekommt still keine Modulfreigabe — und an einer Stelle wird aus
|
||||||
|
zu wenig Lesen sogar zu viel Schreiben.
|
||||||
|
|
||||||
|
Ergebnis: die Kritikschrift bekommt einen `groups`-Abschnitt, das Messwerkzeug
|
||||||
|
bekommt die Policies dieses Bereichs UND die Antwort auf die einzige offene
|
||||||
|
Architekturfrage der Umstellung (welche Transaktionsform den Mandantenkontext
|
||||||
|
traegt), zwei Dienste sind umgestellt, eine latente Mitgliedschaftsluecke ist
|
||||||
|
geschlossen, die maschinelle Absicherung sieht erstmals Zugriffe ueber den
|
||||||
|
Transaktionsparameter, und das Klassifikationsdokument weist seinen neuen Stand
|
||||||
|
maschinell nach.
|
||||||
|
|
||||||
|
Aufgabe 1 fuehrt bewusst, obwohl sie keinen Nutzernutzen liefert: sie ist der
|
||||||
|
Durchstich durch die gesamte Kette (ausgelieferte Policy -> Rolle ohne BYPASSRLS ->
|
||||||
|
Bindungsmuster -> die drei Transaktionsformen, die dieser Bereich tatsaechlich
|
||||||
|
verwendet) und beantwortet die Frage, auf der die gesamte Umstellung ruht, mit einer
|
||||||
|
Messung statt mit einer Annahme. Erst danach wird Dienstcode angefasst.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.claude/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/STATE.md
|
||||||
|
@docs/mandantentrennung-zugriffsklassifikation.md
|
||||||
|
@docs/mandantentrennung-etappe2-fehlerrichtung.md
|
||||||
|
@.planning/quick/260909-ipc-mandantentrennung-etappe-2-bereich-ldap-/260909-ipc-SUMMARY.md
|
||||||
|
@apps/api/src/prisma/prisma-tenant.extension.ts
|
||||||
|
@apps/api/src/prisma/rls-access-inventory.spec.ts
|
||||||
|
@apps/api/scripts/rls-scratch-check.mjs
|
||||||
|
@apps/api/src/ldap/ldap-config.service.ts
|
||||||
|
@apps/api/src/groups/groups.service.ts
|
||||||
|
@apps/api/src/groups/module-grants.service.ts
|
||||||
|
@apps/api/src/groups/groups.controller.ts
|
||||||
|
@apps/api/src/groups/module-grants.controller.ts
|
||||||
|
@apps/api/src/user/admin-seed.service.ts
|
||||||
|
@CLAUDE.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<planning_time_findings>
|
||||||
|
|
||||||
|
Alles Folgende wurde am 2026-09-09 zur Planungszeit am lebenden Baum gemessen. Die
|
||||||
|
Zeilennummern aus dem Auftrag waren Hinweise zum Aufschlagen, keine
|
||||||
|
Aenderungsvollmacht — jede Fundstelle wurde einzeln angesehen.
|
||||||
|
|
||||||
|
**Ausgangsstand (gemessen, nicht zitiert):**
|
||||||
|
|
||||||
|
- `npm --prefix apps/api run test` -> 53 Dateien, **719 Tests**, gruen, 4,94 s.
|
||||||
|
- `npm --prefix apps/api run type-check` -> Rueckgabewert 0.
|
||||||
|
- `pnpm --filter @tessera/api exec vitest run src/groups` -> 84 Tests gruen
|
||||||
|
(42 in `groups.service.spec.ts`, 28 in `module-grants.service.spec.ts`,
|
||||||
|
14 in `migration-sql.spec.ts`).
|
||||||
|
- `npm --prefix apps/api run test -- src/groups/groups.service.spec.ts src/prisma/rls-access-inventory.spec.ts`
|
||||||
|
-> 51 Tests gruen (die Zielform der Aufgaben-Verifikation laeuft).
|
||||||
|
- Wegwerf-Werkzeug: `TESSERA_SCRATCH_ADMIN_URL="postgresql://tessera:tessera_dev@172.19.0.2:5432/postgres" node apps/api/scripts/rls-scratch-check.mjs`
|
||||||
|
-> "Alle 13 Pruefungen bestanden.", Rueckgabewert 0. Das Fundament ist damit
|
||||||
|
JETZT belegt, nicht laut Bericht. `docker inspect tessera-ctl-db-1` liefert
|
||||||
|
derzeit 172.19.0.2 — eine Container-Adresse ist veraenderlich und wird bei der
|
||||||
|
Ausfuehrung neu ermittelt.
|
||||||
|
|
||||||
|
**Die 37 Rohtreffer, einzeln aufgeschlagen — und warum 37 nicht die Zahl der
|
||||||
|
Fundstellen ist.** Die Bereichsuebersicht misst mit
|
||||||
|
`grep -ro "this\.prisma\.[a-zA-Z]*"`. Dieses Muster trifft auch
|
||||||
|
`this.prisma.$transaction`, weil `[a-zA-Z]*` auch null Zeichen erlaubt. Von den 37
|
||||||
|
Rohtreffern sind daher **drei gar keine Modellzugriffe**, sondern die drei
|
||||||
|
Transaktionsaufrufe in `groups.service.ts`. Tatsaechliche Modellzugriffe: 21 + 13 =
|
||||||
|
**34**. Dazu kommen **fuenf weitere**, die die Zaehlung ueberhaupt nicht sieht (siehe
|
||||||
|
Befund B). Die dokumentierte Bereichszahl 37 ist als Rohtrefferzahl korrekt, taugt
|
||||||
|
aber nicht als Arbeitsvorrat.
|
||||||
|
|
||||||
|
`apps/api/src/groups/groups.service.ts` — 21 Modellzugriffe in 12 Methoden, alle mit
|
||||||
|
am Aufrufort bereits bekanntem Mandanten:
|
||||||
|
|
||||||
|
| Methode | Modelle | Anmerkung |
|
||||||
|
|---|---|---|
|
||||||
|
| `listForTenant` | group | Filter auf `tenantId` vorhanden |
|
||||||
|
| `create` | group | schreibt `tenantId` |
|
||||||
|
| `findOwned` (privat) | group | Filter auf `id` UND `tenantId` — der Ownership-Schutz aller CRUD-Routen |
|
||||||
|
| `update` | group (3x, davon 2 in einer Array-Transaktion) | zwei der drei schreiben ueber `where: { id }` allein und verlassen sich auf das vorherige `findOwned` |
|
||||||
|
| `getImpact` | groupMembership, moduleGrant | zaehlt ueber `groupId` allein, ohne Mandantenfilter |
|
||||||
|
| `remove` | group | loescht ueber `id` allein, nach `findOwned` |
|
||||||
|
| `listMembers` | groupMembership | ueber `groupId` allein |
|
||||||
|
| `addMembers` | user, groupMembership | die Benutzerabfrage filtert auf `tenantId` |
|
||||||
|
| `removeMember` | groupMembership | ueber `groupId`/`userId`/`source` |
|
||||||
|
| `ensureDefaultGroup` | group (Zaehler) | plus die interaktive Transaktion, siehe Befund B |
|
||||||
|
| `reassignDefaultBeforeDelete` | group (5x, davon 2 in einer Array-Transaktion) | drei Lesezugriffe filtern auf `tenantId` |
|
||||||
|
| `addUserToDefaultGroup` | group, groupMembership | siehe Befund E |
|
||||||
|
|
||||||
|
`apps/api/src/groups/module-grants.service.ts` — 13 Modellzugriffe in 5 Methoden,
|
||||||
|
alle mit bekanntem Mandanten: `assertTargetBelongsToTenant` (group, user),
|
||||||
|
`grant` (tenantModuleActivation, moduleGrant 2x), `revoke` (moduleGrant),
|
||||||
|
`getMatrix` (tenantModuleActivation, group, moduleGrant),
|
||||||
|
`getUserAccess` (tenantModuleActivation, moduleGrant 2x, groupMembership).
|
||||||
|
|
||||||
|
**Befund A — die drei Transaktionen sind der eigentliche Kern dieser Umstellung, und
|
||||||
|
die Wirkung ihrer Bindung ist NICHT bekannt.** Der Kopfkommentar von
|
||||||
|
`apps/api/src/prisma/prisma-tenant.extension.ts` fuehrt genau diesen Fall als
|
||||||
|
ausdruecklichen Vorbehalt fuer Etappe 2: `$transaction` ist keine Modelloperation,
|
||||||
|
laeuft nicht durch `$allOperations` und bekommt daher keinen Mandantenkontext; die
|
||||||
|
darin enthaltenen Einzeloperationen wuerden jede ihre EIGENE Teiltransaktion
|
||||||
|
bekommen, was die Atomaritaet der aeusseren Transaktion verletzt. Der Kommentar
|
||||||
|
haelt fest, dass zum Zeitpunkt der ldap-Umstellung KEIN gebundener Aufrufer eine
|
||||||
|
eigene Transaktion hatte, und verlangt woertlich, das vor jedem neuen Fall in
|
||||||
|
Etappe 2 erneut zu pruefen. Dieser Bereich ist dieser Fall.
|
||||||
|
|
||||||
|
Gemessen (`grep -rn '\$transaction(' apps/api/src --include=*.ts | grep -v spec`):
|
||||||
|
im gesamten API-Quelltext gibt es vier Transaktionsaufrufe ausserhalb der Erweiterung
|
||||||
|
selbst. Drei davon liegen in `groups.service.ts` (zwei Array-Form in `update` und
|
||||||
|
`reassignDefaultBeforeDelete`, eine interaktive Callback-Form in
|
||||||
|
`ensureDefaultGroup`), der vierte in `tender-fingerprint-backfill.service.ts` auf der
|
||||||
|
plattformweiten `Tender`-Tabelle und damit ausserhalb jeder Mandantenbindung.
|
||||||
|
`groups.service.ts` ist ausserdem die EINZIGE Datei im gesamten API-Quelltext mit
|
||||||
|
einer interaktiven Callback-Transaktion. Die Frage, welche Form den Kontext traegt,
|
||||||
|
faellt also ausschliesslich hier an — und muss vor der Umstellung beantwortet sein,
|
||||||
|
nicht danach.
|
||||||
|
|
||||||
|
**Befund B — fuenf Modellzugriffe, die keine Pruefung dieses Projekts je gesehen
|
||||||
|
hat.** Innerhalb der interaktiven Transaktion in `ensureDefaultGroup` laufen fuenf
|
||||||
|
Zugriffe ueber den Rueckgabeparameter der Transaktion (`group.create`,
|
||||||
|
`user.findMany`, `groupMembership.createMany`, `tenantModuleActivation.findMany`,
|
||||||
|
`moduleGrant.createMany`). Weder die alte Erkennung ueber `this.prisma.<Modell>` noch
|
||||||
|
die in 260909-ipc ergaenzte Erkennung gebundener Zugriffe sieht sie. Eine davon,
|
||||||
|
`tenantModuleActivation`, kommt in `groups.service.ts` NUR dort vor — das Paar
|
||||||
|
(`groups.service.ts`, `tenantModuleActivation`) fehlt deshalb bis heute vollstaendig
|
||||||
|
im Klassifikationsdokument. Und ausgerechnet `moduleGrant.createMany` an dieser
|
||||||
|
Stelle verteilt Modulfreigaben. Die Erkennungsluecke sitzt damit genau auf der
|
||||||
|
Schreibstelle mit der groessten Wirkung.
|
||||||
|
|
||||||
|
**Befund C — die Testdateien koennen die Umstellung nicht bemerken, aber anders als
|
||||||
|
bei ldap.** `grep -n "forTenant\|prisma-tenant\|vi.mock"` liefert in
|
||||||
|
`groups.service.spec.ts` und `module-grants.service.spec.ts` **null Treffer**. Es gibt
|
||||||
|
keinen Identitaets-Mock wie bei ldap — es gibt gar keinen. Beide Dateien uebergeben
|
||||||
|
einen handgeschriebenen In-Memory-Fake als Prisma-Ersatz. Nach der Umstellung liefe
|
||||||
|
`forTenant(this.prisma, tenantId)` gegen ein Objekt ohne `$extends` und JEDER Test
|
||||||
|
wuerde abstuerzen — rot aus dem falschen Grund, ohne irgendetwas zu beweisen. Die
|
||||||
|
Dateien brauchen einen Mock, der den gebundenen Client als ZWEITES, unterscheidbares
|
||||||
|
Objekt ueber DEMSELBEN Speicher liefert (Muster aus 260909-ipc), sonst ist "umgestellt"
|
||||||
|
wieder nur eine Behauptung. Der vorhandene Fake beherrscht bereits beide
|
||||||
|
Transaktionsformen und reicht sich selbst als Transaktionsparameter durch — er ist
|
||||||
|
wiederverwendbar, nicht wegzuwerfen.
|
||||||
|
|
||||||
|
**Befund D — `ensureDefaultGroup` ist NICHT der `beides`-Fall, den der Auftrag
|
||||||
|
vermutet.** Gemessen: die Methode hat VIER Aufrufstellen, nicht drei —
|
||||||
|
`ldap.service.ts` (nach dem Loeschzweig), `tenant.service.ts` (Mandantenanlage) und
|
||||||
|
`admin-seed.service.ts` ZWEIMAL (einmal in `seedAdmin` fuer den frisch angelegten
|
||||||
|
Vorgabe-Mandanten, einmal in der Startup-Reparatur-Schleife ueber alle Mandanten).
|
||||||
|
Alle vier uebergeben einen konkreten, bereits bekannten Mandanten. Die uebergreifende
|
||||||
|
Abfrage ist die Schleifenquelle `tenant.findMany` in `admin-seed.service.ts` — die
|
||||||
|
liegt ausserhalb dieses Bereichs und ist bereits als
|
||||||
|
`keine-mandantengebundene-tabelle` klassifiziert, weil `Tenant` per Definition keine
|
||||||
|
eigene `tenantId` hat. `ensureDefaultGroup` selbst ist damit eindeutig
|
||||||
|
mandantengebunden und MUSS binden. Es gibt hier keinen Konflikt zwischen Schleife und
|
||||||
|
Bindung; die eigentliche Gefahr fuer den Start ist eine andere, naemlich Befund A: die
|
||||||
|
Methode ist die interaktive Transaktion.
|
||||||
|
|
||||||
|
**Befund E — eine latente Mandantenluecke, die die Policy nicht auffaengt.**
|
||||||
|
`addUserToDefaultGroup(tenantId, userId)` sucht die Standardgruppe mandantengebunden,
|
||||||
|
legt danach aber die Mitgliedschaft an, ohne zu pruefen, dass der Zielbenutzer zu
|
||||||
|
diesem Mandanten gehoert. Die ausgelieferte Policy auf `GroupMembership`
|
||||||
|
(`20260804130918_groups_rls_policies`) prueft ausschliesslich die GRUPPENSEITE
|
||||||
|
(`groupId IN (SELECT id FROM "Group" WHERE tenantId = current_tenant_id())`) — die
|
||||||
|
Benutzerseite prueft sie nachweislich nicht. Der einzige heutige Aufrufer
|
||||||
|
(`user.service.ts`, direkt nach `user.create`) uebergibt einen frisch angelegten
|
||||||
|
Benutzer desselben Mandanten, die Luecke ist also heute nicht erreichbar; die Methode
|
||||||
|
ist aber aus `GroupsModule` exportiert und nimmt eine rohe Benutzerkennung entgegen.
|
||||||
|
Das Schwestermuster steht zwei Methoden hoeher: `addMembers` filtert seine
|
||||||
|
Benutzerliste ausdruecklich auf `tenantId` und ueberspringt fremde Kennungen. Dieselbe
|
||||||
|
Pruefung fehlt hier.
|
||||||
|
|
||||||
|
**Befund F — dieselbe Luecke eine Ebene hoeher: die ModuleGrant-Policy prueft die
|
||||||
|
referenzierte Gruppe nicht.** `CREATE POLICY tenant_isolation_policy ON "ModuleGrant"
|
||||||
|
USING ("tenantId" = current_tenant_id())` — eine Freigabezeile mit eigenem, korrektem
|
||||||
|
`tenantId`, die aber auf die Gruppe eines FREMDEN Mandanten zeigt, verletzt diese
|
||||||
|
Policy nicht. Der einzige Schutz davor ist die Anwendungspruefung
|
||||||
|
`assertTargetBelongsToTenant` in `module-grants.service.ts`. Das ist keine
|
||||||
|
Vermutung aus dem Policy-Text, sondern eine in Aufgabe 1 zu messende Tatsache, und
|
||||||
|
es ist der Grund, warum diese Anwendungspruefung bei der Umstellung nicht als
|
||||||
|
"macht jetzt ohnehin die Datenbank" wegfallen darf.
|
||||||
|
|
||||||
|
**Befund G — die offene Frage aus dem Auftrag zur plattformweiten Eindeutigkeit
|
||||||
|
faellt in diesem Bereich nicht an.** Gemessen mit
|
||||||
|
`grep -n "email\|username" apps/api/src/groups/*.ts`: der einzige Treffer ausserhalb
|
||||||
|
von Kommentaren ist eine Feldauswahl in `listMembers` (`select: { id, username,
|
||||||
|
displayName, email }`) — eine Projektion, keine Suche. Beide `user`-Zugriffe des
|
||||||
|
Bereichs (`addMembers`, `assertTargetBelongsToTenant`) suchen ueber Kennung UND
|
||||||
|
Mandant. Die Falle aus Befund A des ldap-Durchlaufs (`resolveEmailForWrite`,
|
||||||
|
plattformweite Eindeutigkeit von `email`/`username`) existiert hier nicht. Beide
|
||||||
|
`user`-Fundstellen binden.
|
||||||
|
|
||||||
|
**Befund H — Fremdzugriff ueber die Kennung allein: in diesem Bereich nicht
|
||||||
|
vorhanden.** Beide Steuerungen (`groups.controller.ts`, `module-grants.controller.ts`)
|
||||||
|
holen den Mandanten ausschliesslich aus dem Sitzungsnachweis und weisen ohne ihn mit
|
||||||
|
403 ab; jede Route reicht ihn an den Dienst weiter. Die Luecke, die der ldap-Durchlauf
|
||||||
|
gefunden hat (Loeschen ueber die Kennung allein), gibt es hier nicht. Die einzige
|
||||||
|
Luecke dieser Klasse ist Befund E, und sie sitzt nicht in der Steuerung, sondern in
|
||||||
|
einer aus dem Modul exportierten Dienstmethode.
|
||||||
|
|
||||||
|
**Befund I — welcher Code Leere als Abwesenheit deutet (Vorarbeit fuer Aufgabe 1,
|
||||||
|
dort auszuformulieren und zu ergaenzen, nicht abzuschreiben):**
|
||||||
|
`reassignDefaultBeforeDelete` (zweimal: kein Treffer fuer die Gruppe, kein
|
||||||
|
Ersatzkandidat — beide Male stilles `false`, und der Aufrufer loescht danach
|
||||||
|
trotzdem), `getImpact` (0/0 vor einer kaskadierenden Loeschung),
|
||||||
|
`addUserToDefaultGroup` (stilles `return` ohne Standardgruppe),
|
||||||
|
`ensureDefaultGroup` (der Zaehler ist UMGEKEHRT gepolt: null gelesen heisst hier
|
||||||
|
nicht "nichts tun", sondern "alles neu anlegen"). Gegenbeispiele in die andere
|
||||||
|
Richtung, ebenfalls festzuhalten: die Aktivierungspruefung in `grant` und
|
||||||
|
`assertTargetBelongsToTenant` werfen bei Leere LAUT.
|
||||||
|
|
||||||
|
Zur Frage, ob eine leere Freigabe-Matrix zu einem Massen-Entzug fuehren kann:
|
||||||
|
gemessen in `apps/web/src/app/(portal)/admin/modules/grants/page.tsx` — die Matrix
|
||||||
|
schaltet je Zelle einzeln (POST bzw. DELETE pro Klick), es gibt keinen
|
||||||
|
Sammel-Speichern-Knopf, der einen Abgleich gegen den gelesenen Zustand faehrt. Ein zu
|
||||||
|
kleines Leseergebnis fuehrt dort also zu einer leeren Anzeige, nicht zu einem
|
||||||
|
Massen-Entzug. Das ist der Unterschied zum `deleteMany`-mit-`notIn` des
|
||||||
|
ldap-Bereichs und gehoert als Entlastung in die Kritikschrift.
|
||||||
|
|
||||||
|
**Gewaehltes Muster (bewusst, nicht stillschweigend):** Der Mandantenkontext wird
|
||||||
|
weiterhin IM DIENST erzeugt, wie im Bereich `ldap` und in `auth.service.ts`. Der
|
||||||
|
offene Befund `req.tenantPrisma` (gesetzt in `tenant.middleware.ts` und
|
||||||
|
`tenant.guard.ts`, nirgends gelesen) wird auch von diesem Durchlauf AUSDRUECKLICH
|
||||||
|
NICHT entschieden.
|
||||||
|
|
||||||
|
**Nicht angefasst:** `prisma/schema.prisma`, `prisma/migrations/`, alle vier
|
||||||
|
Compose-Dateien, `.env`. `DATABASE_URL` bleibt auf der Rolle `tessera` mit
|
||||||
|
BYPASSRLS — das Scharfschalten ist Etappe 4. Am Verzeichnis (AD) wird nichts
|
||||||
|
geaendert; der Dienstzugang ist auslegungsgemaess nur lesend.
|
||||||
|
</planning_time_findings>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="tracer">
|
||||||
|
<name>Aufgabe 1: Fehlerrichtung fuer groups schreiben und die Transaktionsfrage messen</name>
|
||||||
|
<precondition>Der Container `tessera-ctl-db-1` laeuft und ist erreichbar; seine Adresse per `docker inspect tessera-ctl-db-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$v.IPAddress}}{{end}}'` NEU ermitteln (zur Planungszeit 172.19.0.2 — eine Container-Adresse ist veraenderlich und darf nicht aus dem Plan abgeschrieben werden).</precondition>
|
||||||
|
<files>apps/api/scripts/rls-scratch-check.mjs, docs/mandantentrennung-etappe2-fehlerrichtung.md</files>
|
||||||
|
<action>
|
||||||
|
Zuerst messen, dann die Kritik aus der Messung schreiben — nicht umgekehrt. Kein
|
||||||
|
Dienstcode in dieser Aufgabe.
|
||||||
|
|
||||||
|
TEIL 1, `apps/api/scripts/rls-scratch-check.mjs`: einen vierten Abschnitt
|
||||||
|
`runGroupsAreaChecks(adminUrl, scratchRoleUrl, results)` nach dem Vorbild des
|
||||||
|
vorhandenen `runLdapAreaChecks` ergaenzen und in `main()` nach diesem aufrufen.
|
||||||
|
|
||||||
|
Die Policies werden NICHT im Werkzeug neu getippt. Sie kommen aus zwei
|
||||||
|
ausgelieferten Migrationen: `Group`, `GroupMembership` und `ModuleGrant` aus dem
|
||||||
|
Verzeichnis, das auf `_groups_rls_policies` endet, `TenantModuleActivation` aus dem
|
||||||
|
Verzeichnis, das auf `_rls_remaining_tenant_tables` endet. Das vorhandene
|
||||||
|
`extractPolicySql` kann beide bedienen; `readRlsPoliciesMigrationSql` schliesst die
|
||||||
|
Groups-Migration heute ausdruecklich aus und braucht deshalb ein zweites, eigenes
|
||||||
|
Lesehilfsmittel statt einer Aenderung am bestehenden. Findet die Extraktion eine der
|
||||||
|
vier Anweisungen nicht, meldet der Abschnitt eine FEHLGESCHLAGENE Pruefung
|
||||||
|
`groups-policies-aus-migration-gefunden` und bricht ab — das Werkzeug darf nicht
|
||||||
|
still weitermessen, wenn es nichts zu messen gefunden hat.
|
||||||
|
|
||||||
|
Der Abschnitt legt in der Wegwerf-Datenbank schlanke Tabellen an, die genau die
|
||||||
|
Spalten tragen, die die vier Policies und die Messungen brauchen: `Group`
|
||||||
|
(id, tenantId, name, isDefault), `GroupMembership` (id, groupId, userId, source),
|
||||||
|
`ModuleGrant` (id, tenantId, moduleId, groupId, userId) und
|
||||||
|
`TenantModuleActivation` (id, tenantId, moduleId, isActive). Danach ENABLE plus
|
||||||
|
FORCE ROW LEVEL SECURITY, die vier extrahierten Policies, die Rechtevergabe an die
|
||||||
|
Wegwerf-Rolle und je Mandant (TENANT-A, TENANT-B) eine Gruppe, eine Mitgliedschaft,
|
||||||
|
eine Freigabe und eine Aktivierung.
|
||||||
|
|
||||||
|
Gemessen werden unter der Rolle ohne BYPASSRLS, ueber das vorhandene
|
||||||
|
`forTenantQuery`-Hilfsmittel, diese Verhaltensweisen mit diesen Kennungen:
|
||||||
|
|
||||||
|
- `group-gebunden-nur-eigene-zeile` — der gebundene SELECT unter TENANT-A liefert
|
||||||
|
genau die Gruppe von A und keine von B.
|
||||||
|
- `group-ungebunden-null-zeilen` — DERSELBE SELECT ohne vorheriges Setzen des
|
||||||
|
Kontexts liefert null Zeilen. Das ist die Belegzeile, die die Kritikschrift traegt.
|
||||||
|
- `groupmembership-folgt-join-auf-group` — gebunden unter TENANT-A ist genau die
|
||||||
|
Mitgliedschaft sichtbar, die an A's Gruppe haengt.
|
||||||
|
- `groupmembership-schreiben-fremde-gruppe-abgelehnt` — ein gebundenes INSERT unter
|
||||||
|
TENANT-A mit der Gruppenkennung von B wird abgewiesen; die Abweisung ist das
|
||||||
|
bestandene Ergebnis.
|
||||||
|
- `groupmembership-schreiben-fremder-benutzer-nicht-verhindert` — ein gebundenes
|
||||||
|
INSERT unter TENANT-A mit A's Gruppe, aber einer Benutzerkennung, die es in A
|
||||||
|
nicht gibt, GELINGT. Diese Pruefung gilt als bestanden, wenn das INSERT
|
||||||
|
durchgeht: sie belegt Befund E, naemlich dass die Policy die Benutzerseite nicht
|
||||||
|
prueft und die Anwendung sie pruefen muss. Der Meldetext dieser Pruefung sagt das
|
||||||
|
ausdruecklich, damit eine bestandene Pruefung nicht mit "ist abgesichert"
|
||||||
|
verwechselt wird.
|
||||||
|
- `modulegrant-gebunden-nur-eigene-zeile` — wie bei `Group`.
|
||||||
|
- `modulegrant-fremde-gruppe-trotz-eigener-mandantenkennung-erlaubt` — ein
|
||||||
|
gebundenes INSERT unter TENANT-A mit korrekter eigener Mandantenkennung, aber der
|
||||||
|
Gruppenkennung von B, GELINGT. Auch hier ist das Durchgehen das bestandene
|
||||||
|
Ergebnis und der Meldetext benennt die Konsequenz: `assertTargetBelongsToTenant`
|
||||||
|
ist der einzige Schutz und darf bei der Umstellung nicht entfallen (Befund F).
|
||||||
|
- `tenantmoduleactivation-gebunden-nur-eigene-zeile` — wie bei `Group`.
|
||||||
|
|
||||||
|
TEIL 2, dieselbe Datei, die Transaktionsmessung — der eigentliche Grund, warum diese
|
||||||
|
Aufgabe vor jedem Dienstcode steht. Gemessen wird an einem Client, der WORTGLEICH die
|
||||||
|
Erweiterungsform aus `apps/api/src/prisma/prisma-tenant.extension.ts` nachbaut
|
||||||
|
(`$extends` mit `$allOperations`, darin die Array-Form der Transaktion aus
|
||||||
|
Kontextsetzung und eigentlicher Abfrage) — nicht ueber das vereinfachte
|
||||||
|
`forTenantQuery`, denn genau die Erweiterungsschicht ist hier der Gegenstand.
|
||||||
|
|
||||||
|
Drei Formen werden beobachtet, jeweils mit `pg_backend_pid()` UND
|
||||||
|
`current_tenant_id()` in jeder Teilabfrage plus einem echten Lesezugriff auf
|
||||||
|
`"Group"`, damit sichtbar wird, ob die Policy die Zeile durchlaesst:
|
||||||
|
|
||||||
|
(i) die Array-Form auf dem gebundenen Client — das, was `update` und
|
||||||
|
`reassignDefaultBeforeDelete` nach einer naiven Umstellung waeren;
|
||||||
|
(ii) die interaktive Callback-Form auf dem gebundenen Client — das, was
|
||||||
|
`ensureDefaultGroup` nach einer naiven Umstellung waere;
|
||||||
|
(iii) die interaktive Callback-Form auf dem UNgebundenen Client, bei der die
|
||||||
|
Kontextsetzung als erste Anweisung auf dem Transaktionsparameter selbst laeuft und
|
||||||
|
danach jede weitere Anweisung ebenfalls auf ihm — der Kandidat fuer ein Hilfsmittel,
|
||||||
|
das mehrschrittige Transaktionen traegt.
|
||||||
|
|
||||||
|
Jede der drei Formen wird ueber eine eigene Meldefunktion `beobachte(...)`
|
||||||
|
ausgegeben, die NICHT in die Pruefliste einfliesst und den Rueckgabewert nicht
|
||||||
|
beeinflusst: sie druckt je Form entweder die beobachteten Werte (Verbindungskennung
|
||||||
|
je Teilschritt, gelesener Mandantenkontext, Zeilenzahl) oder, falls die Form
|
||||||
|
ueberhaupt nicht laeuft, den vollstaendigen Fehlertext. Eine Form, die abbricht, ist
|
||||||
|
ein Messergebnis und kein Werkzeugfehler.
|
||||||
|
|
||||||
|
Darauf gesetzt wird GENAU EINE echte Pruefung:
|
||||||
|
`mindestens-eine-transaktionsform-traegt-den-mandantenkontext`. Sie gilt als
|
||||||
|
bestanden, wenn mindestens eine der drei Formen alle drei Bedingungen erfuellt —
|
||||||
|
gleiche Verbindungskennung ueber alle Teilschritte, gelesener Mandantenkontext gleich
|
||||||
|
TENANT-A, und der Lesezugriff liefert genau die Zeile von A. Ihr Meldetext nennt
|
||||||
|
NAMENTLICH, welche Formen bestanden haben und welche nicht. Das Ergebnis dieser
|
||||||
|
Pruefung ist die Entscheidungsgrundlage fuer Aufgabe 2; ohne sie gaebe es dort nur
|
||||||
|
eine Annahme.
|
||||||
|
|
||||||
|
Das Werkzeug raeumt weiterhin ausschliesslich seine fest verdrahtete
|
||||||
|
Wegwerf-Datenbank ab und bekommt keine steuerbaren Namen (T-EOR-07 bleibt gueltig).
|
||||||
|
|
||||||
|
TEIL 3, `docs/mandantentrennung-etappe2-fehlerrichtung.md` um einen Abschnitt
|
||||||
|
`## Bereich groups` ERWEITERN, nicht ein zweites Dokument anlegen. Die Leitfrage aus
|
||||||
|
Abschnitt (a) gilt unveraendert weiter und wird nicht wiederholt; der neue Abschnitt
|
||||||
|
verweist darauf und haelt im Kopf fest, dass er den Bereich `groups` zum Zeitpunkt
|
||||||
|
seiner Umstellung beschreibt (Quick-Task 260909-jts).
|
||||||
|
|
||||||
|
Inhalt, in ganzen Saetzen auf Deutsch:
|
||||||
|
|
||||||
|
(g1) Die Messung aus Teil 1 und Teil 2 mit den TATSAECHLICH beobachteten Zeilen als
|
||||||
|
Beleg — nicht mit erwarteten. Insbesondere die Belegzeile
|
||||||
|
`group-ungebunden-null-zeilen` und das namentliche Ergebnis der
|
||||||
|
Transaktionsmessung.
|
||||||
|
|
||||||
|
(g2) Eine Signaltabelle je umzustellendem Pfad mit den Spalten Pfad, Verhalten bei zu
|
||||||
|
wenig Ergebnis, konkretes Signal. Mindestens diese Zeilen, jeweils mit dem Ort, an
|
||||||
|
dem man es merkt: die Gruppenliste in der Verwaltung (Mitgliederzahl je Zeile), der
|
||||||
|
Loeschdialog mit seinen zwei Zahlen, die Mitgliederliste im Gruppen-Detail, die
|
||||||
|
Freigabe-Matrix (Module- und Gruppenachse), das Benutzer-Detail mit seinen zwei
|
||||||
|
unabhaengigen Antworten, der Zaehler `defaultMarkerMoved` im Abgleich-Bericht des
|
||||||
|
Verzeichnis-Syncs, und die Modulkacheln, die ein frisch angelegter Benutzer nach
|
||||||
|
seiner ersten Anmeldung sieht.
|
||||||
|
|
||||||
|
(g3) Ein eigener, hervorgehobener Abschnitt "Welcher Code deutet Leere als
|
||||||
|
Abwesenheit", mit je Stelle der Richtung der Gefahr. Die Vorarbeit aus Befund I
|
||||||
|
dieses Plans ist der Ausgangspunkt und ausdruecklich NICHT die vollstaendige Liste —
|
||||||
|
beide Dateien werden dafuer noch einmal durchgesehen, und was dabei zusaetzlich
|
||||||
|
auffaellt, kommt dazu. Vier Punkte muessen darin auf jeden Fall vorkommen:
|
||||||
|
|
||||||
|
- `reassignDefaultBeforeDelete` — zerstoerend und still. Zwei getrennte Stellen
|
||||||
|
liefern `false`: die Gruppe selbst ist nicht sichtbar, oder es ist kein
|
||||||
|
Ersatzkandidat sichtbar. Der Aufrufer im Verzeichnis-Sync loescht die Gruppe
|
||||||
|
danach in beiden Faellen trotzdem, und die Loeschung nimmt ueber die
|
||||||
|
Kaskadenregeln Mitgliedschaften und Modulfreigaben mit. Das ist der Befund D aus
|
||||||
|
der ldap-Kritik, und ihn zu schliessen ist ein Hauptzweck dieses Durchlaufs.
|
||||||
|
- `ensureDefaultGroup` — die einzige Stelle des Bereichs, an der zu wenig Lesen zu
|
||||||
|
ZU VIEL Schreiben fuehrt. Der Waechter ist umgekehrt gepolt: null gelesene
|
||||||
|
Gruppen heisst nicht "nichts zu tun", sondern "alles neu aufbauen". Bliebe der
|
||||||
|
Zaehler ungebunden waehrend der Schreibteil gebunden liefe, legte die Methode
|
||||||
|
fuer einen Mandanten, der bereits Gruppen hat, eine zweite Standardgruppe an,
|
||||||
|
naehme alle seine Benutzer hinein und verteilte Freigaben fuer alle aktiven
|
||||||
|
Module — eine stille Ausweitung von Berechtigungen, ausgeloest durch ein zu
|
||||||
|
kleines Leseergebnis. Der partielle Eindeutigkeitsindex faengt einen Teil der
|
||||||
|
Faelle ab und liefert dann `null`; die Faelle, in denen der Mandant Gruppen, aber
|
||||||
|
keine markierte Standardgruppe hat, faengt er nicht ab. Genau deshalb muessen
|
||||||
|
Zaehler und Transaktion gemeinsam gebunden werden, nie einzeln.
|
||||||
|
- `getImpact` — die Zahlen des Loeschdialogs. Zwei Zaehlungen ohne Mandantenfilter,
|
||||||
|
die bei Leere 0 und 0 melden. Der Administrator entscheidet auf dieser Grundlage
|
||||||
|
ueber eine kaskadierende Loeschung und bekommt "keine Mitglieder, keine
|
||||||
|
Freigaben" fuer eine volle Gruppe angezeigt.
|
||||||
|
- `addUserToDefaultGroup` — stilles Zurueckkehren ohne sichtbare Standardgruppe.
|
||||||
|
Jeder neu angelegte Benutzer landet dann in keiner Gruppe und sieht nach seiner
|
||||||
|
ersten Anmeldung kein einziges Modul. Nicht zerstoerend, aber lautlos und in der
|
||||||
|
Wirkung ein Berechtigungsverlust.
|
||||||
|
|
||||||
|
Zusaetzlich die Gegenrichtung festhalten: die Aktivierungspruefung beim Erteilen
|
||||||
|
einer Freigabe und die Mandanten-Gegenpruefung vor jedem Erteilen werfen bei Leere
|
||||||
|
LAUT und sind damit die harmlosen Stellen des Bereichs. Und die Entlastung: die
|
||||||
|
Freigabe-Matrix schaltet je Zelle einzeln, es gibt keinen Sammel-Abgleich gegen den
|
||||||
|
gelesenen Zustand — ein zu kleines Leseergebnis fuehrt dort zu einer leeren
|
||||||
|
Anzeige, nicht zu einem Massen-Entzug. Das ist ausdruecklich am Frontend
|
||||||
|
nachgesehen und nicht aus dem Backend geschlossen.
|
||||||
|
|
||||||
|
(g4) Ein Abschnitt "Was dieser Durchlauf bewusst nicht loest" mit: der offenen Frage
|
||||||
|
`req.tenantPrisma`, die auch dieser Bereich nicht entscheidet; und der Feststellung,
|
||||||
|
dass die Policies auf `GroupMembership` und `ModuleGrant` die jeweils zweite
|
||||||
|
Referenz (Benutzerseite bzw. Gruppenseite) nachweislich nicht pruefen — die
|
||||||
|
Anwendungspruefungen bleiben deshalb der primaere Schutz und werden nicht durch die
|
||||||
|
Datenbank ersetzt.
|
||||||
|
|
||||||
|
(g5) Ein Satz zur Fortschreibung des ldap-Abschnitts: der dort als offen gefuehrte
|
||||||
|
Befund D wird durch diesen Durchlauf geschlossen. Der Vermerk selbst wird in
|
||||||
|
Aufgabe 3 gesetzt, wenn die Schliessung tatsaechlich vorliegt — nicht hier auf
|
||||||
|
Vorrat.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>set -o pipefail && TESSERA_SCRATCH_ADMIN_URL="postgresql://tessera:tessera_dev@$(docker inspect tessera-ctl-db-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$v.IPAddress}}{{end}}'):5432/postgres" node apps/api/scripts/rls-scratch-check.mjs | tee "${TMPDIR:-/tmp}/rls-groups-check.log" && grep -q "group-ungebunden-null-zeilen: bestanden" "${TMPDIR:-/tmp}/rls-groups-check.log" && grep -q "groupmembership-folgt-join-auf-group: bestanden" "${TMPDIR:-/tmp}/rls-groups-check.log" && grep -q "modulegrant-fremde-gruppe-trotz-eigener-mandantenkennung-erlaubt: bestanden" "${TMPDIR:-/tmp}/rls-groups-check.log" && grep -q "mindestens-eine-transaktionsform-traegt-den-mandantenkontext: bestanden" "${TMPDIR:-/tmp}/rls-groups-check.log" && grep -q "^## Bereich groups" docs/mandantentrennung-etappe2-fehlerrichtung.md && npm --prefix apps/api run test && npm --prefix apps/api run type-check</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Das Werkzeug meldet alle Pruefungen bestanden und beendet sich mit 0 — die 13 aus den Vorlaeufern plus die neuen dieses Bereichs. Die Ausgabe nennt namentlich, welche Transaktionsform den Mandantenkontext auf derselben Verbindung traegt und welche nicht. Der Abschnitt `## Bereich groups` in der Kritikschrift existiert, traegt die tatsaechlich beobachteten Werte (nicht erwartete), nennt je Pfad ein konkretes Signal und enthaelt die vier Pflichtpunkte einschliesslich der Stelle, an der zu wenig Lesen zu viel Schreiben ausloest. Die 719 Tests und die Typpruefung sind unveraendert gruen. Kein Dienstcode wurde angefasst.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Aufgabe 2: groups.service.ts binden, die Transaktionen tragfaehig machen, die Absicherung sehend machen</name>
|
||||||
|
<files>apps/api/src/prisma/prisma-tenant.extension.ts, apps/api/src/prisma/rls-access-inventory.spec.ts, apps/api/src/groups/groups.service.spec.ts, apps/api/src/groups/groups.service.ts, docs/mandantentrennung-zugriffsklassifikation.md</files>
|
||||||
|
<behavior>
|
||||||
|
- Jede der zwoelf Methoden von `GroupsService` erzeugt ihren Mandantenkontext aus dem uebergebenen Mandanten und fuehrt ihre Abfragen darauf aus; je Methode weist ein Test nach, dass der Kontext mit genau diesem Mandanten erzeugt wurde.
|
||||||
|
- Die beiden mehrschrittigen Aenderungen (Standardmarkierung umsetzen; Standardmarkierung vor einer Loeschung verschieben) laufen weiterhin als EINE Transaktion und tragen dabei den Mandantenkontext; ein Test weist nach, dass beide Teilschritte am gebundenen Client landen.
|
||||||
|
- Der Aufbau einer Standardgruppe laeuft weiterhin als EINE Transaktion ueber alle vier Schritte (Gruppe, Mitgliedschaften, Aktivierungen lesen, Freigaben) und traegt dabei den Mandantenkontext; ein Test weist nach, dass auch die Zaehlung davor am gebundenen Client landet — Zaehler und Schreibteil duerfen nie unterschiedlich gebunden sein.
|
||||||
|
- Der Aufbau einer Standardgruppe bleibt fuer alle vier Aufrufwege unveraendert wirksam: Mandantenanlage, Startanlage des Vorgabe-Mandanten, Startreparatur je Mandant in der Schleife, und der Aufruf nach dem Loeschzweig des Verzeichnis-Abgleichs. Die vorhandenen Tests dieser Wege bleiben gruen.
|
||||||
|
- Das Verschieben der Standardmarkierung vor einer Loeschung findet den Ersatzkandidaten weiterhin deterministisch (bevorzugt die gleichnamige Standardgruppe, sonst die aelteste andere) und meldet `false` ausschliesslich dann, wenn es tatsaechlich keinen gibt.
|
||||||
|
- Die Mitgliedschaftsanlage in der Standardgruppe nimmt einen Zielbenutzer nur auf, wenn er zum selben Mandanten gehoert; ein Test mit einem fremden Benutzer weist nach, dass keine Mitgliedschaft entsteht und die Methode nicht wirft.
|
||||||
|
- Alle 42 Bestandstests der Datei bleiben gruen, insbesondere die Uebersetzung der Eindeutigkeits- und Nichtgefunden-Fehlercodes, die Namenssperre fuer importierte Gruppen und die Beschraenkung des Mitglieder-Entfernens auf manuelle Mitgliedschaften.
|
||||||
|
- Die Bestandsaufnahme-Pruefung sieht Modellzugriffe ueber den Rueckgabeparameter einer interaktiven Transaktion und ordnet sie gebunden oder ungebunden zu.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
Reihenfolge: erst das Fundament aus der Messung, dann die Absicherung, dann die
|
||||||
|
Tests, dann die Umstellung. Fundstellen werden ueber ihren Inhalt aufgesucht, nicht
|
||||||
|
ueber Zeilennummern — die Datei verschiebt sich waehrend ihrer eigenen Bearbeitung.
|
||||||
|
|
||||||
|
SCHRITT 1, das Fundament, `apps/api/src/prisma/prisma-tenant.extension.ts`.
|
||||||
|
Die Entscheidung faellt aus dem Ergebnis der Pruefung
|
||||||
|
`mindestens-eine-transaktionsform-traegt-den-mandantenkontext` aus Aufgabe 1, nicht
|
||||||
|
aus einer Vermutung:
|
||||||
|
|
||||||
|
- Traegt die Array-Form auf dem gebundenen Client den Kontext, bleiben die beiden
|
||||||
|
mehrschrittigen Aenderungen bei ihrer heutigen Form und laufen einfach auf dem
|
||||||
|
gebundenen Client. Kein neues Hilfsmittel noetig.
|
||||||
|
- Traegt die interaktive Form auf dem gebundenen Client den Kontext, gilt dasselbe
|
||||||
|
fuer den Aufbau der Standardgruppe.
|
||||||
|
- Traegt eine der beiden Formen ihn NICHT, bekommt diese Datei ein zweites,
|
||||||
|
ausgeschriebenes Hilfsmittel `withTenantTransaction(prisma, tenantId, fn)`: es
|
||||||
|
oeffnet eine interaktive Transaktion auf dem UNgebundenen Client, setzt als
|
||||||
|
erste Anweisung den Mandantenkontext ueber ein getaggtes Roh-Template auf dem
|
||||||
|
Transaktionsparameter selbst (parametrisiert, nie zusammengebauter Text — die
|
||||||
|
Injektionsfestigkeit aus T-02-05 bleibt erhalten) und reicht denselben
|
||||||
|
Transaktionsparameter an `fn` weiter, sodass jede Folgeanweisung auf derselben
|
||||||
|
Verbindung laeuft. Ueber der Funktion steht ein Absatz, der die in Aufgabe 1
|
||||||
|
beobachteten Werte nennt und daraus begruendet, warum es sie gibt.
|
||||||
|
|
||||||
|
In JEDEM dieser Faelle wird der Vorbehalt im Kopfkommentar der Datei
|
||||||
|
fortgeschrieben: er sagt heute, zum Zeitpunkt der ldap-Umstellung habe kein
|
||||||
|
gebundener Aufrufer eine eigene Transaktion gehabt, und verlangt eine erneute
|
||||||
|
Pruefung vor jedem neuen Fall. Diese Pruefung hat jetzt stattgefunden; ihr
|
||||||
|
Ergebnis gehoert an genau diese Stelle, damit der naechste Bereich nicht wieder
|
||||||
|
bei null anfaengt.
|
||||||
|
|
||||||
|
SCHRITT 2, die Absicherung sehend machen, `apps/api/src/prisma/rls-access-inventory.spec.ts`
|
||||||
|
(Befund B). Die Fundstellensuche bekommt eine dritte Erkennung fuer Modellzugriffe
|
||||||
|
ueber den Rueckgabeparameter einer interaktiven Transaktion. Je Datei werden die
|
||||||
|
Callback-Parameternamen solcher Transaktionen eingesammelt und danach ihre
|
||||||
|
`<Parameter>.<Modell>`-Vorkommen gesucht. Die Zuordnung richtet sich nach dem
|
||||||
|
Empfaenger der Transaktion: laeuft sie auf einem Namen, der aus einer erkannten
|
||||||
|
Bindungszuweisung stammt, oder ueber das in Schritt 1 gegebenenfalls ergaenzte
|
||||||
|
Hilfsmittel, zaehlen die Zugriffe als gebunden; laeuft sie auf dem ungebundenen
|
||||||
|
Client, zaehlen sie als ungebunden. Die vorhandene Kommentarfilterung gilt
|
||||||
|
unveraendert auch fuer diese Erkennung.
|
||||||
|
|
||||||
|
Die Erkennung bekommt dieselbe offen gehaltene Grenze wie die zweite: jede
|
||||||
|
interaktive Transaktion im Quelltext muss einer der erkannten Empfaengerformen
|
||||||
|
entsprechen oder in einer kurzen, begruendeten Ausnahmeliste stehen — sonst schlaegt
|
||||||
|
eine eigene Pruefung fehl. Gemessen zur Planungszeit gibt es im gesamten
|
||||||
|
API-Quelltext genau eine interaktive Transaktion, und sie steht in der Datei, die
|
||||||
|
diese Aufgabe umstellt; die Ausnahmeliste startet deshalb leer. Die
|
||||||
|
Fehlermeldung nennt je Verstoss Datei und Anzahl, damit sie ohne Ratespiel behebbar
|
||||||
|
ist.
|
||||||
|
|
||||||
|
Diese Erweiterung wird die Paarmenge des Quelltexts vergroessern: mindestens das
|
||||||
|
Paar aus dieser Datei und dem Modell der Mandanten-Modulaktivierungen wird erstmals
|
||||||
|
sichtbar und fehlt heute im Klassifikationsdokument. Wie viele Paare es am Ende
|
||||||
|
sind, wird der Ausgabe der fehlschlagenden Pruefung entnommen, nicht geschaetzt.
|
||||||
|
|
||||||
|
SCHRITT 3, die Tests scharf machen, `apps/api/src/groups/groups.service.spec.ts`
|
||||||
|
(Befund C). Die Datei hat heute keinerlei Ersatz fuer die Kontextbindung; nach der
|
||||||
|
Umstellung wuerde jeder Test am fehlenden Erweiterungsaufruf abstuerzen — rot aus dem
|
||||||
|
falschen Grund. Sie bekommt deshalb einen Ersatz nach dem in 260909-ipc etablierten
|
||||||
|
Muster mit ZWEI unterscheidbaren Clients: der ungebundene Ersatz ist der vorhandene
|
||||||
|
handgeschriebene Speicher-Fake, der gebundene ist ein davon unterscheidbares Objekt,
|
||||||
|
das auf DENSELBEN Speicher zugreift und mitschreibt, welche Aufrufe ueber ihn
|
||||||
|
liefen. Nur so bleiben die 42 Bestandstests aussagefaehig UND ein vergessener
|
||||||
|
Bindungsaufruf faellt auf. Der Fake beherrscht bereits beide Transaktionsformen und
|
||||||
|
reicht sich selbst als Transaktionsparameter durch — er wird erweitert, nicht
|
||||||
|
ersetzt.
|
||||||
|
|
||||||
|
Darauf die in `<behavior>` beschriebenen Erwartungen als Tests schreiben, je Methode
|
||||||
|
mindestens einen Bindungsnachweis, dazu die drei Transaktionsnachweise und den
|
||||||
|
Nachweis fuer den fremden Zielbenutzer. Diese Tests laufen zunaechst rot. Ein Test,
|
||||||
|
der auch bei einer weggelassenen Bindung gruen bliebe, ist kein Nachweis und wird
|
||||||
|
umgeschrieben, bis er es ist.
|
||||||
|
|
||||||
|
SCHRITT 4, `apps/api/src/groups/groups.service.ts` umstellen. Alle 21
|
||||||
|
Modellzugriffe und die fuenf Zugriffe innerhalb der Transaktion des
|
||||||
|
Standardgruppen-Aufbaus laufen danach ueber den Mandantenkontext des jeweils
|
||||||
|
uebergebenen Mandanten. Je Methode wird der Kontext einmal am Methodenkopf erzeugt,
|
||||||
|
in der Schreibweise der Bestandsstellen aus `ldap.service.ts` und
|
||||||
|
`ldap-config.service.ts`, damit die Typpruefung gruen bleibt und die
|
||||||
|
Fundstellenerkennung aus Schritt 2 sie je Datei zuordnen kann. Gebundene Clients
|
||||||
|
werden NICHT zwischen Methoden weitergereicht.
|
||||||
|
|
||||||
|
Drei Stellen brauchen besondere Aufmerksamkeit:
|
||||||
|
|
||||||
|
- Der Aufbau der Standardgruppe: die Zaehlung davor und die Transaktion danach
|
||||||
|
muessen GEMEINSAM gebunden sein. Eine halb umgestellte Fassung ist gefaehrlicher
|
||||||
|
als die heutige, weil sie aus einem zu kleinen Leseergebnis eine zusaetzliche
|
||||||
|
Standardgruppe samt Modulfreigaben erzeugen wuerde — die Stelle, an der zu wenig
|
||||||
|
Lesen zu viel Schreiben ausloest. Das Abfangen des Eindeutigkeitsfehlers und
|
||||||
|
seine Uebersetzung in "nichts zu tun" bleiben unveraendert.
|
||||||
|
- Das Verschieben der Standardmarkierung vor einer Loeschung: die drei
|
||||||
|
Lesezugriffe und die zweischrittige Aenderung gehoeren an denselben gebundenen
|
||||||
|
Client. Das bewusste Weglassen der werfenden Ownership-Pruefung bleibt
|
||||||
|
unveraendert — eine fremde oder nicht markierte Gruppe ist weiterhin ein
|
||||||
|
folgenloses Nichttun mit Rueckgabe `false` und darf einen Abgleichlauf nicht
|
||||||
|
abbrechen.
|
||||||
|
- Die Mitgliedschaftsanlage in der Standardgruppe: hier kommt die fehlende
|
||||||
|
Mandantenpruefung des Zielbenutzers dazu (Befund E, T-JTS-02), nach dem Vorbild
|
||||||
|
der Methode zum manuellen Hinzufuegen von Mitgliedern zwei Methoden hoeher —
|
||||||
|
Benutzer laden, auf den Mandanten filtern, bei keinem Treffer folgenlos
|
||||||
|
zurueckkehren statt zu werfen. Warum das noetig ist, obwohl die Datenbank eine
|
||||||
|
Regel auf dieser Tabelle hat, steht als ausgeschriebener Absatz an der Stelle:
|
||||||
|
die ausgelieferte Regel prueft die Gruppenseite und nicht die Benutzerseite, und
|
||||||
|
das ist in Aufgabe 1 gemessen.
|
||||||
|
|
||||||
|
Die vorhandenen Filter auf den Mandanten bleiben ueberall stehen. Sie sind das erste
|
||||||
|
Netz, die Regel in der Datenbank das zweite — an keiner Stelle wird ein
|
||||||
|
Anwendungsfilter mit der Begruendung entfernt, die Datenbank erledige das jetzt.
|
||||||
|
|
||||||
|
SCHRITT 5, `docs/mandantentrennung-zugriffsklassifikation.md` fuer diese Datei
|
||||||
|
nachziehen: die vier vorhandenen Zeilen bekommen ihren gemessenen Stand, das durch
|
||||||
|
Schritt 2 neu sichtbar gewordene Paar bekommt eine eigene Zeile mit Klasse,
|
||||||
|
gemessenem Stand und einer Begruendung, die sagt, warum es bis heute unsichtbar war.
|
||||||
|
Die Klassen-Verteilung wird nachgerechnet, nicht fortgeschrieben. Die Quelle fuer
|
||||||
|
alle eingetragenen Stand-Werte ist die Ausgabe der Pruefung aus Schritt 2, nicht eine
|
||||||
|
Schaetzung.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api run test -- src/groups/groups.service.spec.ts src/prisma/rls-access-inventory.spec.ts && npm --prefix apps/api run type-check && npm --prefix apps/api run test</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Die 42 Bestandstests der Datei sind weiterhin gruen, dazu die neuen Bindungs- und Transaktionsnachweise und der Nachweis fuer den fremden Zielbenutzer. Die Bestandsaufnahme-Pruefung sieht Zugriffe ueber den Transaktionsparameter, prueft die Stand-Spalte gegen den Quelltext und ist gruen — mit ergaenztem Dokument, nicht mit geloeschten Zeilen. Der gesamte Testlauf zeigt mindestens 719 Tests gruen, die Typpruefung liefert 0.</done>
|
||||||
|
<reversibility rating="reversible">Reine Dienst-, Test- und Werkzeugaenderung ohne Schema-, Migrations- oder Konfigurationsanteil; ein einzelner Commit laesst sich zuruecknehmen.</reversibility>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Aufgabe 3: module-grants.service.ts binden und beide Dokumente schliessen</name>
|
||||||
|
<files>apps/api/src/groups/module-grants.service.spec.ts, apps/api/src/groups/module-grants.service.ts, docs/mandantentrennung-zugriffsklassifikation.md, docs/mandantentrennung-etappe2-fehlerrichtung.md</files>
|
||||||
|
<behavior>
|
||||||
|
- Alle fuenf Methoden von `ModuleGrantsService` erzeugen ihren Mandantenkontext aus dem uebergebenen Mandanten und fuehren ihre Abfragen darauf aus; je Methode weist ein Test nach, dass der Kontext mit genau diesem Mandanten erzeugt wurde.
|
||||||
|
- Die Mandanten-Gegenpruefung vor jedem Erteilen bleibt bestehen und bleibt wirksam: eine Gruppe oder ein Benutzer eines fremden Mandanten fuehrt weiterhin zu einer Nichtgefunden-Antwort, und ein Test haelt fest, dass diese Pruefung nicht durch die Datenbankregel ersetzt wurde.
|
||||||
|
- Die Entweder-oder-Regel, die Aktivierungspruefung, das Abfangen des Eindeutigkeitsfehlers beim Doppelklick und die Protokollzeile je erfolgreicher Aenderung bleiben unveraendert.
|
||||||
|
- Die beiden Datenlieferungen (Freigabe-Matrix, Benutzer-Detail) behalten Form und Sortierung exakt bei; der Anzeigename faellt weiterhin per Nullish auf den Gruppennamen zurueck.
|
||||||
|
- Alle 28 Bestandstests der Datei bleiben gruen.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
SCHRITT 1, Tests zuerst, `apps/api/src/groups/module-grants.service.spec.ts`. Wie in
|
||||||
|
Aufgabe 2: die Datei hat heute keinen Ersatz fuer die Kontextbindung (Befund C) und
|
||||||
|
bekommt denselben Aufbau mit zwei unterscheidbaren Clients ueber demselben
|
||||||
|
Speicher-Fake. Darauf je Methode ein Bindungsnachweis sowie der Nachweis, dass die
|
||||||
|
Mandanten-Gegenpruefung vor dem Erteilen erhalten geblieben ist. Diese Tests laufen
|
||||||
|
zunaechst rot.
|
||||||
|
|
||||||
|
SCHRITT 2, `apps/api/src/groups/module-grants.service.ts` umstellen. Alle 13
|
||||||
|
Modellzugriffe in fuenf Methoden laufen danach ueber den Mandantenkontext des
|
||||||
|
uebergebenen Mandanten; je Methode wird er einmal am Methodenkopf erzeugt. Bei den
|
||||||
|
beiden Datenlieferungen bedeutet das, dass alle parallel abgesetzten Teilabfragen
|
||||||
|
denselben gebundenen Client benutzen — es entsteht kein zweiter.
|
||||||
|
|
||||||
|
Der Kommentarblock ueber der Mitgliedschaftsabfrage im Benutzer-Detail, der heute
|
||||||
|
begruendet, warum an dieser Stelle KEIN Mandantenkontext erzeugt wird, ist nach der
|
||||||
|
Umstellung falsch und wird durch einen Absatz ersetzt, der den neuen Stand
|
||||||
|
beschreibt: der Kontext wird gesetzt, der Filter ueber die Beziehung zur Gruppe
|
||||||
|
bleibt zusaetzlich stehen, und die Regel auf dieser Tabelle bezieht ihre Sichtbarkeit
|
||||||
|
ueber die Gruppenseite. Ein stehen gebliebener alter Kommentar waere die naechste
|
||||||
|
stille Falle: er wuerde den naechsten Leser ueber den tatsaechlichen Zustand
|
||||||
|
taeuschen.
|
||||||
|
|
||||||
|
Die Mandanten-Gegenpruefung vor jedem Erteilen bleibt ausdruecklich erhalten und
|
||||||
|
bekommt einen Absatz mit dem in Aufgabe 1 gemessenen Grund: die Regel auf der
|
||||||
|
Freigabetabelle prueft ausschliesslich die Mandantenkennung der Zeile selbst und
|
||||||
|
nicht die referenzierte Gruppe; eine Zeile mit korrekter eigener Mandantenkennung,
|
||||||
|
die auf die Gruppe eines fremden Mandanten zeigt, verletzt sie nachweislich nicht.
|
||||||
|
Die Anwendungspruefung ist damit der einzige Schutz gegen diese Form der
|
||||||
|
Rechteausweitung und darf nicht als "macht jetzt die Datenbank" entfallen
|
||||||
|
(Befund F, T-JTS-03).
|
||||||
|
|
||||||
|
SCHRITT 3, `docs/mandantentrennung-zugriffsklassifikation.md` schliessen:
|
||||||
|
|
||||||
|
- Die fuenf Zeilen dieser Datei bekommen ihren gemessenen Stand.
|
||||||
|
- Die Bereichsuebersicht wird fuer `groups` NEU GEMESSEN, nicht fortgeschrieben:
|
||||||
|
beide Zaehlungen (ungebunden, gebunden) mit den im Kopf der Uebersicht
|
||||||
|
dokumentierten Befehlen erneut ausfuehren, beide Werte eintragen und die
|
||||||
|
Summenzeile nachrechnen. In die Hinweisspalte kommt, was sich geaendert hat.
|
||||||
|
- Der Abschnitt zum Hintergrunddienst als Falle wird beim Eintrag zum
|
||||||
|
Verzeichnis-Abgleich fortgeschrieben: die dort als offene Reihenfolgebedingung
|
||||||
|
fuer Etappe 4 gefuehrte Uebergabe der Standardgruppe ist mit diesem Durchlauf
|
||||||
|
geschlossen.
|
||||||
|
- Im Abschnitt "Was diese Etappe NICHT entscheidet" wird festgehalten, dass auch
|
||||||
|
der Bereich `groups` den dienst-internen Weg gewaehlt hat und die Frage
|
||||||
|
`req.tenantPrisma` weiterhin fuer die uebrigen Bereiche offen bleibt.
|
||||||
|
- Die Zaehlung der Paare in der Ueberschrift der Klassen-Verteilung wird an die
|
||||||
|
tatsaechliche Zahl angepasst, die die Pruefung meldet.
|
||||||
|
|
||||||
|
SCHRITT 4, `docs/mandantentrennung-etappe2-fehlerrichtung.md` schliessen: im
|
||||||
|
ldap-Abschnitt (e) wird der dort als offen gefuehrte Befund zur Uebergabe der
|
||||||
|
Standardgruppe als durch diesen Durchlauf erledigt vermerkt, mit Verweis auf den
|
||||||
|
`groups`-Abschnitt. Der bestehende Text wird dabei nicht geloescht — die urspruengliche
|
||||||
|
Feststellung bleibt lesbar und bekommt einen Nachtrag; eine stillschweigend
|
||||||
|
umgeschriebene Vorgeschichte waere fuer die spaeteren Etappen wertlos.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm --prefix apps/api run test -- src/groups/module-grants.service.spec.ts src/prisma/rls-access-inventory.spec.ts && npm --prefix apps/api run type-check && npm --prefix apps/api run test && test "$(awk -F'|' '$2 ~ /groups\/(groups|module-grants)\.service\.ts/ { gsub(/ /,"",$5); print $5 }' docs/mandantentrennung-zugriffsklassifikation.md | sort -u)" = "gebunden" && test "$(awk -F'|' '$2 ~ /groups\/(groups|module-grants)\.service\.ts/ { print }' docs/mandantentrennung-zugriffsklassifikation.md | wc -l)" -ge 9 && test -z "$(git status --porcelain -- apps/api/prisma/schema.prisma apps/api/prisma/migrations docker-compose.yml docker-compose.dev.yml docker-compose.prod.yml docker-compose.ci.yml)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Die 28 Bestandstests der Datei plus die neuen Bindungsnachweise sind gruen. Die Bestandsaufnahme-Pruefung ist mit aktualisiertem Dokument gruen. Jede Zeile des Bereichs `groups` in der Bestandsaufnahme traegt den Stand `gebunden`, und es sind mindestens die neun bisherigen Zeilen. Der gesamte Testlauf zeigt mindestens 719 Tests gruen, die Typpruefung liefert 0. Schema, Migrationen und alle vier Compose-Dateien sind unberuehrt.</done>
|
||||||
|
<reversibility rating="reversible">Dienst-, Test- und Dokumentaenderung ohne Schema- oder Konfigurationsanteil.</reversibility>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Vertrauensgrenzen
|
||||||
|
|
||||||
|
| Grenze | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| Browser/Administrator -> API | Der Mandant stammt aus dem Sitzungsnachweis; Gruppen-, Benutzer- und Modulkennungen stammen aus Pfad und Rumpf der Anfrage und sind ungeprueft fremd |
|
||||||
|
| API -> PostgreSQL | Heute Rolle `tessera` mit BYPASSRLS; die Regeln wirken erst nach Etappe 4. Bis dahin ist die Bindung Vorsorge, keine Durchsetzung |
|
||||||
|
| Verzeichnis (AD) -> API | Nur lesend; Verzeichnisantworten loesen den Loeschzweig aus, der die Uebergabe der Standardgruppe in diesem Bereich aufruft |
|
||||||
|
| Startvorgang -> PostgreSQL | Der Startvorgang legt Mandanten und Standardgruppen an, bevor irgendeine Anfrage existiert |
|
||||||
|
| Werkzeug -> PostgreSQL | Das Wegwerf-Werkzeug spricht dieselbe Instanz an wie die Entwicklungsdatenbank |
|
||||||
|
|
||||||
|
## STRIDE-Register
|
||||||
|
|
||||||
|
| Kennung | Kategorie | Bauteil | Schwere | Umgang | Massnahme |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| T-JTS-01 | Information Disclosure | Lesen von `Group`, `GroupMembership`, `ModuleGrant`, `TenantModuleActivation` in beiden Diensten | high | mitigate | Alle 34 sichtbaren und 5 bisher unsichtbaren Modellzugriffe laufen nach Aufgabe 2/3 ueber den Mandantenkontext; dass die vier ausgelieferten Regeln tragen, wird in Aufgabe 1 unter einer Rolle ohne BYPASSRLS gemessen statt behauptet. |
|
||||||
|
| T-JTS-02 | Elevation of Privilege | Mitgliedschaftsanlage in der Standardgruppe ohne Mandantenpruefung des Zielbenutzers | medium | mitigate | Die Regel auf der Mitgliedschaftstabelle prueft nachweislich nur die Gruppenseite. Aufgabe 2 zieht die Mandantenpruefung des Zielbenutzers nach dem Vorbild des manuellen Hinzufuegens nach; Aufgabe 1 misst die Luecke in der Regel, damit die Massnahme nicht als ueberfluessig zurueckgebaut wird. Heute ueber keinen Aufrufer erreichbar, deshalb medium und nicht high. |
|
||||||
|
| T-JTS-03 | Elevation of Privilege | Freigabe mit eigener Mandantenkennung auf die Gruppe eines fremden Mandanten | high | mitigate | Die Regel auf der Freigabetabelle prueft nur die Mandantenkennung der Zeile, nicht die referenzierte Gruppe. Die Anwendungspruefung vor jedem Erteilen bleibt der Schutz; Aufgabe 1 misst die Luecke, Aufgabe 3 haelt sie mit einem Test fest und begruendet sie am Ort. |
|
||||||
|
| T-JTS-04 | Denial of Service (selbst verursacht, zerstoerend) | Uebergabe der Standardmarkierung vor der Loeschung im Verzeichnis-Abgleich | high | mitigate | Ein zu kleines Leseergebnis meldet still "kein Ersatzkandidat", der Aufrufer loescht die Gruppe trotzdem, und die Kaskade nimmt Mitgliedschaften und Freigaben mit. Aufgabe 2 bindet beide Lesewege und die zweischrittige Aenderung; Aufgabe 1 haelt Richtung und Signal schriftlich fest. Dies ist der Befund D des ldap-Durchlaufs und eine Reihenfolgebedingung fuer Etappe 4. |
|
||||||
|
| T-JTS-05 | Tampering | Aufbau der Standardgruppe bei halb umgestelltem Zustand | high | mitigate | Der Waechter ist umgekehrt gepolt: ein zu kleines Leseergebnis loest hier ein Schreiben aus statt es zu unterlassen. Eine zweite Standardgruppe samt Mitgliedschaften ALLER Benutzer und Freigaben ALLER aktiven Module waere die Folge. Aufgabe 2 bindet Zaehler und Transaktion zwingend gemeinsam und weist das mit einem eigenen Test nach. |
|
||||||
|
| T-JTS-06 | Denial of Service | Mitgliedschaftsanlage in der Standardgruppe bei nicht sichtbarer Standardgruppe | medium | mitigate | Neue Benutzer landen still in keiner Gruppe und sehen kein Modul. Nicht zerstoerend, aber lautlos; Aufgabe 2 bindet den Lesezugriff, Aufgabe 1 nennt das Signal (die Modulkacheln nach der ersten Anmeldung). |
|
||||||
|
| T-JTS-07 | Repudiation | Zahlen des Loeschdialogs | high | mitigate | Zwei Zaehlungen ohne Mandantenfilter melden bei Leere 0 und 0; der Administrator entscheidet auf dieser Grundlage ueber eine kaskadierende Loeschung. Aufgabe 2 bindet beide Zaehlungen; Aufgabe 1 fuehrt den Dialog als Signalort. |
|
||||||
|
| T-JTS-08 | Denial of Service | Startvorgang: Startanlage und Startreparatur der Standardgruppen | medium | mitigate | Der Aufbau der Standardgruppe hat vier Aufrufwege, zwei davon im Startvorgang. Eine Bindung, die den Kontext nicht auf dieselbe Verbindung bringt, koennte den Start beschaedigen. Aufgabe 1 misst die Transaktionsformen VOR der Umstellung; Aufgabe 2 haelt alle vier Wege mit Tests fest. |
|
||||||
|
| T-JTS-09 | Spoofing | Regeltext im Messwerkzeug | medium | mitigate | Die gemessenen Regeln werden aus den beiden ausgelieferten Migrationen extrahiert, nicht im Werkzeug nachgetippt; findet die Extraktion eine der vier nicht, meldet das Werkzeug eine fehlgeschlagene Pruefung statt still weiterzumessen. |
|
||||||
|
| T-JTS-10 | Tampering | Wegwerf-Werkzeug trifft dieselbe Datenbankinstanz wie die Entwicklung | high | mitigate | Der Name der Wegwerf-Datenbank bleibt fest verdrahtet und nicht steuerbar; der neue Abschnitt legt seine Tabellen ausschliesslich dort an und raeumt mit dem vorhandenen Abbau ab (T-EOR-07 unveraendert gueltig). |
|
||||||
|
| T-JTS-11 | Repudiation | Testsuiten, die eine fehlende Bindung nicht bemerken koennen | high | mitigate | Beide Testdateien haben heute gar keinen Ersatz fuer die Kontextbindung. Aufgabe 2 und 3 fuehren zwei unterscheidbare Clients ueber demselben Speicher ein; ein Test, der auch ohne Bindung gruen bliebe, wird umgeschrieben, bis er rot werden kann. |
|
||||||
|
|
||||||
|
**Paketlegitimitaet:** Dieser Durchlauf installiert kein Paket (npm/pip/cargo). Das
|
||||||
|
Legitimitaetstor greift daher nicht; es wird kein Lieferketten-Eintrag erfunden.
|
||||||
|
|
||||||
|
**Schema-Tor:** `apps/api/prisma/schema.prisma` und `apps/api/prisma/migrations/`
|
||||||
|
werden nicht angefasst, es entsteht keine Migration. Das Schema-Tor greift nicht.
|
||||||
|
Sollte sich bei der Ausfuehrung zeigen, dass eine Schemaaenderung unvermeidbar ist,
|
||||||
|
ist das ein Abbruchgrund: melden statt machen.
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. `npm --prefix apps/api run test` -> mindestens 719 Tests gruen (Ausgangsstand am
|
||||||
|
2026-09-09 gemessen: 53 Dateien, 719 Tests, 4,94 s).
|
||||||
|
2. `npm --prefix apps/api run type-check` -> Rueckgabewert 0.
|
||||||
|
3. Das Wegwerf-Werkzeug meldet alle Pruefungen bestanden, einschliesslich der neuen
|
||||||
|
des Bereichs `groups` und der einen Transaktionspruefung, und beendet sich mit 0.
|
||||||
|
4. Die Bestandsaufnahme-Pruefung ist gruen, obwohl Fundstellen von ungebunden auf
|
||||||
|
gebunden gewechselt sind UND mindestens eine bisher unsichtbare Fundstelle
|
||||||
|
hinzugekommen ist — die Absicherung ist mitgewachsen, nicht ausgehoehlt.
|
||||||
|
5. Jede Zeile des Bereichs `groups` in der Bestandsaufnahme traegt den Stand
|
||||||
|
`gebunden`, maschinell gegen den Quelltext geprueft.
|
||||||
|
6. `git status --porcelain` zeigt keine Aenderung an `apps/api/prisma/schema.prisma`,
|
||||||
|
an `apps/api/prisma/migrations/` oder an einer der vier Compose-Dateien. Die
|
||||||
|
Umgebungsdatei wird nicht geoeffnet und nicht geaendert; `DATABASE_URL` bleibt
|
||||||
|
unveraendert auf der Rolle `tessera`.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Der Bereich `groups` ist umgestellt: 34 sichtbare und 5 bisher unsichtbare
|
||||||
|
Modellzugriffe in zwei Dateien laufen mandantengebunden; kein Zugriff dieses
|
||||||
|
Bereichs bleibt bewusst uebergreifend, und dass dem so ist, wurde gemessen und
|
||||||
|
nicht angenommen.
|
||||||
|
- Welche Transaktionsform den Mandantenkontext traegt, ist an der Datenbank gemessen,
|
||||||
|
BEVOR die drei Transaktionsstellen darauf umgestellt wurden; das Ergebnis steht im
|
||||||
|
Kopfkommentar der Erweiterung, damit der naechste Bereich es nicht erneut suchen
|
||||||
|
muss.
|
||||||
|
- Die Uebergabe der Standardgruppe vor einer Gruppenloeschung ist geschlossen; die
|
||||||
|
Reihenfolgebedingung fuer Etappe 4 aus dem ldap-Durchlauf ist erfuellt und in
|
||||||
|
beiden Dokumenten als erfuellt vermerkt.
|
||||||
|
- Die Frage "Woran wuerde ich merken, dass eine umgestellte Abfrage zu wenig
|
||||||
|
liefert?" ist fuer diesen Bereich schriftlich beantwortet, je Pfad mit einem
|
||||||
|
konkreten Signal, und die Antwort stuetzt sich auf eine Messung an den
|
||||||
|
ausgelieferten Regeln.
|
||||||
|
- Die maschinelle Absicherung sieht Zugriffe ueber den Transaktionsparameter; die
|
||||||
|
Erkennungsluecke, die ausgerechnet die Schreibstelle fuer Modulfreigaben verdeckt
|
||||||
|
hat, ist geschlossen.
|
||||||
|
- Beide Testdateien koennen rot werden, wenn eine Fundstelle ungebunden bleibt.
|
||||||
|
- Klassifikationsdokument und maschinelle Absicherung zeigen denselben, gemessenen
|
||||||
|
Stand.
|
||||||
|
- Der Schalter ist unveraendert AUS; Schema, Migrationen und Compose-Dateien sind
|
||||||
|
unberuehrt; am Verzeichnis wurde nichts geaendert.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Erzeuge
|
||||||
|
`.planning/quick/260909-jts-mandantentrennung-etappe-2-bereich-group/260909-jts-SUMMARY.md`,
|
||||||
|
wenn alle drei Aufgaben abgeschlossen sind. Der Bericht haelt fest: die tatsaechlich
|
||||||
|
gemessenen Zahlen (Testanzahl, Fundstellen je Stand, Paarzahl vor und nach der
|
||||||
|
erweiterten Erkennung, Ergebnis des Wegwerf-Werkzeugs), welche Transaktionsform sich
|
||||||
|
als tragfaehig erwiesen hat und welche nicht, die beiden gemessenen Luecken in den
|
||||||
|
ausgelieferten Regeln (Benutzerseite der Mitgliedschaft, Gruppenseite der Freigabe)
|
||||||
|
samt der Massnahme dagegen, und die an spaetere Etappen uebergebenen Punkte.
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user