184 lines
9.0 KiB
Markdown
184 lines
9.0 KiB
Markdown
---
|
|
phase: quick-260909-mir
|
|
plan: 01
|
|
status: complete
|
|
subsystem: database
|
|
tags: [prisma, postgresql, row-level-security, multi-tenancy, nestjs, dkv]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: quick-260909-laa
|
|
provides: forTenant() binding pattern for user-CRUD services plus the two-client test harness (tenders)
|
|
provides:
|
|
- dkv.service.ts fully bound to forTenant() — config paths, invoice history, vehicle master
|
|
- Cross-tenant ownership gate on the DKV export-file download (T-MIR-03), closing a pre-existing IDOR
|
|
- dkv.service.spec.ts created from nothing — the area had no test file at all
|
|
- rls-scratch-check.mjs dkv-area section, including the previously unmeasured parallel-bound-single-ops shape
|
|
- docs/mandantentrennung-etappe2-fehlerrichtung.md "Bereich dkv" section
|
|
- WINDOWS #21 — the DKV scheduler start path carried forward as named debt
|
|
affects: [stage-3-planning, stage-4-preflight, settings-area-quick-task]
|
|
|
|
# Actuals
|
|
actuals:
|
|
tasks: 3
|
|
commits: 3
|
|
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "forTenant() bound once per method (groups/ldap/tenders convention); withTenantTransaction() deliberately NOT introduced — this area has no transaction"
|
|
- "__makeBoundClient() two-client test proof, ported from tender-triage.service.spec.ts into a spec file that did not previously exist"
|
|
- "Ownership gate derived from the only tenant-bound statement of file ownership (DkvInvoiceHistory.exportFilename) rather than from the filename"
|
|
|
|
key-files:
|
|
created:
|
|
- apps/api/src/dkv/dkv.service.spec.ts
|
|
modified:
|
|
- apps/api/scripts/rls-scratch-check.mjs
|
|
- apps/api/src/dkv/dkv.service.ts
|
|
- apps/api/src/dkv/dkv-scheduler.service.ts
|
|
- docs/mandantentrennung-etappe2-fehlerrichtung.md
|
|
- docs/mandantentrennung-zugriffsklassifikation.md
|
|
- .planning/WINDOWS.md
|
|
---
|
|
|
|
# Etappe 2, Bereich dkv — Zusammenfassung
|
|
|
|
## Ergebnis
|
|
|
|
Alle 21 klassifizierten Zugriffe in `apps/api/src/dkv/dkv.service.ts` sind
|
|
umgestellt. Gebunden sind es am Ende 22 statt 21, weil der neue Besitzriegel vor
|
|
dem Ausfuhrdatei-Download einen zusaetzlichen Lesezugriff auf
|
|
`dkvInvoiceHistory` einfuehrt. Genau ein Zugriff bleibt bewusst ungebunden: der
|
|
Planer-Startpfad, siehe unten.
|
|
|
|
**Endstand, unabhaengig nachgemessen:** 789 Tests gruen (54 Dateien, Ausgangsstand
|
|
772/53), Typpruefung 0, `rls-scratch-check.mjs` 41/41. Schema, Migrationen,
|
|
Compose-Dateien und Umgebungsdateien unberuehrt; der Umstellungsschalter bleibt
|
|
aus.
|
|
|
|
## Die drei Funde
|
|
|
|
### 1. Eine bereits bestehende Fremdzugriffsluecke (T-MIR-03)
|
|
|
|
`DkvService.getExportFile(tenantId, filename)` nahm die Mandantenkennung
|
|
entgegen und benutzte sie nie. Die Datei wurde allein ueber ihren Namen aus dem
|
|
gemeinsamen `user-files/`-Verzeichnis geholt, abgesichert nur durch einen
|
|
Schutz gegen Pfad-Tricks und ein Namensmuster. Ein Administrator eines beliebigen
|
|
Mandanten konnte damit die Tankkarten-Auswertung eines anderen herunterladen,
|
|
sofern er den Dateinamen kannte.
|
|
|
|
Der Riegel leitet die Zugehoerigkeit jetzt aus `DkvInvoiceHistory.exportFilename`
|
|
ab — der einzigen mandantengebundenen Aussage darueber, wem eine Ausfuhrdatei
|
|
gehoert. Vor dem Umbau wurde in der Oberflaeche geprueft, dass jeder angebotene
|
|
Dateiname aus einer Historienzeile stammt (`ExportFileList.tsx`,
|
|
`InvoiceHistoryTable.tsx`); fuer die regulaere Nutzung aendert der Riegel deshalb
|
|
nichts.
|
|
|
|
Die Luecke ist keine Folge des Umbaus. Sie bestand seit jeher und faellt nur auf,
|
|
weil dieser Durchlauf jede Zeile des Bereichs einzeln aufschlaegt.
|
|
|
|
### 2. Der Bereich hatte keinerlei Tests
|
|
|
|
Weder eine Attrappe, die nichts prueft (der `ldap`-Fehler), noch eine fehlende
|
|
Attrappe (`groups`, `tenders`) — sondern gar keine Testdatei. Jede Zusicherung
|
|
dieses Plans waere unpruefbar geblieben. `dkv.service.spec.ts` wurde deshalb neu
|
|
angelegt, mit dem Zwei-Client-Nachweis aus `tender-triage.service.spec.ts`.
|
|
|
|
### 3. Ein zerstoerender Fehler in umgekehrter Richtung
|
|
|
|
`saveConfig` enthaelt einen Zweig, der ein bereits gespeichertes Passwort erhalten
|
|
soll, wenn der Nutzer das Feld leer laesst. Er verschluckte Lese- und
|
|
Entschluesselungsfehler und machte mit den uebergebenen — moeglicherweise leeren —
|
|
Werten weiter. Heute faellt das nicht auf, weil der Lesezugriff nie fehlschlaegt.
|
|
Nach dem Scharfschalten haette derselbe Zweig ein gespeichertes Passwort durch ein
|
|
leeres ersetzt und verschluesselt abgelegt: stiller Verlust, ohne Fehlermeldung,
|
|
nicht rekonstruierbar.
|
|
|
|
## Die bewusst getroffene Entscheidung: WINDOWS #21
|
|
|
|
Der Planer-Startpfad (`DkvSchedulerService.onModuleInit` →
|
|
`DkvService.loadAnyActiveConfigForScheduler`) bleibt ungebunden. Drei Formen
|
|
wurden geprueft:
|
|
|
|
- **(a) an einen konkreten Mandanten binden** — nicht moeglich, `onModuleInit()`
|
|
hat beim Start strukturell keinen Mandantenkontext.
|
|
- **(b) Umbau auf einmal-abfragen-viele-bedienen** — abgelehnt. Das ist die in
|
|
Phase 07-04 zurueckgestellte Mehrmandanten-Planung, also eine
|
|
Funktionsaenderung und kein Bindungsumbau.
|
|
- **(c) als benannte Altlast weiterfuehren** — gewaehlt.
|
|
|
|
Praezedenzfall ist `LdapConfigService.getAllActiveConfigs()` aus 260909-ipc, mit
|
|
einer Unsymmetrie, die dieser Praezedenzfall NICHT deckt und die deshalb
|
|
ausgeschrieben ist: `getAllActiveConfigs` ist heute korrekt und verstummt erst
|
|
nach dem Scharfschalten. Der DKV-Planer ist **heute bereits falsch** — `findFirst()`
|
|
ohne Bedingung zieht bei mehreren Mandanten einen beliebigen und bedient die
|
|
uebrigen nie; ist ausgerechnet die gezogene Zeile inaktiv, bedient er niemanden —
|
|
**und** verstummt zusaetzlich spaeter.
|
|
|
|
Die Markierung ist dreifach: eine eigens benannte Methode mit Kopfkommentar, der
|
|
beide Zustaende nennt (bewusst keine Verzweigung hinter einem optionalen
|
|
Parameter, die jemand spaeter "vereinheitlicht"), der fortgeschriebene
|
|
Kopfkommentar in `dkv-scheduler.service.ts`, und der Ledger-Eintrag WINDOWS #21.
|
|
|
|
Das Signal fuer das Verstummen gehoert in die Vorabpruefung von Etappe 4
|
|
(`rls-preflight.mjs`), nicht in diesen Durchlauf.
|
|
|
|
## Gegenbefunde — geprueft und verworfen
|
|
|
|
- **Kein `$transaction` im gesamten Bereich.** Die im Kopf von
|
|
`prisma-tenant.extension.ts` geforderte erneute Pruefung fuer jeden neuen Fall
|
|
ist damit beantwortet: kein neuer Fall, `withTenantTransaction()` wird hier
|
|
nicht gebraucht und wurde nicht eingefuehrt.
|
|
- **`DkvVehicleMaster` traegt `@@unique([tenantId, kennzeichen])`.** Dieser
|
|
Bereich hat also NICHT die `tenders`-Falle einer Eindeutigkeitsverletzung auf
|
|
einer unsichtbaren Zeile. Als Messung festgehalten statt als Absicherung, die
|
|
nichts absichert.
|
|
- **Die 21 hielt der Pruefung stand.** Erster Bereich dieses Vorhabens, dessen
|
|
Kopfzahl beim Hineinsehen nicht kleiner wurde (zuvor 36→6, 37→34, 62→61, 10→8).
|
|
|
|
## Neu gemessene Form
|
|
|
|
`getHistory` fuehrt zwei gebundene Einzelabfragen parallel ueber `Promise.all`
|
|
aus — eine Form, die bisher in keinem Bereich vorkam und die das Werkzeug jetzt
|
|
mit einer eigenen Pruefung abdeckt
|
|
(`dkv-zwei-parallele-gebundene-einzelabfragen-je-eigener-kontext`).
|
|
|
|
## Falsifizierungsnachweise
|
|
|
|
Der Plan verlangt fuer jede Umstellungsaufgabe, dass die Tests durch Rueckbau
|
|
falsifiziert werden — ein Test, der nicht rot werden kann, beweist nichts. Der
|
|
Nachweis lag zunaechst nur in einer Commit-Nachricht bzw. gar nicht vor und wird
|
|
hier nachgetragen, damit er dort steht, wo spaeter jemand nachsieht.
|
|
|
|
**Aufgabe 2** (Konfigurationspfade, Commit `222f453`): Der erste gebundene
|
|
Client von `getConfigForApi` wurde probeweise durch `this.prisma` ersetzt. Genau
|
|
Test 1 wurde rot, die sechs uebrigen blieben gruen; der Rueckbau wurde
|
|
zurueckgenommen und die Dateiidentitaet zum Ausgangsstand bestaetigt. Belegt in
|
|
der Commit-Nachricht von `222f453`.
|
|
|
|
**Aufgabe 3** (Historie, Fahrzeugstammdaten, Besitzriegel, Commit `5e8237d`):
|
|
Der Nachweis fehlte, weil die Ausfuehrung an dieser Stelle durch das
|
|
Sitzungslimit abbrach. Er wurde bei der Verifikation nachgeholt: die Bindung des
|
|
Besitzriegels wurde zurueckgebaut, worauf genau der benannte Test 10 mit einer
|
|
spezifischen Meldung rot wurde, waehrend die sechzehn uebrigen gruen blieben;
|
|
danach zurueckgesetzt und der saubere Stand bestaetigt (789/789 Tests,
|
|
Typpruefung 0, Werkzeug 41/41).
|
|
|
|
Beide Nachweise stammen damit aus unterschiedlichen Haenden — Aufgabe 2 vom
|
|
ausfuehrenden Agenten, Aufgabe 3 vom pruefenden. Das ist kein Nachteil: der
|
|
zweite Nachweis ist der staerkere, weil ihn jemand erbracht hat, der die Bindung
|
|
nicht selbst geschrieben hatte.
|
|
|
|
## Ablauf-Hinweis
|
|
|
|
Die Ausfuehrung wurde am 2026-09-09 gegen Ende von Aufgabe 3 durch ein
|
|
Sitzungslimit unterbrochen; die beiden ersten Aufgaben waren committet, die
|
|
dritte lag vollstaendig im Arbeitsbaum. Nachgetragen wurden am 2026-09-10 die
|
|
Uebersichtstabelle und die Summenzeile im Klassifikationsdokument sowie diese
|
|
Zusammenfassung. Die Verifikation fand daran zwei Luecken — der Abschnitt "Der
|
|
Hintergrunddienst als Falle" war nicht um den DKV-Planer erweitert worden
|
|
(Aufgabe 3 verlangte das ausdruecklich), und die Falsifizierungsnachweise
|
|
fehlten in dieser Zusammenfassung; beides wurde danach nachgetragen. Der Bruch fiel auf, weil `git status` einen nicht leeren
|
|
Arbeitsbaum zeigte — nicht, weil ein Bericht ihn gemeldet haette.
|