teecup/005_rls_null_guard.sql

61 lines
2.6 KiB
MySQL
Raw Permalink Normal View History

RLS-tomstreng-buggen er fikset og verifisert grundig. Fiksen: ny migrasjon 005_rls_null_guard.sql — én delt STABLE SQL-funksjon app_current_org() (NULLIF(current_setting('app.current_org', true), '')::uuid) erstatter det rå uttrykket i alle 15 RLS-policyer (14 org_isolation + org_self) via ALTER POLICY. NULLIF konverterer tomstreng til NULL før cast, så "trygg standard: se ingenting" gjenopprettes uansett GUC-tilstand. Verifisert to ganger, ulikt: Tre nye regresjonstester i test_isolation.sql (Test 10–12): tomstreng-lesing gir 0 rader ikke krasj, tomstreng-skriving avvises av RLS ikke krasj, og org-bootstrap-innsetting (tidligere aldri testet) fungerer rett etter tomstreng-tilstand. Faktisk gjenskaping av original-buggen mot en ekte container (pool-størrelse tvunget til 1 for å garantere tilkoblings-gjenbruk): varmet opp med et vanlig org_connection()-kall, kalte deretter /auth/me på samme tilkobling — gikk fra 500 til 200. En viktig ting jeg tok feil om i forrige runde, oppdaget ved å faktisk teste i stedet for å anta: fiksen gjør ikke at /auth/me trygt kan joine organization direkte via plain_connection(). Den løser tomstreng-krasjen, men org_self-policyen krever fortsatt en matchende app.current_org for å vise noen rad i det hele tatt — riktig RLS-design, ikke noe fiksen skulle endre. Siden en bruker kan tilhøre flere organisasjoner, finnes det ingen én kontekst å sette. Rettet til riktig løsning: /auth/me slår nå opp hvert org-navn enkeltvis via org_connection() (verifisert at det faktisk returnerer navnet korrekt). Alt dokumentert i CLAUDE.md/FEATURE_BACKLOG.md.
2026-07-16 15:34:24 +02:00
-- =====================================================================
-- TeeCup — migrasjon 005
-- RLS-fiks: tomstreng i app.current_org (se FEATURE_BACKLOG.md)
-- =====================================================================
-- Kjøres etter 001-004.
--
-- BUG: current_setting('app.current_org', true)::uuid håndterer NULL trygt
-- (gir ingen rader, som skjemaets kommentar i 001 lover: "trygg standard"),
-- men IKKE tomstreng. En gjenbrukt asyncpg-pool-tilkobling der en TIDLIGERE
-- forespørsel satte GUC-en via SET LOCAL (org_connection(), app/db.py) kan
-- lese den tilbake som '' etter at den transaksjonen er ferdig — en custom
-- GUC sin nullstilte tilstand er tomstreng når placeholderen først er
-- opprettet i sesjonen, ikke fullstendig fraværende. ''::uuid kaster en
-- feil (500) i stedet for trygt null rader. Oppdaget da /auth/me (egen
-- runde) spurte `organization` via plain_connection() (ingen org-kontekst).
--
-- FIKS: NULLIF(current_setting(...), '')::uuid — konverterer tomstreng til
-- NULL FØR cast. Samlet i én STABLE SQL-funksjon fremfor duplisert i 15
-- policyer.
-- =====================================================================
\set ON_ERROR_STOP on
-- Feil raskt fremfor å blokkere hele appen hvis en ALTER POLICY skulle køe
-- bak en lang spørring — hver ALTER POLICY under tar ACCESS EXCLUSIVE på
-- måltabellen til COMMIT.
SET lock_timeout = '5s';
CREATE OR REPLACE FUNCTION app_current_org() RETURNS uuid
LANGUAGE sql STABLE AS $$
SELECT NULLIF(current_setting('app.current_org', true), '')::uuid
$$;
-- De 14 org_isolation-tabellene: 12 fra 001 + 2 fra 003 (samme array som
-- CREATE-loopene der — hold denne i sync hvis en fremtidig migrasjon
-- legger til en ny org-scopet tabell).
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'player','course','hole','tee','tee_rating',
'tournament','team','team_roster','session',
'match','match_participant','hole_score',
'match_hole_result','lineup_lock'
]
LOOP
EXECUTE format($p$
ALTER POLICY org_isolation ON %I
USING (organization_id = app_current_org())
WITH CHECK (organization_id = app_current_org());
$p$, t);
END LOOP;
END $$;
-- org_self ble opprinnelig laget UTEN eksplisitt WITH CHECK (polwithcheck er
-- NULL i pg_policy, ikke en snapshot av USING) — Postgres sin
-- speiler-USING-når-WITH-CHECK-mangler-fallback er dynamisk, ikke en
-- engangskopi tatt ved CREATE. Å re-issue kun USING her er derfor korrekt.
ALTER POLICY org_self ON organization
USING (id = app_current_org());