-- ===================================================================== -- TeeCup — isolasjonstest (oppførsel, ikke bare skjema) -- ===================================================================== -- Beviser at RLS faktisk hindrer én organisasjon i å se en annens data, -- sett fra runtime-rollen teecup_app (som IKKE er superuser/BYPASSRLS). -- -- Kjør som admin/superbruker mot en scratch- eller test-database der -- 001_initial_schema.sql, 002_roles_and_grants.sql, -- 003_scoring_and_blinddraw.sql og 005_rls_null_guard.sql allerede er kjørt -- (003 kreves for match_hole_result/lineup_lock, 005 for -- app_current_org()-funksjonen testene 10-12 bruker indirekte): -- psql -d teecup_scratch -f test_isolation.sql -- -- Alt kjøres i én transaksjon som RULLES TILBAKE til slutt — ingen testdata -- blir liggende igjen. Skriptet stopper med feil hvis isolasjonen svikter. -- ===================================================================== \set ON_ERROR_STOP on BEGIN; -- --- Seed to organisasjoner + én turnering hver (som privilegert rolle) --- -- (Superbruker omgår RLS, så innsettingen går uhindret her — det er meningen.) INSERT INTO organization (id, name) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'Org A'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'Org B'); INSERT INTO tournament (organization_id, name, join_code) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-Cup', 'TESTA1'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-Cup', 'TESTB1'); -- --- Seed nok struktur til å teste match_hole_result og lineup_lock --- -- (ADR-012/013, migrasjon 003). Fortsatt privilegert rolle, RLS omgås her. INSERT INTO course (id, organization_id, name) VALUES ('c1111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'A-banen'), ('c2222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'B-banen'); INSERT INTO team (id, organization_id, tournament_id, name) SELECT 'de111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'A rødt' FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; INSERT INTO team (id, organization_id, tournament_id, name) SELECT 'de222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'A blått' FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; INSERT INTO team (id, organization_id, tournament_id, name) SELECT 'df111111-1111-1111-1111-111111111111', t.organization_id, t.id, 'B rødt' FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'; INSERT INTO team (id, organization_id, tournament_id, name) SELECT 'df222222-2222-2222-2222-222222222222', t.organization_id, t.id, 'B blått' FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'; INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id) SELECT 'e5111111-1111-1111-1111-111111111111', t.organization_id, t.id, 1, 'singles', 'c1111111-1111-1111-1111-111111111111' FROM tournament t WHERE t.organization_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; INSERT INTO session (id, organization_id, tournament_id, sequence, format, course_id) SELECT 'e5222222-2222-2222-2222-222222222222', t.organization_id, t.id, 1, 'singles', 'c2222222-2222-2222-2222-222222222222' FROM tournament t WHERE t.organization_id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'; INSERT INTO match (id, organization_id, session_id, sequence, team_a_id, team_b_id) VALUES ('fa111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'e5111111-1111-1111-1111-111111111111', 1, 'de111111-1111-1111-1111-111111111111', 'de222222-2222-2222-2222-222222222222'), ('fa222222-2222-2222-2222-222222222222', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'e5222222-2222-2222-2222-222222222222', 1, 'df111111-1111-1111-1111-111111111111', 'df222222-2222-2222-2222-222222222222'); INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'fa111111-1111-1111-1111-111111111111', 1, 'a'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'fa222222-2222-2222-2222-222222222222', 1, 'b'); INSERT INTO lineup_lock (organization_id, session_id, team_id) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'e5111111-1111-1111-1111-111111111111', 'de111111-1111-1111-1111-111111111111'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'e5222222-2222-2222-2222-222222222222', 'df111111-1111-1111-1111-111111111111'); -- --- Bytt til runtime-rollen: nå SKAL RLS gjelde --------------------- SET ROLE teecup_app; SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false); -- Test 1: Ser bare Org A sine turneringer (1, ikke 2). DO $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM tournament; IF n <> 1 THEN RAISE EXCEPTION 'ISOLASJON FEILET (lesing): ser % turneringer, forventet 1', n; END IF; RAISE NOTICE 'OK Test 1: ser kun egen organisasjons turneringer (%).', n; END $$; -- Test 2: Ser bare sin egen organisasjonsrad. DO $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM organization; IF n <> 1 THEN RAISE EXCEPTION 'ISOLASJON FEILET (organization): ser % rader, forventet 1', n; END IF; RAISE NOTICE 'OK Test 2: ser kun egen organisasjonsrad (%).', n; END $$; -- Test 3: Kan IKKE sette inn data merket med en annen organisasjon. -- WITH CHECK skal blokkere det (SQLSTATE 42501). DO $$ BEGIN BEGIN INSERT INTO tournament (organization_id, name, join_code) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'kryss-org-forsøk', 'TESTX01'); -- Kommer vi hit, ble det tillatt -> isolasjonen er brutt. RAISE EXCEPTION 'ISOLASJON FEILET (skriving): fikk sette inn data for annen org'; EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'OK Test 3: WITH CHECK blokkerte kryss-org innsetting.'; END; END $$; -- Test 4: Kontrollprøve — bytter kontekst til Org B og ser Bs turnering isteden. SELECT set_config('app.current_org', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', false); DO $$ DECLARE nm text; BEGIN SELECT name INTO nm FROM tournament; IF nm <> 'B-Cup' THEN RAISE EXCEPTION 'ISOLASJON FEILET (kontekstbytte): forventet B-Cup, fikk %', nm; END IF; RAISE NOTICE 'OK Test 4: kontekstbytte gir riktig organisasjons data (%).', nm; END $$; -- --- Tilbake til Org A-kontekst for de nye tabellene (ADR-012/013) ---- SELECT set_config('app.current_org', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', false); -- Test 5: match_hole_result — ser bare Org A sin rad (1, ikke 2). DO $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM match_hole_result; IF n <> 1 THEN RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result lesing): ser % rader, forventet 1', n; END IF; RAISE NOTICE 'OK Test 5: match_hole_result — ser kun egen organisasjons rad (%).', n; END $$; -- Test 6: match_hole_result — kan IKKE sette inn en rad merket med annen org, -- selv om match_id-en (fra org B) i seg selv er FK-gyldig. DO $$ BEGIN BEGIN INSERT INTO match_hole_result (organization_id, match_id, hole_number, winning_side) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'fa222222-2222-2222-2222-222222222222', 2, 'b'); RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result skriving): fikk sette inn data for annen org'; EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'OK Test 6: match_hole_result — WITH CHECK blokkerte kryss-org innsetting.'; END; END $$; -- Test 7: lineup_lock — ser bare Org A sin rad (1, ikke 2). DO $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM lineup_lock; IF n <> 1 THEN RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock lesing): ser % rader, forventet 1', n; END IF; RAISE NOTICE 'OK Test 7: lineup_lock — ser kun egen organisasjons rad (%).', n; END $$; -- Test 8: lineup_lock — kan IKKE sette inn en rad merket med annen org, -- selv om session_id/team_id (fra org B) i seg selv er FK-gyldige. DO $$ BEGIN BEGIN INSERT INTO lineup_lock (organization_id, session_id, team_id) VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'e5222222-2222-2222-2222-222222222222', 'df222222-2222-2222-2222-222222222222'); RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock skriving): fikk sette inn data for annen org'; EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'OK Test 8: lineup_lock — WITH CHECK blokkerte kryss-org innsetting.'; END; END $$; -- Test 9: kontekstbytte til Org B gir Org Bs rader for begge nye tabeller. SELECT set_config('app.current_org', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', false); DO $$ DECLARE ws team_side; DECLARE tid uuid; BEGIN SELECT winning_side INTO ws FROM match_hole_result; IF ws <> 'b' THEN RAISE EXCEPTION 'ISOLASJON FEILET (match_hole_result kontekstbytte): forventet b, fikk %', ws; END IF; RAISE NOTICE 'OK Test 9a: match_hole_result — kontekstbytte gir riktig organisasjons data (%).', ws; SELECT team_id INTO tid FROM lineup_lock; IF tid <> 'df111111-1111-1111-1111-111111111111' THEN RAISE EXCEPTION 'ISOLASJON FEILET (lineup_lock kontekstbytte): forventet Org Bs lag, fikk %', tid; END IF; RAISE NOTICE 'OK Test 9b: lineup_lock — kontekstbytte gir riktig organisasjons data (%).', tid; END $$; -- --- Regresjonstester for RLS-tomstreng-buggen (migrasjon 005) ---------- -- current_setting('app.current_org', true)::uuid håndterte NULL trygt, men -- IKKE tomstreng — en gjenbrukt pool-tilkobling kunne lese GUC-en tilbake -- som '' og få en kastet feil i stedet for trygt "se ingenting". Simulerer -- tilstanden direkte (set_config med is_local=false, ikke SET LOCAL) siden -- den er lettere å fremtvinge deterministisk enn selve pool-gjenbruken som -- utløste den i praksis. -- Test 10: tomstreng-GUC — SELECT skal gi 0 rader, IKKE en kastet feil. SELECT set_config('app.current_org', '', false); DO $$ DECLARE n int; BEGIN SELECT count(*) INTO n FROM tournament; IF n <> 0 THEN RAISE EXCEPTION 'ISOLASJON FEILET (tomstreng-lesing): ser % rader, forventet 0', n; END IF; RAISE NOTICE 'OK Test 10: tomstreng-GUC — SELECT gir trygt 0 rader, ikke feil (%).', n; END $$; -- Test 11: tomstreng-GUC — INSERT skal avvises av RLS (insufficient_privilege), -- IKKE feile med "invalid input syntax for type uuid". DO $$ BEGIN BEGIN INSERT INTO tournament (organization_id, name, join_code) VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'tomstreng-forsøk', 'TESTX02'); RAISE EXCEPTION 'ISOLASJON FEILET (tomstreng-skriving): fikk sette inn data uten gyldig org-kontekst'; EXCEPTION WHEN insufficient_privilege THEN RAISE NOTICE 'OK Test 11: tomstreng-GUC — WITH CHECK blokkerte innsetting trygt (ikke krasj).'; END; END $$; -- Test 12: bootstrap-mønster (sett current_org til en FERSK uuid, deretter -- INSERT organization med SAMME id) skal fungere korrekt selv RETT ETTER at -- tilstanden nettopp var tomstreng — beviser at en eksplisitt satt verdi -- alltid overstyrer uansett tidligere GUC-tilstand. Dobler som første test -- av selve org-bootstrap-innsettingsstien (002s kommentar, tidligere utestet). SELECT set_config('app.current_org', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', false); DO $$ BEGIN INSERT INTO organization (id, name) VALUES ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'Bootstrap-test-org'); RAISE NOTICE 'OK Test 12: org-bootstrap — INSERT lykkes rett etter tomstreng-tilstand.'; END $$; RESET ROLE; RESET app.current_org; ROLLBACK; -- ingen testdata blir liggende igjen \echo '============================================' \echo ' Alle 12 isolasjonstester bestått (se OK-linjer)' \echo ' Dekker: tournament/organization (001),' \echo ' match_hole_result/lineup_lock (003, ADR-012/013),' \echo ' og RLS-tomstreng-fiksen (005)' \echo '============================================'