feat(quick-260910-jab): drei zu kurz greifende RLS-Regeln schliessen (T-JTS-02, T-JTS-03, WINDOWS #19)
- Neue, handgeschriebene Migration 20260910120000_rls_widen_membership_grant_and_platform_read: GroupMembership prueft jetzt beide Seiten der Beziehung (Gruppe UND Benutzer), ModuleGrant prueft zusaetzlich die referenzierte Gruppe/den referenzierten Benutzer (mit Leer-Zulassung, D-04), TenderRssFeedSource bekommt vier nach Befehl getrennte Regeln statt einer (Lesen schliesst plattformweite Zeilen ein, Schreiben verlangt weiterhin einen Mandanten). SearchProvider bewusst unveraendert (Befund E: Praemisse widerlegt). Lokal angewandt und gegen den Systemkatalog der lebenden Datenbank gemessen. Der Schalter bleibt aus (Rolle tessera). - rls-scratch-check.mjs: die drei loch-behauptenden Pruefungen umgekehrt (nicht geloescht), Gegenmessungen ueber die Wartungsrolle ergaenzt, vier Befehlsrichtungen fuer TenderRssFeedSource gemessen, neuer Abschnitt fuer SearchProvider, Extraktion auf die neue Migration umgeleitet und um eine mehrfach-treffer-faehige Form ergaenzt (extractAllPolicySql). - migration-sql.spec.ts: neuer Beschreibungsblock fuer die neue Migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
+124
@@ -0,0 +1,124 @@
|
||||
-- 260910-jab, Aufgabe 1 — schliesst T-JTS-02, T-JTS-03 und WINDOWS #19: drei
|
||||
-- Regeln, die kuerzer greifen als sie sollen.
|
||||
--
|
||||
-- Loest drei ausgelieferte Regeln ab. Die betroffenen Dateien
|
||||
-- (20260804130130_add_groups_and_module_grants fuer die Tabellenform,
|
||||
-- 20260804130918_groups_rls_policies fuer die alte GroupMembership/
|
||||
-- ModuleGrant-Regel, 20260909140000_rls_remaining_tenant_tables fuer die
|
||||
-- alte TenderRssFeedSource-Regel) bleiben UNVERAENDERT stehen — Prisma
|
||||
-- fuehrt ihre Pruefsumme, eine Aenderung braechte "prisma migrate deploy"
|
||||
-- zum Abbruch. Praezedenzfall und Kopfform: 20260909140000_rls_remaining_
|
||||
-- tenant_tables.
|
||||
--
|
||||
-- (1) GroupMembership (T-JTS-02): die ausgelieferte Regel prueft
|
||||
-- ausschliesslich, ob die referenzierte Gruppe zum laufenden Mandanten
|
||||
-- gehoert ("groupId" IN (...)) — nicht, ob der referenzierte Benutzer es
|
||||
-- tut. Eine Mitgliedschaft konnte dadurch eine Gruppe des einen Mandanten
|
||||
-- mit einem Benutzer eines anderen verbinden. Die neue Regel prueft beide
|
||||
-- Seiten mit UND verknuepft, nach demselben Join-Muster, das
|
||||
-- PasswordResetToken seit 20260618112133 fuer die Benutzerseite vormacht.
|
||||
--
|
||||
-- (2) ModuleGrant (T-JTS-03): die ausgelieferte Regel prueft ausschliesslich
|
||||
-- die Mandantenkennung der Zeile selbst — nicht, wohin "groupId"/"userId"
|
||||
-- zeigen. Eine Freigabe mit korrekter eigener Mandantenkennung, aber
|
||||
-- fremder Gruppen- ODER fremder Benutzerkennung, wurde durchgelassen. Die
|
||||
-- neue Regel prueft zusaetzlich beide moeglichen Ziele; die Leer-Zulassung
|
||||
-- ist zwingend, weil das Modell Gruppe und Benutzer als Entweder-oder fuehrt
|
||||
-- (D-04, CHECK-Constraint "ModuleGrant_group_xor_user"). WICHTIG:
|
||||
-- `assertTargetBelongsToTenant` in apps/api/src/groups/module-grants.service.ts
|
||||
-- bleibt UNVERAENDERT bestehen — diese Datenbankregel ist ein ZWEITES Netz,
|
||||
-- kein Ersatz dafuer.
|
||||
--
|
||||
-- (3) TenderRssFeedSource (WINDOWS #19): "tenantId" ist nullable — NULL
|
||||
-- markiert eine plattformweite Zeile (D-06). Die ausgelieferte Regel
|
||||
-- "tenantId" = current_tenant_id() vergleicht NULL nie gleich; eine
|
||||
-- plattformweite Zeile waere nach dem Scharfschalten fuer JEDEN Mandanten
|
||||
-- unsichtbar, nicht nur fuer fremde. Ersetzt durch VIER nach Befehl
|
||||
-- getrennte Regeln: die Leseregel schliesst die plattformweiten Zeilen
|
||||
-- ausdruecklich ein, die drei Schreibregeln (Einfuegen/Aendern/Entfernen)
|
||||
-- verlangen weiterhin ausnahmslos einen Mandanten. Vier ausdrueckliche
|
||||
-- Regeln statt einer mit stillschweigender Wirkung, weil ein einzelner
|
||||
-- USING-Ausdruck auch bestimmt, welche Zeilen UPDATE und DELETE ueberhaupt
|
||||
-- erreichen — eine Regel, die die plattformweiten Zeilen zum Lesen
|
||||
-- einschliesst, wuerde ohne die Trennung jedem Mandanten auch das Aendern
|
||||
-- und Entfernen dieser Zeilen erlauben.
|
||||
--
|
||||
-- (4) SearchProvider — bewusst UNVERAENDERT, keine Anweisung in dieser
|
||||
-- Migration. "tenantId" ist hier ebenfalls nullable, aber die Praemisse von
|
||||
-- WINDOWS #19 stimmt fuer diese Tabelle nachweislich NICHT: es gibt keinen
|
||||
-- Codeweg, der eine mandantenlose Zeile erzeugt — der einzige Schreibweg
|
||||
-- (apps/api/src/dashboard/dashboard.service.ts) verlangt die
|
||||
-- Mandantenkennung als Pflichtparameter, und die Vorgabe-Suchmaschinen sind
|
||||
-- Konstanten (Entscheidung 05-02), keine Datenbankzeilen. Lokal gemessen
|
||||
-- (2026-09-10): null Zeilen insgesamt in "SearchProvider". Eine Lockerung
|
||||
-- waere hier die falsche Richtung — sie wuerde eine kuenftige mandantenlose
|
||||
-- Zeile jedem Mandanten zeigen. Die Schliessung von WINDOWS #19 schliesst
|
||||
-- diese Haelfte deshalb als WIDERLEGTE PRAEMISSE, nicht als geloestes
|
||||
-- Problem.
|
||||
--
|
||||
-- WICHTIG: wie alle bisherigen RLS-Migrationen wirken diese Regeln erst,
|
||||
-- wenn die Anwendung als Rolle ohne Umgehungsrecht verbindet (siehe
|
||||
-- 20260909130000_rls_app_role und docs/mandantentrennung-datenbankrolle.md).
|
||||
-- Die Verbindung ist zum Zeitpunkt dieser Migration weiterhin NICHT
|
||||
-- umgestellt — `DATABASE_URL` zeigt unveraendert auf die Rolle `tessera`.
|
||||
-- Ohne diesen Satz waere diese Datei genau das, wovor WINDOWS #18 warnt:
|
||||
-- eine Regel, die Sicherheit vortaeuscht.
|
||||
|
||||
-- (1) GroupMembership — beide Seiten der Beziehung.
|
||||
DROP POLICY tenant_isolation_policy ON "GroupMembership";
|
||||
|
||||
CREATE POLICY tenant_isolation_policy ON "GroupMembership"
|
||||
USING (
|
||||
"groupId" IN (
|
||||
SELECT "id" FROM "Group" WHERE "tenantId" = current_tenant_id()
|
||||
)
|
||||
AND "userId" IN (
|
||||
SELECT "id" FROM "User" WHERE "tenantId" = current_tenant_id()
|
||||
)
|
||||
);
|
||||
|
||||
-- (2) ModuleGrant — die Zeile selbst UND beide moeglichen Ziele.
|
||||
DROP POLICY tenant_isolation_policy ON "ModuleGrant";
|
||||
|
||||
CREATE POLICY tenant_isolation_policy ON "ModuleGrant"
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (
|
||||
"groupId" IS NULL
|
||||
OR "groupId" IN (
|
||||
SELECT "id" FROM "Group" WHERE "tenantId" = current_tenant_id()
|
||||
)
|
||||
)
|
||||
AND (
|
||||
"userId" IS NULL
|
||||
OR "userId" IN (
|
||||
SELECT "id" FROM "User" WHERE "tenantId" = current_tenant_id()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
-- (3) TenderRssFeedSource — Lesen schliesst die plattformweiten Zeilen ein,
|
||||
-- Schreiben (Einfuegen/Aendern/Entfernen) verlangt ausnahmslos einen
|
||||
-- Mandanten. Vier Regeln statt einer, nach Befehl getrennt (Begruendung
|
||||
-- oben).
|
||||
DROP POLICY tenant_isolation_policy ON "TenderRssFeedSource";
|
||||
|
||||
CREATE POLICY tenant_platform_read_policy ON "TenderRssFeedSource"
|
||||
FOR SELECT
|
||||
USING ("tenantId" = current_tenant_id() OR "tenantId" IS NULL);
|
||||
|
||||
CREATE POLICY tenant_insert_policy ON "TenderRssFeedSource"
|
||||
FOR INSERT
|
||||
WITH CHECK ("tenantId" = current_tenant_id());
|
||||
|
||||
CREATE POLICY tenant_update_policy ON "TenderRssFeedSource"
|
||||
FOR UPDATE
|
||||
USING ("tenantId" = current_tenant_id())
|
||||
WITH CHECK ("tenantId" = current_tenant_id());
|
||||
|
||||
CREATE POLICY tenant_delete_policy ON "TenderRssFeedSource"
|
||||
FOR DELETE
|
||||
USING ("tenantId" = current_tenant_id());
|
||||
|
||||
-- (4) SearchProvider — keine Anweisung. Die ausgelieferte Regel
|
||||
-- ("tenantId" = current_tenant_id()) bleibt unveraendert bestehen.
|
||||
Reference in New Issue
Block a user