feat(quick-260909-dgj): Policies fuer die 16 fehlenden Tabellen (Task 4/4)

- Migration 20260909140000_rls_remaining_tenant_tables ergaenzt ENABLE +
  FORCE ROW LEVEL SECURITY und je eine tenant_isolation_policy fuer alle 16
  noch offenen Tabellen mit tenantId
- Kopfkommentar korrigiert die zu pauschale D-03-Aussage aus
  20260804130918: nur die drei Tender-Tabellen OHNE tenantId sind davon
  betroffen, die sechs MIT tenantId bekommen jetzt eine Policy — die alte
  Migrationsdatei bleibt unveraendert
- rls-coverage.spec.ts misst die Abdeckung aus Schema und Migrationen
  statt Text zu vergleichen (rot mit 16 gemeldeten Luecken vor der
  Migration, jetzt gruen); waechst automatisch mit kuenftigen Modellen und
  erzwingt bei jedem neuen tenantId-losen Modell eine bewusste Entscheidung

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
This commit is contained in:
2026-09-09 10:05:15 +02:00
parent 44a90e5bfd
commit efaabc97a2
2 changed files with 293 additions and 0 deletions
@@ -0,0 +1,156 @@
-- WINDOWS #18 — vollstaendige RLS-Abdeckung fuer alle Tabellen mit
-- tenantId (T-DGJ-02). Zweite von zwei Migrationen zu diesem Fund; die
-- erste (20260909130000_rls_app_role) legt die Anwendungsrolle
-- tessera_app ohne Superuser-/BYPASSRLS-Recht an.
--
-- Zaehlung im Schema (Stand 2026-09-09): 28 Modelle insgesamt, davon 20 mit
-- direkter tenantId-Spalte. Von diesen 20 trugen bislang 4 eine Policy
-- (User, LdapConfig, Group, ModuleGrant — aus 20260618112133 und
-- 20260804130918). Diese Migration ergaenzt die restlichen 16.
--
-- Die 8 Modelle OHNE tenantId zerfallen in zwei Gruppen:
--
-- (a) Drei ueber einen Join geschuetzt, keine eigene tenantId-Spalte,
-- bereits mit Policy versehen: PasswordResetToken (userId -> User),
-- LdapFieldMapping (ldapConfigId -> LdapConfig), GroupMembership
-- (groupId -> Group).
--
-- (b) Fuenf bewusst OHNE Policy, weil sie keine tenantId-Spalte tragen
-- und auch keine tragen sollen:
-- - Tenant: die Mandantentabelle selbst. Eine Regel darauf wuerde
-- die Aufloesung des Mandanten verhindern, auf der jede andere
-- Regel beruht.
-- - Module: der Modulkatalog ist plattformweit; die
-- mandantenbezogene Zuordnung liegt in TenantModuleActivation,
-- und die bekommt hier eine Policy.
-- - Tender, TenderSource, TenderSourcePollConfig: der
-- Ausschreibungskatalog ist plattformweite Bezugsdaten
-- (Entscheidung D-03 aus Phase 10, siehe
-- .planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-01-PLAN.md:93:
-- kein tenantId, weil global). Eine Policy darauf wuerde einem
-- zweiten Mandanten den gemeinsamen Katalog verbergen.
--
-- KORREKTUR einer Aussage aus dem Bestand — die alte Datei bleibt dabei
-- unveraendert, weil Prisma ihre Pruefsumme fuehrt und eine Aenderung
-- "prisma migrate deploy" zum Abbruch braechte:
-- Der Kopf von 20260804130918_groups_rls_policies sagt pauschal, "die
-- Tender*-Tabellen bleiben bewusst ohne RLS (D-03)". Nachgemessen trifft
-- das nur auf die drei oben genannten Tabellen OHNE tenantId zu. Die
-- sechs Tender-Tabellen MIT tenantId (TenderEmailConfig, TenderMatch,
-- TenderNotificationPref, TenderRssFeedSource, TenderSavedSearch,
-- TenderTriage) enthalten keine Katalogdaten, sondern Zeilen einzelner
-- Nutzer und Mandanten — Suchprofile, Treffer,
-- Benachrichtigungseinstellungen, Postfachanbindungen. D-03 betrifft sie
-- nicht; sie bekommen unten allesamt eine Policy.
--
-- WICHTIG: Diese Policies wirken erst, wenn die Anwendung als Rolle ohne
-- Umgehungsrecht verbindet — siehe Migration 20260909130000_rls_app_role
-- und docs/mandantentrennung-datenbankrolle.md. Die Verbindung ist zum
-- Zeitpunkt dieser Migration noch NICHT umgestellt. Ohne diesen Satz waere
-- diese Datei genau das, wovor WINDOWS #18 warnt: eine Regel, die
-- Sicherheit vortaeuscht.
--
-- Keine getrennte WITH CHECK-Klausel: laesst man sie weg, verwendet
-- PostgreSQL denselben USING-Ausdruck auch fuer neu geschriebene Zeilen —
-- genau das ist gewollt, damit unter der neuen Rolle niemand eine Zeile
-- mit fremder Mandantenkennung einfuegen kann.
-- CalendarSource — Kalenderquellen (CalDAV/ICS/Exchange) eines Nutzers
ALTER TABLE "CalendarSource" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "CalendarSource" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "CalendarSource"
USING ("tenantId" = current_tenant_id());
-- DashboardLayout — Widget-Anordnung eines Nutzers auf dem Dashboard
ALTER TABLE "DashboardLayout" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "DashboardLayout" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "DashboardLayout"
USING ("tenantId" = current_tenant_id());
-- DkvInvoiceHistory — DKV-Rechnungshistorie samt Exportstatus
ALTER TABLE "DkvInvoiceHistory" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "DkvInvoiceHistory" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "DkvInvoiceHistory"
USING ("tenantId" = current_tenant_id());
-- DkvModuleConfig — Postfach-/Zugangsdaten des DKV-Moduls je Mandant
ALTER TABLE "DkvModuleConfig" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "DkvModuleConfig" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "DkvModuleConfig"
USING ("tenantId" = current_tenant_id());
-- DkvVehicleMaster — Fahrzeugstammdaten des DKV-Moduls
ALTER TABLE "DkvVehicleMaster" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "DkvVehicleMaster" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "DkvVehicleMaster"
USING ("tenantId" = current_tenant_id());
-- FavoriteLink — Favoriten-Links eines Nutzers
ALTER TABLE "FavoriteLink" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "FavoriteLink" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "FavoriteLink"
USING ("tenantId" = current_tenant_id());
-- SearchProvider — vom Nutzer angelegte Suchmaschinen fuer das Such-Widget
-- (tenantId ist nullable — Vorgabe-Anbieter sind ueber Konstanten geloest,
-- 05-02; eine mandantenlose Zeile wird von dieser Policy nicht ausgeliefert)
ALTER TABLE "SearchProvider" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "SearchProvider" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "SearchProvider"
USING ("tenantId" = current_tenant_id());
-- SmtpConfig — SMTP-Zugangsdaten je Mandant
ALTER TABLE "SmtpConfig" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "SmtpConfig" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "SmtpConfig"
USING ("tenantId" = current_tenant_id());
-- TenantModuleActivation — welche Module ein Mandant aktiviert hat
ALTER TABLE "TenantModuleActivation" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenantModuleActivation" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenantModuleActivation"
USING ("tenantId" = current_tenant_id());
-- TenderEmailConfig — Postfachanbindung eines Nutzers im Ausschreibungs-Radar
ALTER TABLE "TenderEmailConfig" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderEmailConfig" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderEmailConfig"
USING ("tenantId" = current_tenant_id());
-- TenderMatch — Treffer eines gespeicherten Suchprofils
ALTER TABLE "TenderMatch" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderMatch" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderMatch"
USING ("tenantId" = current_tenant_id());
-- TenderNotificationPref — Benachrichtigungseinstellungen eines Nutzers
ALTER TABLE "TenderNotificationPref" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderNotificationPref" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderNotificationPref"
USING ("tenantId" = current_tenant_id());
-- TenderRssFeedSource — RSS-Quellen im Ausschreibungs-Radar
-- (tenantId ist nullable — null markiert eine plattformweite Quelle, D-06;
-- eine solche Zeile wird von dieser Policy nicht ausgeliefert)
ALTER TABLE "TenderRssFeedSource" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderRssFeedSource" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderRssFeedSource"
USING ("tenantId" = current_tenant_id());
-- TenderSavedSearch — gespeicherte Suchprofile eines Nutzers
ALTER TABLE "TenderSavedSearch" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderSavedSearch" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderSavedSearch"
USING ("tenantId" = current_tenant_id());
-- TenderTriage — Favorisierungs-/Ablehnungsstatus eines Nutzers je Treffer
ALTER TABLE "TenderTriage" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "TenderTriage" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "TenderTriage"
USING ("tenantId" = current_tenant_id());
-- WidgetInstance — platzierte Dashboard-Widgets eines Nutzers
ALTER TABLE "WidgetInstance" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "WidgetInstance" FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_policy ON "WidgetInstance"
USING ("tenantId" = current_tenant_id());