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.
60 lines
2.6 KiB
PL/PgSQL
60 lines
2.6 KiB
PL/PgSQL
-- =====================================================================
|
|
-- 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());
|