-- Suppress NOTICE messages when users/groups don't exist SET client_min_messages TO'warning';
DROP USER IFEXISTS regress_rls_alice; DROP USER IFEXISTS regress_rls_bob; DROP USER IFEXISTS regress_rls_carol; DROP USER IFEXISTS regress_rls_dave; DROP USER IFEXISTS regress_rls_exempt_user; DROP ROLE IFEXISTS regress_rls_group1; DROP ROLE IFEXISTS regress_rls_group2;
DROPSCHEMAIFEXISTS regress_rls_schema CASCADE;
RESET client_min_messages;
-- initial setup CREATE USER regress_rls_alice NOLOGIN; CREATE USER regress_rls_bob NOLOGIN; CREATE USER regress_rls_carol NOLOGIN; CREATE USER regress_rls_dave NOLOGIN; CREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN; CREATE ROLE regress_rls_group1 NOLOGIN; CREATE ROLE regress_rls_group2 NOLOGIN;
GRANT regress_rls_group1 TO regress_rls_bob; GRANT regress_rls_group2 TO regress_rls_carol;
CREATESCHEMA regress_rls_schema; GRANTALLONSCHEMA regress_rls_schema to public; SET search_path = regress_rls_schema;
-- setup of malicious function CREATEORREPLACE FUNCTION f_leak(text) RETURNS bool
COST 0.0000001 LANGUAGE plpgsql AS'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END'; GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
-- BASIC Row-Level Security Scenario
SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE uaccount (
pguser name primarykey,
seclv int
); GRANTSELECTON uaccount TO public; INSERTINTO uaccount VALUES
('regress_rls_alice', 99),
('regress_rls_bob', 1),
('regress_rls_carol', 2),
('regress_rls_dave', 3);
-- user's security level must be higher than or equal to document's CREATE POLICY p1 ON document AS PERMISSIVE USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
-- try to create a policy of bogus type CREATE POLICY p1 ON document AS UGLY USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
-- but Dave isn't allowed to anything at cid 50 or above -- this is to make sure that we sort the policies by name first -- when applying WITH CHECK, a later INSERT by Dave should fail due -- to p1r first CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave USING (cid <> 44AND cid < 50);
-- and Dave isn't allowed to see manga documents CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave USING (cid <> 44);
\dp
\d document SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema'AND tablename = 'document'ORDERBY policyname;
-- viewpoint from regress_rls_bob SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TOON; SELECT * FROM document WHERE f_leak(dtitle) ORDERBY did; SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle) ORDERBY did;
-- try a sampled version SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) WHERE f_leak(dtitle) ORDERBY did;
-- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document WHERE f_leak(dtitle) ORDERBY did; SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle) ORDERBY did;
-- try a sampled version SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) WHERE f_leak(dtitle) ORDERBY did;
EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); EXPLAIN (COSTS OFF) SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle);
-- viewpoint from regress_rls_dave SET SESSION AUTHORIZATION regress_rls_dave; SELECT * FROM document WHERE f_leak(dtitle) ORDERBY did; SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle) ORDERBY did;
EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); EXPLAIN (COSTS OFF) SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle);
-- 44 would technically fail for both p2r and p1r, but we should get an error -- back from p1r for this because it sorts first INSERTINTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail -- Just to see a p2r error INSERTINTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
-- only owner can change policies ALTER POLICY p1 ON document USING (true); --fail DROP POLICY p1 ON document; --fail
SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY p1 ON document USING (dauthor = current_user);
-- viewpoint from regress_rls_bob again SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM document WHERE f_leak(dtitle) ORDERBY did; SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle) ORDERby did;
-- viewpoint from rls_regres_carol again SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document WHERE f_leak(dtitle) ORDERBY did; SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle) ORDERby did;
EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); EXPLAIN (COSTS OFF) SELECT * FROM document NATURALJOIN category WHERE f_leak(dtitle);
-- interaction of FK/PK constraints SET SESSION AUTHORIZATION regress_rls_alice; CREATE POLICY p2 ON category USING (CASEWHENcurrent_user = 'regress_rls_bob'THEN cid IN (11, 33) WHENcurrent_user = 'regress_rls_carol'THEN cid IN (22, 44) ELSEfalse END);
ALTERTABLE category ENABLE ROW LEVEL SECURITY;
-- cannot delete PK referenced by invisible FK SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM document d FULL OUTERJOIN category c on d.cid = c.cid ORDERBY d.did, c.cid; DELETEFROM category WHERE cid = 33; -- fails with FK violation
-- can insert FK referencing invisible PK SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM document d FULL OUTERJOIN category c on d.cid = c.cid ORDERBY d.did, c.cid; INSERTINTO document VALUES (11, 33, 1, current_user, 'hoge');
-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row SET SESSION AUTHORIZATION regress_rls_bob; INSERTINTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
-- RLS policies are checked before constraints INSERTINTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation UPDATE document SET did = 8, dauthor = 'regress_rls_carol'WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation
-- database superuser does bypass RLS policy when enabled
RESET SESSION AUTHORIZATION; SET row_security TOON; SELECT * FROM document; SELECT * FROM category;
-- database superuser does bypass RLS policy when disabled
RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM document; SELECT * FROM category;
-- database non-superuser with bypass privilege can bypass RLS policy when disabled SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF; SELECT * FROM document; SELECT * FROM category;
-- RLS policy does not apply to table owner when RLS enabled. SET SESSION AUTHORIZATION regress_rls_alice; SET row_security TOON; SELECT * FROM document; SELECT * FROM category;
-- RLS policy does not apply to table owner when RLS disabled. SET SESSION AUTHORIZATION regress_rls_alice; SET row_security TO OFF; SELECT * FROM document; SELECT * FROM category;
-- -- Table inheritance and RLS policy -- SET SESSION AUTHORIZATION regress_rls_alice;
SET row_security TOON;
CREATETABLE t1 (id intnotnullprimarykey, a int, junk1 text, b text); ALTERTABLE t1 DROPCOLUMN junk1; -- just a disturbing factor GRANTALLON t1 TO public;
COPY t1 FROM stdin WITH ; 1011 aba 1022 bbb 1033 ccc 1044 dad
\.
CREATETABLE t2 (c float) INHERITS (t1); GRANTALLON t2 TO public;
SELECT * FROM t1; EXPLAIN (COSTS OFF) SELECT * FROM t1;
SELECT * FROM t1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
-- reference to system column SELECT tableoid::regclass, * FROM t1; EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
-- reference to whole-row reference SELECT *, t1 FROM t1; EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
-- for share/update lock SELECT * FROM t1 FOR SHARE; EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
-- union all query SELECT a, b, tableoid::regclass FROM t2 UNIONALLSELECT a, b, tableoid::regclass FROM t3; EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNIONALLSELECT a, b, tableoid::regclass FROM t3;
-- superuser is allowed to bypass RLS checks
RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
-- non-superuser with bypass privilege can bypass RLS policy when disabled SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
-- -- Partitioned Tables --
SET SESSION AUTHORIZATION regress_rls_alice;
CREATETABLE part_document (
did int,
cid int,
dlevel intnotnull,
dauthor name,
dtitle text
) PARTITION BY RANGE (cid); GRANTALLON part_document TO public;
-- Create partitions for document categories CREATETABLE part_document_fiction PARTITION OF part_document FORVALUESFROM (11) to (12); CREATETABLE part_document_satire PARTITION OF part_document FORVALUESFROM (55) to (56); CREATETABLE part_document_nonfiction PARTITION OF part_document FORVALUESFROM (99) to (100);
GRANTALLON part_document_fiction TO public; GRANTALLON part_document_satire TO public; GRANTALLON part_document_nonfiction TO public;
-- Create policy on parent -- user's security level must be higher than or equal to document's CREATE POLICY pp1 ON part_document AS PERMISSIVE USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
-- Dave is only allowed to see cid < 55 CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave USING (cid < 55);
\d+ part_document SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema'AND tablename like'%part_document%'ORDERBY policyname;
-- viewpoint from regress_rls_bob SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TOON; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- viewpoint from regress_rls_dave SET SESSION AUTHORIZATION regress_rls_dave; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- Show that RLS policy does not apply for direct inserts to children -- This should fail with RLS POLICY pp1r violation. INSERTINTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail -- But this should succeed. INSERTINTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- success -- We still cannot see the row using the parent SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; -- But we can if we look directly SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDERBY did;
-- Turn on RLS and create policy on child to show RLS is checked before constraints SET SESSION AUTHORIZATION regress_rls_alice; ALTERTABLE part_document_satire ENABLE ROW LEVEL SECURITY; CREATE POLICY pp3 ON part_document_satire AS RESTRICTIVE USING (cid < 55); -- This should fail with RLS violation now. SET SESSION AUTHORIZATION regress_rls_dave; INSERTINTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail -- And now we cannot see directly into the partition either, due to RLS SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDERBY did; -- The parent looks same as before -- viewpoint from regress_rls_dave SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- viewpoint from regress_rls_carol SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did; EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- only owner can change policies ALTER POLICY pp1 ON part_document USING (true); --fail DROP POLICY pp1 ON part_document; --fail
SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY pp1 ON part_document USING (dauthor = current_user);
-- viewpoint from regress_rls_bob again SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did;
-- viewpoint from rls_regres_carol again SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM part_document WHERE f_leak(dtitle) ORDERBY did;
EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-- database superuser does bypass RLS policy when enabled
RESET SESSION AUTHORIZATION; SET row_security TOON; SELECT * FROM part_document ORDERBY did; SELECT * FROM part_document_satire ORDERby did;
-- database non-superuser with bypass privilege can bypass RLS policy when disabled SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF; SELECT * FROM part_document ORDERBY did; SELECT * FROM part_document_satire ORDERby did;
-- RLS policy does not apply to table owner when RLS enabled. SET SESSION AUTHORIZATION regress_rls_alice; SET row_security TOON; SELECT * FROM part_document ORDERby did; SELECT * FROM part_document_satire ORDERby did;
-- When RLS disabled, other users get ERROR. SET SESSION AUTHORIZATION regress_rls_dave; SET row_security TO OFF; SELECT * FROM part_document ORDERby did; SELECT * FROM part_document_satire ORDERby did;
-- Check behavior with a policy that uses a SubPlan not an InitPlan. SET SESSION AUTHORIZATION regress_rls_alice; SET row_security TOON; CREATE POLICY pp3 ON part_document AS RESTRICTIVE USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));
----- Dependencies ----- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security TOON;
CREATETABLE dependee (x integer, y integer);
CREATETABLE dependent (x integer, y integer); CREATE POLICY d1 ON dependent FORALL TO PUBLIC USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
DROPTABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?
DROPTABLE dependee CASCADE;
EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
----- RECURSION ----
-- -- Simple recursion -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE rec1 (x integer, y integer); CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y)); ALTERTABLE rec1 ENABLE ROW LEVEL SECURITY; SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rec1; -- fail, direct recursion
-- -- Mutual recursion -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE rec2 (a integer, b integer); ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y)); CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b)); ALTERTABLE rec2 ENABLE ROW LEVEL SECURITY;
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rec1; -- fail, mutual recursion
-- -- Mutual recursion via views -- SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW rec1v ASSELECT * FROM rec1; CREATE VIEW rec2v ASSELECT * FROM rec2; SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rec1; -- fail, mutual recursion via views
-- -- Mutual recursion via .s.b views -- SET SESSION AUTHORIZATION regress_rls_bob;
DROP VIEW rec1v, rec2v CASCADE;
CREATE VIEW rec1v WITH (security_barrier) ASSELECT * FROM rec1; CREATE VIEW rec2v WITH (security_barrier) ASSELECT * FROM rec2; SET SESSION AUTHORIZATION regress_rls_alice; CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rec1; -- fail, mutual recursion via s.b. views
-- -- recursive RLS and VIEWs in policy -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE s1 (a int, b text); INSERTINTO s1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10) x);
CREATETABLE s2 (x int, y text); INSERTINTO s2 (SELECT x, public.fipshash(x::text) FROM generate_series(-6,6) x);
GRANTSELECTON s1, s2 TO regress_rls_bob;
CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like'%2f%')); CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like'%22%')); CREATE POLICY p3 ON s1 FORINSERTWITHCHECK (a = (SELECT a FROM s1));
SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW v2 ASSELECT * FROM s2 WHERE y like'%af%'; SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
SET SESSION AUTHORIZATION regress_rls_alice; DROP POLICY p3 on s1; ALTER POLICY p2 ON s2 USING (x % 2 = 0);
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM s1 WHERE f_leak(b); -- OK EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM s1 WHERE f_leak(b); -- OK EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
SELECT (SELECT x FROM s1 LIMIT1) xx, * FROM s2 WHERE y like'%28%'; EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT1) xx, * FROM s2 WHERE y like'%28%';
SET SESSION AUTHORIZATION regress_rls_alice; ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like'%d2%')); SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view)
-- prepared statement with regress_rls_alice privilege
PREPARE p1(int) ASSELECT * FROM t1 WHERE a <= $1;
EXECUTE p1(2); EXPLAIN (COSTS OFF) EXECUTE p1(2);
-- superuser is allowed to bypass RLS checks
RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
-- plan cache should be invalidated
EXECUTE p1(2); EXPLAIN (COSTS OFF) EXECUTE p1(2);
PREPARE p2(int) ASSELECT * FROM t1 WHERE a = $1;
EXECUTE p2(2); EXPLAIN (COSTS OFF) EXECUTE p2(2);
-- also, case when privilege switch from superuser SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TOON;
EXECUTE p2(2); EXPLAIN (COSTS OFF) EXECUTE p2(2);
-- -- UPDATE / DELETE and Row-level security -- SET SESSION AUTHORIZATION regress_rls_bob; EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b); UPDATE t1 SET b = b || b WHERE f_leak(b);
EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt'WHERE f_leak(b); UPDATE only t1 SET b = b || '_updt'WHERE f_leak(b);
-- returning clause with system column UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
-- updates with from clause EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 WHERE t2.a = 3and t3.a = 2AND f_leak(t2.b) AND f_leak(t3.b);
UPDATE t2 SET b=t2.b FROM t3 WHERE t2.a = 3and t3.a = 2AND f_leak(t2.b) AND f_leak(t3.b);
EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 WHERE t1.a = 3and t2.a = 3AND f_leak(t1.b) AND f_leak(t2.b);
UPDATE t1 SET b=t1.b FROM t2 WHERE t1.a = 3and t2.a = 3AND f_leak(t1.b) AND f_leak(t2.b);
EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 WHERE t1.a = 3and t2.a = 3AND f_leak(t1.b) AND f_leak(t2.b);
UPDATE t2 SET b=t2.b FROM t1 WHERE t1.a = 3and t2.a = 3AND f_leak(t1.b) AND f_leak(t2.b);
-- updates with from clause self join EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 WHERE t2_1.a = 3AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 WHERE t2_1.a = 3AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 WHERE t1_1.a = 4AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 WHERE t1_1.a = 4AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1 ORDERBY a,b;
SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TOON; EXPLAIN (COSTS OFF) DELETEFROM only t1 WHERE f_leak(b); EXPLAIN (COSTS OFF) DELETEFROM t1 WHERE f_leak(b);
DELETEFROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; DELETEFROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
-- -- S.b. view on top of Row-level security -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE b1 (a int, b text); INSERTINTO b1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10) x);
CREATE POLICY p1 ON b1 USING (a % 2 = 0); ALTERTABLE b1 ENABLE ROW LEVEL SECURITY; GRANTALLON b1 TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW bv1 WITH (security_barrier) ASSELECT * FROM b1 WHERE a > 0WITHCHECKOPTION; GRANTALLON bv1 TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol;
EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b); SELECT * FROM bv1 WHERE f_leak(b);
INSERTINTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO INSERTINTO bv1 VALUES (11, 'xxx'); -- should fail RLS check INSERTINTO bv1 VALUES (12, 'xxx'); -- ok
EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy'WHERE a = 4AND f_leak(b); UPDATE bv1 SET b = 'yyy'WHERE a = 4AND f_leak(b);
EXPLAIN (COSTS OFF) DELETEFROM bv1 WHERE a = 6AND f_leak(b); DELETEFROM bv1 WHERE a = 6AND f_leak(b);
SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM b1; -- -- INSERT ... ON CONFLICT DO UPDATE and Row-level security --
SET SESSION AUTHORIZATION regress_rls_alice; DROP POLICY p1 ON document; DROP POLICY p1r ON document;
CREATE POLICY p1 ON document FORSELECTUSING (true); CREATE POLICY p2 ON document FORINSERTWITHCHECK (dauthor = current_user); CREATE POLICY p3 ON document FORUPDATE USING (cid = (SELECT cid from category WHERE cname = 'novel')) WITHCHECK (dauthor = current_user);
SET SESSION AUTHORIZATION regress_rls_bob;
-- Exists... SELECT * FROM document WHERE did = 2;
-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since -- alternative UPDATE path happens to be taken): INSERTINTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;
-- Violates USING qual for UPDATE policy p3. -- -- UPDATE path is taken, but UPDATE fails purely because *existing* row to be -- updated is not a "novel"/cid 11 (row is not leaked, even though we have -- SELECT privileges sufficient to see the row in this instance): INSERTINTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement INSERTINTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle; -- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs -- not violated): INSERTINTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle RETURNING *; -- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated): INSERTINTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; -- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the -- case in respect of *existing* tuple): INSERTINTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; -- Same query a third time, but now fails due to existing tuple finally not -- passing quals: INSERTINTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; -- Don't fail just because INSERT doesn't satisfy WITH CHECK option that -- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE -- path *isn't* taken, and so UPDATE-related policy does not apply: INSERTINTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle RETURNING *; -- But this time, the same statement fails, because the UPDATE path is taken, -- and updating the row just inserted falls afoul of security barrier qual -- (enforced as WCO) -- what we might have updated target tuple to is -- irrelevant, in fact. INSERTINTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle RETURNING *;
-- Test default USING qual enforced as WCO SET SESSION AUTHORIZATION regress_rls_alice; DROP POLICY p1 ON document; DROP POLICY p2 ON document; DROP POLICY p3 ON document;
CREATE POLICY p3_with_default ON document FORUPDATE USING (cid = (SELECT cid from category WHERE cname = 'novel'));
SET SESSION AUTHORIZATION regress_rls_bob; -- Just because WCO-style enforcement of USING quals occurs with -- existing/target tuple does not mean that the implementation can be allowed -- to fail to also enforce this qual against the final tuple appended to -- relation (since in the absence of an explicit WCO, this is also interpreted -- as an UPDATE/ALL WCO in general). -- -- UPDATE path is taken here (fails due to existing tuple). Note that this is -- not reported as a "USING expression", because it's an RLS UPDATE check that originated as -- a USING qual for the purposes of RLS in general, as opposed to an explicit -- USING qual that is ordinarily a security barrier. We leave it up to the -- UPDATE to make this fail: INSERTINTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle RETURNING *;
-- UPDATE path is taken here. Existing tuple passes, since its cid -- corresponds to "novel", but default USING qual is enforced against -- post-UPDATE tuple too (as always when updating with a policy that lacks an -- explicit WCO), and so this fails: INSERTINTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel') ON CONFLICT (did) DO UPDATESET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;
SET SESSION AUTHORIZATION regress_rls_alice; DROP POLICY p3_with_default ON document;
-- -- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE -- tests) -- CREATE POLICY p3_with_all ON document FORALL USING (cid = (SELECT cid from category WHERE cname = 'novel')) WITHCHECK (dauthor = current_user);
SET SESSION AUTHORIZATION regress_rls_bob;
-- Fails, since ALL WCO is enforced in insert path: INSERTINTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle, cid = 33; -- Fails, since ALL policy USING qual is enforced (existing, target tuple is in -- violation, since it has the "manga" cid): INSERTINTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') ON CONFLICT (did) DO UPDATESET dtitle = EXCLUDED.dtitle; -- Fails, since ALL WCO are enforced: INSERTINTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') ON CONFLICT (did) DO UPDATESET dauthor = 'regress_rls_carol';
-- -- MERGE --
RESET SESSION AUTHORIZATION; DROP POLICY p3_with_all ON document;
ALTERTABLE document ADDCOLUMN dnotes text DEFAULT''; -- all documents are readable CREATE POLICY p1 ON document FORSELECTUSING (true); -- one may insert documents only authored by them CREATE POLICY p2 ON document FORINSERTWITHCHECK (dauthor = current_user); -- one may only update documents in 'novel' category and new dlevel must be > 0 CREATE POLICY p3 ON document FORUPDATE USING (cid = (SELECT cid from category WHERE cname = 'novel')) WITHCHECK (dlevel > 0); -- one may only delete documents in 'manga' category CREATE POLICY p4 ON document FORDELETE USING (cid = (SELECT cid from category WHERE cname = 'manga'));
SELECT * FROM document;
SET SESSION AUTHORIZATION regress_rls_bob;
-- Fails, since update violates WITH CHECK qual on dlevel
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge1 ', dlevel = 0;
-- Should be OK since USING and WITH CHECK quals pass
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge2 ';
-- Even when dlevel is updated explicitly, but to the existing value
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge3 ', dlevel = 1;
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow -- updating an item in category 'science fiction'
MERGE INTO document d USING (SELECT3as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge ';
-- The same thing with DELETE action, but fails again because no permissions -- to delete items in 'science fiction' category that did 3 belongs to.
MERGE INTO document d USING (SELECT3as sdid) s ON did = s.sdid WHEN MATCHED THEN DELETE;
-- Document with did 4 belongs to 'manga' category which is allowed for -- deletion. But this fails because the UPDATE action is matched first and -- UPDATE policy does not allow updation in the category.
MERGE INTO document d USING (SELECT4as sdid) s ON did = s.sdid WHEN MATCHED AND dnotes = ''THEN UPDATESET dnotes = dnotes || ' notes added by merge ' WHEN MATCHED THEN DELETE;
-- UPDATE action is not matched this time because of the WHEN qual. -- DELETE still fails because role regress_rls_bob does not have SELECT -- privileges on 'manga' category row in the category table.
MERGE INTO document d USING (SELECT4as sdid) s ON did = s.sdid WHEN MATCHED AND dnotes <> ''THEN UPDATESET dnotes = dnotes || ' notes added by merge ' WHEN MATCHED THEN DELETE;
-- OK if DELETE is replaced with DO NOTHING
MERGE INTO document d USING (SELECT4as sdid) s ON did = s.sdid WHEN MATCHED AND dnotes <> ''THEN UPDATESET dnotes = dnotes || ' notes added by merge ' WHEN MATCHED THEN
DO NOTHING;
SELECT * FROM document WHERE did = 4;
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed -- this time
RESET SESSION AUTHORIZATION; SET SESSION AUTHORIZATION regress_rls_carol;
MERGE INTO document d USING (SELECT4as sdid) s ON did = s.sdid WHEN MATCHED AND dnotes <> ''THEN UPDATESET dnotes = dnotes || ' notes added by merge ' WHEN MATCHED THEN DELETE;
-- Switch back to regress_rls_bob role
RESET SESSION AUTHORIZATION; SET SESSION AUTHORIZATION regress_rls_bob;
-- Try INSERT action. This fails because we are trying to insert -- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow -- that
MERGE INTO document d USING (SELECT12as sdid) s ON did = s.sdid WHEN MATCHED THEN DELETE WHENNOT MATCHED THEN INSERTVALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
-- This should be fine
MERGE INTO document d USING (SELECT12as sdid) s ON did = s.sdid WHEN MATCHED THEN DELETE WHENNOT MATCHED THEN INSERTVALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-- ok
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge4 ' WHENNOT MATCHED THEN INSERTVALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-- drop and create a new SELECT policy which prevents us from reading -- any document except with category 'novel'
RESET SESSION AUTHORIZATION; DROP POLICY p1 ON document; CREATE POLICY p1 ON document FORSELECT USING (cid = (SELECT cid from category WHERE cname = 'novel'));
SET SESSION AUTHORIZATION regress_rls_bob;
-- MERGE can no longer see the matching row and hence attempts the -- NOT MATCHED action, which results in unique key violation
MERGE INTO document d USING (SELECT7as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge5 ' WHENNOT MATCHED THEN INSERTVALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-- UPDATE action fails if new row is not visible
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge6 ',
cid = (SELECT cid from category WHERE cname = 'technology');
-- but OK if new row is visible
MERGE INTO document d USING (SELECT1as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge7 ',
cid = (SELECT cid from category WHERE cname = 'novel');
-- OK to insert a new row that is not visible
MERGE INTO document d USING (SELECT13as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge8 ' WHENNOT MATCHED THEN INSERTVALUES (13, 44, 1, 'regress_rls_bob', 'new manga'); SELECT * FROM document WHERE did = 13;
-- but not OK if RETURNING is used
MERGE INTO document d USING (SELECT14as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge9 ' WHENNOT MATCHED THEN INSERTVALUES (14, 44, 1, 'regress_rls_bob', 'new manga')
RETURNING *;
-- but OK if new row is visible
MERGE INTO document d USING (SELECT14as sdid) s ON did = s.sdid WHEN MATCHED THEN UPDATESET dnotes = dnotes || ' notes added by merge10 ' WHENNOT MATCHED THEN INSERTVALUES (14, 11, 1, 'regress_rls_bob', 'new novel')
RETURNING *;
RESET SESSION AUTHORIZATION; -- drop the restrictive SELECT policy so that we can look at the -- final state of the table DROP POLICY p1 ON document; -- Just check everything went per plan SELECT * FROM document;
-- -- ROLE/GROUP -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE z1 (a int, b text); CREATETABLE z2 (a int, b text);
GRANTSELECTON z1,z2 TO regress_rls_group1, regress_rls_group2,
regress_rls_bob, regress_rls_carol;
-- -- Views should follow policy for view owner. -- -- View and Table owner are the same. SET SESSION AUTHORIZATION regress_rls_alice; CREATE VIEW rls_view ASSELECT * FROM z1 WHERE f_leak(b); GRANTSELECTON rls_view TO regress_rls_bob;
-- Query as role that is not owner of view or table. Should return all records. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as view/table owner. Should return all records. SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view; DROP VIEW rls_view;
-- View and Table owners are different. SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW rls_view ASSELECT * FROM z1 WHERE f_leak(b); GRANTSELECTON rls_view TO regress_rls_alice;
-- Query as role that is not owner of view but is owner of table. -- Should return records based on view owner policies. SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not owner of table but is owner of view. -- Should return records based on view owner policies. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not the owner of the table or view without permissions. SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view with permissions. SET SESSION AUTHORIZATION regress_rls_bob; GRANTSELECTON rls_view TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Policy requiring access to another table. SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE z1_blacklist (a int); INSERTINTO z1_blacklist VALUES (3), (4); CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOTIN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view without permissions. SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not owner of table but is owner of view with permissions. SET SESSION AUTHORIZATION regress_rls_alice; GRANTSELECTON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not the owner of the table or view with permissions. SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_alice; REVOKESELECTON z1_blacklist FROM regress_rls_bob; DROP POLICY p3 ON z1;
SET SESSION AUTHORIZATION regress_rls_bob; DROP VIEW rls_view;
-- -- Security invoker views should follow policy for current user. -- -- View and table owner are the same. SET SESSION AUTHORIZATION regress_rls_alice; CREATE VIEW rls_view WITH (security_invoker) AS SELECT * FROM z1 WHERE f_leak(b); GRANTSELECTON rls_view TO regress_rls_bob; GRANTSELECTON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records. SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Queries as other users. -- Should return records based on current user's policies. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- View and table owners are different. SET SESSION AUTHORIZATION regress_rls_alice; DROP VIEW rls_view;
SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW rls_view WITH (security_invoker) AS SELECT * FROM z1 WHERE f_leak(b); GRANTSELECTON rls_view TO regress_rls_alice; GRANTSELECTON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records. SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Queries as other users. -- Should return records based on current user's policies. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Policy requiring access to another table. SET SESSION AUTHORIZATION regress_rls_alice; CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOTIN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions. SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view without permissions. SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not owner of table but is owner of view with permissions. SET SESSION AUTHORIZATION regress_rls_alice; GRANTSELECTON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not the owner of the table or view without permissions. SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; --fail - permission denied. EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view with permissions. SET SESSION AUTHORIZATION regress_rls_alice; GRANTSELECTON z1_blacklist TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM rls_view; EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_bob; DROP VIEW rls_view;
-- -- Command specific -- SET SESSION AUTHORIZATION regress_rls_alice;
CREATETABLE x1 (a int, b text, c text); GRANTALLON x1 TO PUBLIC;
CREATE POLICY p0 ON x1 FORALLUSING (c = current_user); CREATE POLICY p1 ON x1 FORSELECTUSING (a % 2 = 0); CREATE POLICY p2 ON x1 FORINSERTWITHCHECK (a % 2 = 1); CREATE POLICY p3 ON x1 FORUPDATEUSING (a % 2 = 0); CREATE POLICY p4 ON x1 FORDELETEUSING (a < 8);
ALTERTABLE x1 ENABLE ROW LEVEL SECURITY;
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM x1 WHERE f_leak(b) ORDERBY a ASC; UPDATE x1 SET b = b || '_updt'WHERE f_leak(b) RETURNING *;
SET SESSION AUTHORIZATION regress_rls_carol; SELECT * FROM x1 WHERE f_leak(b) ORDERBY a ASC; UPDATE x1 SET b = b || '_updt'WHERE f_leak(b) RETURNING *; DELETEFROM x1 WHERE f_leak(b) RETURNING *;
-- -- Duplicate Policy Names -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE y1 (a int, b text); CREATETABLE y2 (a int, b text);
GRANTALLON y1, y2 TO regress_rls_bob;
CREATE POLICY p1 ON y1 FORALLUSING (a % 2 = 0); CREATE POLICY p2 ON y1 FORSELECTUSING (a > 2); CREATE POLICY p1 ON y1 FORSELECTUSING (a % 2 = 1); --fail CREATE POLICY p1 ON y2 FORALLUSING (a % 2 = 0); --OK
-- -- Expression structure with SBV -- -- Create view as table owner. RLS should NOT be applied. SET SESSION AUTHORIZATION regress_rls_alice; CREATE VIEW rls_sbv WITH (security_barrier) AS SELECT * FROM y1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); DROP VIEW rls_sbv;
-- Create view as role that does not own table. RLS should be applied. SET SESSION AUTHORIZATION regress_rls_bob; CREATE VIEW rls_sbv WITH (security_barrier) AS SELECT * FROM y1 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); DROP VIEW rls_sbv;
-- -- Expression structure -- SET SESSION AUTHORIZATION regress_rls_alice; INSERTINTO y2 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x); CREATE POLICY p2 ON y2 USING (a % 3 = 0); CREATE POLICY p3 ON y2 USING (a % 4 = 0);
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM y2 WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
-- -- Qual push-down of leaky functions, when not referring to table -- SELECT * FROM y2 WHERE f_leak('abc'); EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
DROPTABLE test_qual_pushdown;
-- -- Plancache invalidate on user change. --
RESET SESSION AUTHORIZATION;
DROPTABLE t1 CASCADE;
CREATETABLE t1 (a integer);
GRANTSELECTON t1 TO regress_rls_bob, regress_rls_carol;
CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0); CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);
ALTERTABLE t1 ENABLE ROW LEVEL SECURITY;
-- Prepare as regress_rls_bob SET ROLE regress_rls_bob;
PREPARE role_inval ASSELECT * FROM t1; -- Check plan EXPLAIN (COSTS OFF) EXECUTE role_inval;
-- Change to regress_rls_carol SET ROLE regress_rls_carol; -- Check plan- should be different EXPLAIN (COSTS OFF) EXECUTE role_inval;
-- Change back to regress_rls_bob SET ROLE regress_rls_bob; -- Check plan- should be back to original EXPLAIN (COSTS OFF) EXECUTE role_inval;
-- -- CTE and RLS --
RESET SESSION AUTHORIZATION; DROPTABLE t1 CASCADE; CREATETABLE t1 (a integer, b text); CREATE POLICY p1 ON t1 USING (a % 2 = 0);
ALTERTABLE t1 ENABLE ROW LEVEL SECURITY;
GRANTALLON t1 TO regress_rls_bob;
INSERTINTO t1 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x);
SET SESSION AUTHORIZATION regress_rls_bob;
WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; EXPLAIN (COSTS OFF) WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
WITH cte1 AS (INSERTINTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail WITH cte1 AS (INSERTINTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
-- -- Rename Policy --
RESET SESSION AUTHORIZATION; ALTER POLICY p1 ON t1 RENAMETO p1; --fail
SELECT polname, relname FROM pg_policy pol JOIN pg_class pc ON (pc.oid = pol.polrelid) WHERE relname = 't1';
ALTER POLICY p1 ON t1 RENAMETO p2; --ok
SELECT polname, relname FROM pg_policy pol JOIN pg_class pc ON (pc.oid = pol.polrelid) WHERE relname = 't1';
-- -- Check INSERT SELECT -- SET SESSION AUTHORIZATION regress_rls_bob; CREATETABLE t2 (a integer, b text); INSERTINTO t2 (SELECT * FROM t1); EXPLAIN (COSTS OFF) INSERTINTO t2 (SELECT * FROM t1); SELECT * FROM t2; EXPLAIN (COSTS OFF) SELECT * FROM t2; CREATETABLE t3 ASSELECT * FROM t1; SELECT * FROM t3; SELECT * INTO t4 FROM t1; SELECT * FROM t4;
-- -- RLS with JOIN -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE blog (id integer, author text, post text); CREATETABLE comment (blog_id integer, message text);
SET SESSION AUTHORIZATION regress_rls_bob; -- Check RLS JOIN with Non-RLS. SELECT id, author, message FROM blog JOIN comment ON id = blog_id; -- Check Non-RLS JOIN with RLS. SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
SET SESSION AUTHORIZATION regress_rls_alice; CREATE POLICY comment_1 ON comment USING (blog_id < 4);
ALTERTABLE comment ENABLE ROW LEVEL SECURITY;
SET SESSION AUTHORIZATION regress_rls_bob; -- Check RLS JOIN RLS SELECT id, author, message FROM blog JOIN comment ON id = blog_id; SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
SET SESSION AUTHORIZATION regress_rls_alice; DROPTABLE blog, comment;
-- -- Default Deny Policy --
RESET SESSION AUTHORIZATION; DROP POLICY p2 ON t1; ALTERTABLE t1 OWNER TO regress_rls_alice;
-- Check that default deny does not apply to superuser.
RESET SESSION AUTHORIZATION; SELECT * FROM t1; EXPLAIN (COSTS OFF) SELECT * FROM t1;
-- Check that default deny does not apply to table owner. SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM t1; EXPLAIN (COSTS OFF) SELECT * FROM t1;
-- Check that default deny applies to non-owner/non-superuser when RLS on. SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TOON; SELECT * FROM t1; EXPLAIN (COSTS OFF) SELECT * FROM t1; SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM t1; EXPLAIN (COSTS OFF) SELECT * FROM t1;
-- -- COPY TO/FROM --
RESET SESSION AUTHORIZATION; DROPTABLE copy_t CASCADE; CREATETABLE copy_t (a integer, b text); CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
ALTERTABLE copy_t ENABLE ROW LEVEL SECURITY;
GRANTALLON copy_t TO regress_rls_bob, regress_rls_exempt_user;
INSERTINTO copy_t (SELECT x, public.fipshash(x::text) FROM generate_series(0,10) x);
-- Check COPY TO as Superuser/owner.
RESET SESSION AUTHORIZATION; SET row_security TO OFF;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; SET row_security TOON;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ',';
-- Check COPY TO as user with permissions. SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO OFF;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS SET row_security TOON;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user with permissions and BYPASSRLS SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --ok SET row_security TOON;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user without permissions. SET row_security TO OFF; SET SESSION AUTHORIZATION regress_rls_carol; SET row_security TO OFF;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS SET row_security TOON;
COPY (SELECT * FROM copy_t ORDERBY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
-- Check COPY relation TO; keep it just one row to avoid reordering issues
RESET SESSION AUTHORIZATION; SET row_security TOON; CREATETABLE copy_rel_to (a integer, b text); CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);
ALTERTABLE copy_rel_to ENABLE ROW LEVEL SECURITY;
GRANTALLON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;
-- Check COPY TO as Superuser/owner.
RESET SESSION AUTHORIZATION; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
-- Check COPY TO as user with permissions. SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user with permissions and BYPASSRLS SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user without permissions. SET row_security TO OFF; SET SESSION AUTHORIZATION regress_rls_carol; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
-- Check behavior with a child table.
RESET SESSION AUTHORIZATION; SET row_security TOON; CREATETABLE copy_rel_to_child () INHERITS (copy_rel_to); INSERTINTO copy_rel_to_child VALUES (1, 'one'), (2, 'two');
-- Check COPY TO as Superuser/owner.
RESET SESSION AUTHORIZATION; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
-- Check COPY TO as user with permissions. SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user with permissions and BYPASSRLS SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
-- Check COPY TO as user without permissions. SET row_security TO OFF; SET SESSION AUTHORIZATION regress_rls_carol; SET row_security TO OFF;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied SET row_security TOON;
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
-- Check COPY FROM as Superuser/owner.
RESET SESSION AUTHORIZATION; SET row_security TO OFF;
COPY copy_t FROM STDIN; --ok 1 abc 2 bcd 3 cde 4 def
\. SET row_security TOON;
COPY copy_t FROM STDIN; --ok 1 abc 2 bcd 3 cde 4 def
\.
-- Check COPY FROM as user with permissions. SET SESSION AUTHORIZATION regress_rls_bob; SET row_security TO OFF;
COPY copy_t FROM STDIN; --fail - would be affected by RLS. SET row_security TOON;
COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
-- Check COPY FROM as user with permissions and BYPASSRLS SET SESSION AUTHORIZATION regress_rls_exempt_user; SET row_security TOON;
COPY copy_t FROM STDIN; --ok 1 abc 2 bcd 3 cde 4 def
\.
-- Check COPY FROM as user without permissions. SET SESSION AUTHORIZATION regress_rls_carol; SET row_security TO OFF;
COPY copy_t FROM STDIN; --fail - permission denied. SET row_security TOON;
COPY copy_t FROM STDIN; --fail - permission denied.
DECLARE current_check_cursor SCROLL CURSORFORSELECT * FROM current_check; -- Returns rows that can be seen according to SELECT policy, like plain SELECT -- above (even rows) FETCH ABSOLUTE 1FROM current_check_cursor; -- Still cannot UPDATE row 2 through cursor UPDATE current_check SET payload = payload || '_new'WHERE CURRENT OF current_check_cursor RETURNING *; -- Can update row 4 through cursor, which is the next visible row FETCH RELATIVE 1FROM current_check_cursor; UPDATE current_check SET payload = payload || '_new'WHERE CURRENT OF current_check_cursor RETURNING *; SELECT * FROM current_check; -- Plan should be a subquery TID scan EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor; -- Similarly can only delete row 4 FETCH ABSOLUTE 1FROM current_check_cursor; DELETEFROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; FETCH RELATIVE 1FROM current_check_cursor; DELETEFROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; SELECT * FROM current_check;
COMMIT;
-- Check that RLS filters that are tidquals don't override WHERE CURRENT OF
BEGIN; CREATETABLE current_check_2 (a int, b text); INSERTINTO current_check_2 VALUES (1, 'Apple'); ALTERTABLE current_check_2 ENABLE ROW LEVEL SECURITY; ALTERTABLE current_check_2 FORCE ROW LEVEL SECURITY; -- policy must accept ctid = (InvalidBlockNumber,0) since updates check it -- before assigning a ctid to the new row CREATE POLICY p1 ON current_check_2 AS PERMISSIVE USING (ctid IN ('(0,1)', '(0,2)', '(4294967295,0)')); SELECT ctid, * FROM current_check_2; DECLARE current_check_cursor CURSORFORSELECT * FROM current_check_2; FETCHFROM current_check_cursor; EXPLAIN (COSTS OFF) UPDATE current_check_2 SET b = 'Manzana'WHERE CURRENT OF current_check_cursor; UPDATE current_check_2 SET b = 'Manzana'WHERE CURRENT OF current_check_cursor; SELECT ctid, * FROM current_check_2;
ROLLBACK;
-- -- check pg_stats view filtering -- SET row_security TOON; SET SESSION AUTHORIZATION regress_rls_alice; ANALYZE current_check; -- Stats visible SELECT row_security_active('current_check'); SELECT attname, most_common_vals FROM pg_stats WHERE tablename = 'current_check' ORDERBY1;
SET SESSION AUTHORIZATION regress_rls_bob; -- Stats not visible SELECT row_security_active('current_check'); SELECT attname, most_common_vals FROM pg_stats WHERE tablename = 'current_check' ORDERBY1;
-- -- Collation support --
BEGIN; CREATETABLE coll_t (c) ASVALUES ('bar'::text); CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE"C")); ALTERTABLE coll_t ENABLE ROW LEVEL SECURITY; GRANTSELECTON coll_t TO regress_rls_alice; SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass; SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM coll_t;
ROLLBACK;
-- -- Shared Object Dependencies --
RESET SESSION AUTHORIZATION;
BEGIN; CREATE ROLE regress_rls_eve; CREATE ROLE regress_rls_frank; CREATETABLE tbl1 (c) ASVALUES ('bar'::text); GRANTSELECTONTABLE tbl1 TO regress_rls_eve; CREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true); SELECT refclassid::regclass, deptype FROM pg_depend WHERE classid = 'pg_policy'::regclass AND refobjid = 'tbl1'::regclass; SELECT refclassid::regclass, deptype FROM pg_shdepend WHERE classid = 'pg_policy'::regclass AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);
SAVEPOINT q; DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p
ROLLBACK TO q;
ALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);
SAVEPOINT q; DROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT
ROLLBACK TO q;
REVOKEALLONTABLE tbl1 FROM regress_rls_eve;
SAVEPOINT q; DROP ROLE regress_rls_eve; --succeeds
ROLLBACK TO q;
SAVEPOINT q; DROP ROLE regress_rls_frank; --fails due to dependency on POLICY p
ROLLBACK TO q;
DROP POLICY p ON tbl1;
SAVEPOINT q; DROP ROLE regress_rls_frank; -- succeeds
ROLLBACK TO q;
ROLLBACK; -- cleanup
-- -- Policy expression handling --
BEGIN; CREATETABLE t (c) ASVALUES ('bar'::text); CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions
ROLLBACK;
-- -- Non-target relations are only subject to SELECT policies -- SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE r1 (a int); CREATETABLE r2 (a int); INSERTINTO r1 VALUES (10), (20); INSERTINTO r2 VALUES (10), (20);
GRANTALLON r1, r2 TO regress_rls_bob;
CREATE POLICY p1 ON r1 USING (true); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY;
CREATE POLICY p1 ON r2 FORSELECTUSING (true); CREATE POLICY p2 ON r2 FORINSERTWITHCHECK (false); CREATE POLICY p3 ON r2 FORUPDATEUSING (false); CREATE POLICY p4 ON r2 FORDELETEUSING (false); ALTERTABLE r2 ENABLE ROW LEVEL SECURITY;
SET SESSION AUTHORIZATION regress_rls_bob; SELECT * FROM r1; SELECT * FROM r2;
-- r2 is read-only INSERTINTO r2 VALUES (2); -- Not allowed UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing DELETEFROM r2 RETURNING *; -- Deletes nothing
-- r2 can be used as a non-target relation in DML INSERTINTO r1 SELECT a + 1FROM r2 RETURNING *; -- OK UPDATE r1 SET a = r2.a + 2FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK DELETEFROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK SELECT * FROM r1; SELECT * FROM r2;
SET SESSION AUTHORIZATION regress_rls_alice; DROPTABLE r1; DROPTABLE r2;
-- -- FORCE ROW LEVEL SECURITY applies RLS to owners too -- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security = on; CREATETABLE r1 (a int); INSERTINTO r1 VALUES (10), (20);
CREATE POLICY p1 ON r1 USING (false); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY; ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
-- No error, but no rows TABLE r1;
-- RLS error INSERTINTO r1 VALUES (1);
-- No error (unable to see any rows to update) UPDATE r1 SET a = 1; TABLE r1;
-- No error (unable to see any rows to delete) DELETEFROM r1; TABLE r1;
SET row_security = off; -- these all fail, would be affected by RLS TABLE r1; UPDATE r1 SET a = 1; DELETEFROM r1;
DROPTABLE r1;
-- -- FORCE ROW LEVEL SECURITY does not break RI -- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security = on; CREATETABLE r1 (a intPRIMARYKEY); CREATETABLE r2 (a intREFERENCES r1); INSERTINTO r1 VALUES (10), (20); INSERTINTO r2 VALUES (10), (20);
-- Create policies on r2 which prevent the -- owner from seeing any rows, but RI should -- still see them. CREATE POLICY p1 ON r2 USING (false); ALTERTABLE r2 ENABLE ROW LEVEL SECURITY; ALTERTABLE r2 FORCE ROW LEVEL SECURITY;
-- Errors due to rows in r2 DELETEFROM r1;
-- Reset r2 to no-RLS DROP POLICY p1 ON r2; ALTERTABLE r2 NO FORCE ROW LEVEL SECURITY; ALTERTABLE r2 DISABLE ROW LEVEL SECURITY;
-- clean out r2 for INSERT test below DELETEFROM r2;
-- Change r1 to not allow rows to be seen CREATE POLICY p1 ON r1 USING (false); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY; ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
-- No rows seen TABLE r1;
-- No error, RI still sees that row exists in r1 INSERTINTO r2 VALUES (10);
DROPTABLE r2; DROPTABLE r1;
-- Ensure cascaded DELETE works CREATETABLE r1 (a intPRIMARYKEY); CREATETABLE r2 (a intREFERENCES r1 ONDELETECASCADE); INSERTINTO r1 VALUES (10), (20); INSERTINTO r2 VALUES (10), (20);
-- Create policies on r2 which prevent the -- owner from seeing any rows, but RI should -- still see them. CREATE POLICY p1 ON r2 USING (false); ALTERTABLE r2 ENABLE ROW LEVEL SECURITY; ALTERTABLE r2 FORCE ROW LEVEL SECURITY;
-- Deletes all records from both DELETEFROM r1;
-- Remove FORCE from r2 ALTERTABLE r2 NO FORCE ROW LEVEL SECURITY;
-- As owner, we now bypass RLS -- verify no rows in r2 now TABLE r2;
DROPTABLE r2; DROPTABLE r1;
-- Ensure cascaded UPDATE works CREATETABLE r1 (a intPRIMARYKEY); CREATETABLE r2 (a intREFERENCES r1 ONUPDATECASCADE); INSERTINTO r1 VALUES (10), (20); INSERTINTO r2 VALUES (10), (20);
-- Create policies on r2 which prevent the -- owner from seeing any rows, but RI should -- still see them. CREATE POLICY p1 ON r2 USING (false); ALTERTABLE r2 ENABLE ROW LEVEL SECURITY; ALTERTABLE r2 FORCE ROW LEVEL SECURITY;
-- Updates records in both UPDATE r1 SET a = a+5;
-- Remove FORCE from r2 ALTERTABLE r2 NO FORCE ROW LEVEL SECURITY;
-- As owner, we now bypass RLS -- verify records in r2 updated TABLE r2;
DROPTABLE r2; DROPTABLE r1;
-- -- Test INSERT+RETURNING applies SELECT policies as -- WithCheckOptions (meaning an error is thrown) -- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security = on; CREATETABLE r1 (a int);
CREATE POLICY p1 ON r1 FORSELECTUSING (false); CREATE POLICY p2 ON r1 FORINSERTWITHCHECK (true); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY; ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
-- Works fine INSERTINTO r1 VALUES (10), (20);
-- No error, but no rows TABLE r1;
SET row_security = off; -- fail, would be affected by RLS TABLE r1;
-- -- Test UPDATE+RETURNING applies SELECT policies as -- WithCheckOptions (meaning an error is thrown) -- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security = on; CREATETABLE r1 (a intPRIMARYKEY);
CREATE POLICY p1 ON r1 FORSELECTUSING (a < 20); CREATE POLICY p2 ON r1 FORUPDATEUSING (a < 20) WITHCHECK (true); CREATE POLICY p3 ON r1 FORINSERTWITHCHECK (true); INSERTINTO r1 VALUES (10); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY; ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
-- Works fine UPDATE r1 SET a = 30;
-- Show updated rows ALTERTABLE r1 NO FORCE ROW LEVEL SECURITY; TABLE r1; -- reset value in r1 for test with RETURNING UPDATE r1 SET a = 10;
-- Verify row reset TABLE r1;
ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
-- Error UPDATE r1 SET a = 30 RETURNING *;
-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out INSERTINTO r1 VALUES (10) ON CONFLICT (a) DO UPDATESET a = 30 RETURNING *;
-- Should still error out without RETURNING (use of arbiter always requires -- SELECT permissions) INSERTINTO r1 VALUES (10) ON CONFLICT (a) DO UPDATESET a = 30; INSERTINTO r1 VALUES (10) ON CONFLICT ONCONSTRAINT r1_pkey DO UPDATESET a = 30;
DROPTABLE r1;
-- -- Test policies using virtual generated columns -- SET SESSION AUTHORIZATION regress_rls_alice; SET row_security = on; CREATETABLE r1 (a int, b int GENERATED ALWAYS AS (a * 10) VIRTUAL); ALTERTABLE r1 ADD c int GENERATED ALWAYS AS (a * 100) VIRTUAL; INSERTINTO r1 VALUES (1), (2), (4);
CREATE POLICY p0 ON r1 USING (b * 10 = c); CREATE POLICY p1 ON r1 AS RESTRICTIVE USING (b > 10); CREATE POLICY p2 ON r1 AS RESTRICTIVE USING ((SELECT c) < 400); ALTERTABLE r1 ENABLE ROW LEVEL SECURITY; ALTERTABLE r1 FORCE ROW LEVEL SECURITY;
CREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2)); ALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;
-- Should return one SELECT count(*) = 1FROM pg_depend WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');
ALTER POLICY dep_p1 ON dep1 USING (true);
-- Should return one SELECT count(*) = 1FROM pg_shdepend WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');
-- Should return one SELECT count(*) = 1FROM pg_shdepend WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');
-- Should return zero SELECT count(*) = 0FROM pg_depend WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');
-- DROP OWNED BY testing
RESET SESSION AUTHORIZATION;
CREATE ROLE regress_rls_dob_role1; CREATE ROLE regress_rls_dob_role2;
CREATETABLE dob_t1 (c1 int); CREATETABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);
CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true); DROP OWNED BY regress_rls_dob_role1; DROP POLICY p1 ON dob_t1; -- should fail, already gone
CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); DROP OWNED BY regress_rls_dob_role1; DROP POLICY p1 ON dob_t1; -- should succeed
-- same cases with duplicate polroles entries CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1 USING (true); DROP OWNED BY regress_rls_dob_role1; DROP POLICY p1 ON dob_t1; -- should fail, already gone
CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1,regress_rls_dob_role2 USING (true); DROP OWNED BY regress_rls_dob_role1; DROP POLICY p1 ON dob_t1; -- should succeed
-- partitioned target CREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); DROP OWNED BY regress_rls_dob_role1; DROP POLICY p1 ON dob_t2; -- should succeed
DROP USER regress_rls_dob_role1; DROP USER regress_rls_dob_role2;
-- Bug #15708: view + table with RLS should check policies as view owner CREATETABLE ref_tbl (a int); INSERTINTO ref_tbl VALUES (1);
CREATETABLE rls_tbl (a int); INSERTINTO rls_tbl VALUES (10); ALTERTABLE rls_tbl ENABLE ROW LEVEL SECURITY; CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT1FROM ref_tbl));
GRANTSELECTON ref_tbl TO regress_rls_bob; GRANTSELECTON rls_tbl TO regress_rls_bob;
CREATE VIEW rls_view ASSELECT * FROM rls_tbl; ALTER VIEW rls_view OWNER TO regress_rls_bob; GRANTSELECTON rls_view TO regress_rls_alice;
SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM ref_tbl; -- Permission denied SELECT * FROM rls_tbl; -- Permission denied SELECT * FROM rls_view; -- OK
RESET SESSION AUTHORIZATION;
DROP VIEW rls_view; DROPTABLE rls_tbl; DROPTABLE ref_tbl;
CREATETABLE rls_ptbl (a int) PARTITION BY RANGE (a); CREATETABLE rls_part PARTITION OF rls_ptbl FORVALUESFROM (-100) TO (100); INSERTINTO rls_ptbl SELECT x/10FROM generate_series(1, 100) x; ANALYZE rls_ptbl, rls_part;
ALTERTABLE rls_ptbl ENABLE ROW LEVEL SECURITY; ALTERTABLE rls_part ENABLE ROW LEVEL SECURITY; GRANTSELECTON rls_ptbl TO regress_rls_alice; GRANTSELECTON rls_part TO regress_rls_alice; CREATE POLICY p1 ON rls_tbl USING (a < 0); CREATE POLICY p2 ON rls_ptbl USING (a < 0); CREATE POLICY p3 ON rls_part USING (a < 0);
SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM rls_tbl WHERE a <<< 1000; SELECT * FROM rls_child_tbl WHERE a <<< 1000; SELECT * FROM rls_ptbl WHERE a <<< 1000; SELECT * FROM rls_part WHERE a <<< 1000; SELECT * FROM (SELECT * FROM rls_tbl UNIONALL SELECT * FROM rls_tbl) t WHERE a <<< 1000; SELECT * FROM (SELECT * FROM rls_child_tbl UNIONALL SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
RESET SESSION AUTHORIZATION;
REVOKESELECTON rls_tbl FROM regress_rls_alice; CREATE VIEW rls_tbl_view ASSELECT * FROM rls_tbl;
ALTERTABLE rls_child_tbl ENABLE ROW LEVEL SECURITY; GRANTSELECTON rls_child_tbl TO regress_rls_alice; CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
SET SESSION AUTHORIZATION regress_rls_alice; SELECT * FROM rls_tbl WHERE a <<< 1000; SELECT * FROM rls_tbl_view WHERE a <<< 1000; SELECT * FROM rls_child_tbl WHERE a <<< 1000; SELECT * FROM (SELECT * FROM rls_tbl UNIONALL SELECT * FROM rls_tbl) t WHERE a <<< 1000; SELECT * FROM (SELECT * FROM rls_child_tbl UNIONALL SELECT * FROM rls_child_tbl) t WHERE a <<< 1000; DROP OPERATOR <<< (int, int); DROP FUNCTION op_leak(int, int);
RESET SESSION AUTHORIZATION; DROPTABLE rls_part; DROPTABLE rls_ptbl; DROPTABLE rls_child_tbl; DROP VIEW rls_tbl_view; DROPTABLE rls_tbl;
-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects SET SESSION AUTHORIZATION regress_rls_alice; CREATETABLE rls_tbl (a int, b int, c int); CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1));
INSERTINTO rls_tbl SELECT10, 20, 30; EXPLAIN (VERBOSE, COSTS OFF) INSERTINTO rls_tbl SELECT * FROM (SELECT b, c FROM rls_tbl ORDERBY a) ss; INSERTINTO rls_tbl SELECT * FROM (SELECT b, c FROM rls_tbl ORDERBY a) ss; SELECT * FROM rls_tbl;
DROPTABLE rls_tbl;
RESET SESSION AUTHORIZATION;
-- CVE-2023-2455: inlining an SRF may introduce an RLS dependency createtable rls_t (c text); insertinto rls_t values ('invisible to bob'); altertable rls_t enable row level security; grantselecton rls_t to regress_rls_alice, regress_rls_bob; create policy p1 on rls_t forselectto regress_rls_alice using (true); create policy p2 on rls_t forselectto regress_rls_bob using (false); create function rls_f () returns setof rls_t
stable language sql as $$ select * from rls_t $$;
prepare q asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute q; set role regress_rls_bob;
execute q;
-- make sure RLS dependencies in CTEs are handled
reset role; createorreplace function rls_f() returns setof rls_t
stable language sql as $$ with cte as (select * from rls_t) select * from cte $$;
prepare r asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute r; set role regress_rls_bob;
execute r;
-- make sure RLS dependencies in subqueries are handled
reset role; createorreplace function rls_f() returns setof rls_t
stable language sql as $$ select * from (select * from rls_t) _ $$;
prepare s asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute s; set role regress_rls_bob;
execute s;
-- make sure RLS dependencies in sublinks are handled
reset role; createorreplace function rls_f() returns setof rls_t
stable language sql as $$ selectexists(select * from rls_t)::text $$;
prepare t asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute t; set role regress_rls_bob;
execute t;
-- make sure RLS dependencies are handled when coercion projections are inserted
reset role; createorreplace function rls_f() returns setof rls_t
stable language sql as $$ select * from (select array_agg(c) as cs from rls_t) _ groupby cs $$;
prepare u asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute u; set role regress_rls_bob;
execute u;
-- make sure RLS dependencies in security invoker views are handled
reset role; create view rls_v with (security_invoker) asselect * from rls_t; grantselecton rls_v to regress_rls_alice, regress_rls_bob; createorreplace function rls_f() returns setof rls_t
stable language sql as $$ select * from rls_v $$;
prepare v asselectcurrent_user, * from rls_f(); set role regress_rls_alice;
execute v; set role regress_rls_bob;
execute v;
RESET ROLE; DROP FUNCTION rls_f(); DROP VIEW rls_v; DROPTABLE rls_t;
-- Check that RLS changes invalidate SQL function plans createtable rls_t (c text); createtable test_t (c text); insertinto rls_t values ('a'), ('b'), ('c'), ('d'); insertinto test_t values ('a'), ('b'); altertable rls_t enable row level security; grantselecton rls_t to regress_rls_alice; grantselecton test_t to regress_rls_alice; create policy p1 on rls_t forselectto regress_rls_alice using (c = current_setting('rls_test.blah'));
-- Function changes row_security setting and so invalidates plan create function rls_f(text) returns text
begin atomic select set_config('rls_test.blah', $1, true) || set_config('row_security', 'false', true) || string_agg(c, ','orderby c) from rls_t;
end;
-- For other users, changes in row_security setting -- should lead to RLS error during query rewrite set role regress_rls_alice; select rls_f(c) from test_t orderby rls_f;
reset role;
set plan_cache_mode to force_generic_plan;
-- Table owner bypasses RLS, although cached plan will be invalidated select rls_f(c) from test_t orderby rls_f;
-- For other users, changes in row_security setting -- should lead to plan invalidation and RLS error during query rewrite set role regress_rls_alice; select rls_f(c) from test_t orderby rls_f;
reset role;
reset plan_cache_mode;
reset rls_test.blah; drop function rls_f(text); droptable rls_t, test_t;
-- -- Clean up objects --
RESET SESSION AUTHORIZATION;
DROPSCHEMA regress_rls_schema CASCADE;
DROP USER regress_rls_alice; DROP USER regress_rls_bob; DROP USER regress_rls_carol; DROP USER regress_rls_dave; DROP USER regress_rls_exempt_user; DROP ROLE regress_rls_group1; DROP ROLE regress_rls_group2;
-- Arrange to have a few policies left over, for testing -- pg_dump/pg_restore CREATESCHEMA regress_rls_schema; CREATETABLE rls_tbl (c1 int); ALTERTABLE rls_tbl ENABLE ROW LEVEL SECURITY; CREATE POLICY p1 ON rls_tbl USING (c1 > 5); CREATE POLICY p2 ON rls_tbl FORSELECTUSING (c1 <= 3); CREATE POLICY p3 ON rls_tbl FORUPDATEUSING (c1 <= 3) WITHCHECK (c1 > 5); CREATE POLICY p4 ON rls_tbl FORDELETEUSING (c1 <= 3);
CREATETABLE rls_tbl_force (c1 int); ALTERTABLE rls_tbl_force ENABLE ROW LEVEL SECURITY; ALTERTABLE rls_tbl_force FORCE ROW LEVEL SECURITY; CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITHCHECK (c1 < 5); CREATE POLICY p2 ON rls_tbl_force FORSELECTUSING (c1 = 8); CREATE POLICY p3 ON rls_tbl_force FORUPDATEUSING (c1 = 8) WITHCHECK (c1 >= 5); CREATE POLICY p4 ON rls_tbl_force FORDELETEUSING (c1 = 8);
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.39Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.