diff options
Diffstat (limited to '')
-rw-r--r-- | src/test/regress/expected/rowsecurity.out | 4061 |
1 files changed, 4061 insertions, 0 deletions
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out new file mode 100644 index 0000000..cff2dcc --- /dev/null +++ b/src/test/regress/expected/rowsecurity.out @@ -0,0 +1,4061 @@ +-- +-- Test of Row-level security feature +-- +-- Clean up in case a prior regression run failed +-- Suppress NOTICE messages when users/groups don't exist +SET client_min_messages TO 'warning'; +DROP USER IF EXISTS regress_rls_alice; +DROP USER IF EXISTS regress_rls_bob; +DROP USER IF EXISTS regress_rls_carol; +DROP USER IF EXISTS regress_rls_dave; +DROP USER IF EXISTS regress_rls_exempt_user; +DROP ROLE IF EXISTS regress_rls_group1; +DROP ROLE IF EXISTS regress_rls_group2; +DROP SCHEMA IF EXISTS 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; +CREATE SCHEMA regress_rls_schema; +GRANT ALL ON SCHEMA regress_rls_schema to public; +SET search_path = regress_rls_schema; +-- setup of malicious function +CREATE OR REPLACE 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; +CREATE TABLE uaccount ( + pguser name primary key, + seclv int +); +GRANT SELECT ON uaccount TO public; +INSERT INTO uaccount VALUES + ('regress_rls_alice', 99), + ('regress_rls_bob', 1), + ('regress_rls_carol', 2), + ('regress_rls_dave', 3); +CREATE TABLE category ( + cid int primary key, + cname text +); +GRANT ALL ON category TO public; +INSERT INTO category VALUES + (11, 'novel'), + (22, 'science fiction'), + (33, 'technology'), + (44, 'manga'); +CREATE TABLE document ( + did int primary key, + cid int references category(cid), + dlevel int not null, + dauthor name, + dtitle text +); +GRANT ALL ON document TO public; +INSERT INTO document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'), + ( 4, 44, 1, 'regress_rls_bob', 'my first manga'), + ( 5, 44, 2, 'regress_rls_bob', 'my second manga'), + ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 33, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 44, 1, 'regress_rls_carol', 'great manga'), + ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 33, 2, 'regress_rls_dave', 'awesome technology book'); +ALTER TABLE document ENABLE ROW LEVEL SECURITY; +-- 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)); +ERROR: unrecognized row security option "ugly" +LINE 1: CREATE POLICY p1 ON document AS UGLY + ^ +HINT: Only PERMISSIVE or RESTRICTIVE policies are supported currently. +-- 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 <> 44 AND 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 + Access privileges + Schema | Name | Type | Access privileges | Column privileges | Policies +--------------------+----------+-------+---------------------------------------------+-------------------+-------------------------------------------- + regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | + | | | =arwdDxt/regress_rls_alice | | + regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: + + | | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv + + | | | | | FROM uaccount + + | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+ + | | | | | p2r (RESTRICTIVE): + + | | | | | (u): ((cid <> 44) AND (cid < 50)) + + | | | | | to: regress_rls_dave + + | | | | | p1r (RESTRICTIVE): + + | | | | | (u): (cid <> 44) + + | | | | | to: regress_rls_dave + regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | + | | | =r/regress_rls_alice | | +(3 rows) + +\d document + Table "regress_rls_schema.document" + Column | Type | Collation | Nullable | Default +---------+---------+-----------+----------+--------- + did | integer | | not null | + cid | integer | | | + dlevel | integer | | not null | + dauthor | name | | | + dtitle | text | | | +Indexes: + "document_pkey" PRIMARY KEY, btree (did) +Foreign-key constraints: + "document_cid_fkey" FOREIGN KEY (cid) REFERENCES category(cid) +Policies: + POLICY "p1" + USING ((dlevel <= ( SELECT uaccount.seclv + FROM uaccount + WHERE (uaccount.pguser = CURRENT_USER)))) + POLICY "p1r" AS RESTRICTIVE + TO regress_rls_dave + USING ((cid <> 44)) + POLICY "p2r" AS RESTRICTIVE + TO regress_rls_dave + USING (((cid <> 44) AND (cid < 50))) + +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname; + schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check +--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------ + regress_rls_schema | document | p1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| + | | | | | | FROM uaccount +| + | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | + regress_rls_schema | document | p1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44) | + regress_rls_schema | document | p2r | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50)) | +(3 rows) + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my first manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 4 | 44 | 1 | regress_rls_bob | my first manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction +(5 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my first manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 44 | 8 | 1 | regress_rls_carol | great manga | manga + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction +(5 rows) + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 4 | 44 | 1 | regress_rls_bob | my first manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book +(10 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 44 | 5 | 2 | regress_rls_bob | my second manga | manga + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 44 | 8 | 1 | regress_rls_carol | great manga | manga + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction + 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology +(10 rows) + +-- try a sampled version +SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) + WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great manga +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------- + Seq Scan on document + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +----------------------------------------------------------- + Hash Join + Hash Cond: (category.cid = document.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on category + -> Hash + -> Seq Scan on document + Filter: ((dlevel <= $0) AND f_leak(dtitle)) +(9 rows) + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book +(7 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => awesome technology book + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-------------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction + 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology +(7 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------------------------------------------------- + Seq Scan on document + Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Hash Join + Hash Cond: (category.cid = document.cid) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on category + -> Hash + -> Seq Scan on document + Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle)) +(9 rows) + +-- 44 would technically fail for both p2r and p1r, but we should get an error +-- back from p1r for this because it sorts first +INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail +ERROR: new row violates row-level security policy "p1r" for table "document" +-- Just to see a p2r error +INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail +ERROR: new row violates row-level security policy "p2r" for table "document" +-- only owner can change policies +ALTER POLICY p1 ON document USING (true); --fail +ERROR: must be owner of table document +DROP POLICY p1 ON document; --fail +ERROR: must be owner of relation document +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) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+-------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga +(5 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my science fiction +NOTICE: f_leak => my first manga +NOTICE: f_leak => my second manga + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-----------------+--------------------+----------------- + 11 | 1 | 1 | regress_rls_bob | my first novel | novel + 11 | 2 | 2 | regress_rls_bob | my second novel | novel + 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction + 44 | 4 | 1 | regress_rls_bob | my first manga | manga + 44 | 5 | 2 | regress_rls_bob | my second manga | manga +(5 rows) + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------- + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga +(3 rows) + +SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great technology book +NOTICE: f_leak => great manga + cid | did | dlevel | dauthor | dtitle | cname +-----+-----+--------+-------------------+-----------------------+----------------- + 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction + 33 | 7 | 2 | regress_rls_carol | great technology book | technology + 44 | 8 | 1 | regress_rls_carol | great manga | manga +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------------- + Nested Loop + -> Seq Scan on document + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Index Scan using category_pkey on category + Index Cond: (cid = document.cid) +(5 rows) + +-- interaction of FK/PK constraints +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY p2 ON category + USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33) + WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44) + ELSE false END); +ALTER TABLE category ENABLE ROW LEVEL SECURITY; +-- cannot delete PK referenced by invisible FK +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; + did | cid | dlevel | dauthor | dtitle | cid | cname +-----+-----+--------+-----------------+--------------------+-----+------------ + 1 | 11 | 1 | regress_rls_bob | my first novel | 11 | novel + 2 | 11 | 2 | regress_rls_bob | my second novel | 11 | novel + 3 | 22 | 2 | regress_rls_bob | my science fiction | | + 4 | 44 | 1 | regress_rls_bob | my first manga | | + 5 | 44 | 2 | regress_rls_bob | my second manga | | + | | | | | 33 | technology +(6 rows) + +DELETE FROM category WHERE cid = 33; -- fails with FK violation +ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document" +DETAIL: Key is still referenced from table "document". +-- can insert FK referencing invisible PK +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; + did | cid | dlevel | dauthor | dtitle | cid | cname +-----+-----+--------+-------------------+-----------------------+-----+----------------- + 6 | 22 | 1 | regress_rls_carol | great science fiction | 22 | science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book | | + 8 | 44 | 1 | regress_rls_carol | great manga | 44 | manga +(3 rows) + +INSERT INTO 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; +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see +ERROR: duplicate key value violates unique constraint "document_pkey" +SELECT * FROM document WHERE did = 8; -- and confirm we can't see it + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+---------+-------- +(0 rows) + +-- RLS policies are checked before constraints +INSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation +ERROR: new row violates row-level security policy for table "document" +UPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation +ERROR: new row violates row-level security policy for table "document" +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- database superuser does bypass RLS policy when disabled +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- 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; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM document; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- 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; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 22 | 2 | regress_rls_bob | my science fiction + 4 | 44 | 1 | regress_rls_bob | my first manga + 5 | 44 | 2 | regress_rls_bob | my second manga + 6 | 22 | 1 | regress_rls_carol | great science fiction + 7 | 33 | 2 | regress_rls_carol | great technology book + 8 | 44 | 1 | regress_rls_carol | great manga + 9 | 22 | 1 | regress_rls_dave | awesome science fiction + 10 | 33 | 2 | regress_rls_dave | awesome technology book + 11 | 33 | 1 | regress_rls_carol | hoge +(11 rows) + +SELECT * FROM category; + cid | cname +-----+----------------- + 11 | novel + 22 | science fiction + 33 | technology + 44 | manga +(4 rows) + +-- +-- Table inheritance and RLS policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE TABLE t1 (id int not null primary key, a int, junk1 text, b text); +ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor +GRANT ALL ON t1 TO public; +COPY t1 FROM stdin WITH ; +CREATE TABLE t2 (c float) INHERITS (t1); +GRANT ALL ON t2 TO public; +COPY t2 FROM stdin; +CREATE TABLE t3 (id int not null primary key, c text, b text, a int); +ALTER TABLE t3 INHERIT t1; +GRANT ALL ON t3 TO public; +COPY t3(id, a,b,c) FROM stdin; +CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number +CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE t2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM t1; + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Filter: ((a % 2) = 0) +(7 rows) + +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) + +-- reference to system column +SELECT tableoid::regclass, * FROM t1; + tableoid | id | a | b +----------+-----+---+----- + t1 | 102 | 2 | bbb + t1 | 104 | 4 | dad + t2 | 202 | 2 | bcd + t2 | 204 | 4 | def + t3 | 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Filter: ((a % 2) = 0) +(7 rows) + +-- reference to whole-row reference +SELECT *, t1 FROM t1; + id | a | b | t1 +-----+---+-----+------------- + 102 | 2 | bbb | (102,2,bbb) + 104 | 4 | dad | (104,4,dad) + 202 | 2 | bcd | (202,2,bcd) + 204 | 4 | def | (204,4,def) + 302 | 2 | yyy | (302,2,yyy) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Filter: ((a % 2) = 0) +(7 rows) + +-- for share/update lock +SELECT * FROM t1 FOR SHARE; + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE; + QUERY PLAN +------------------------------------- + LockRows + -> Append + -> Seq Scan on t1 t1_1 + Filter: ((a % 2) = 0) + -> Seq Scan on t2 t1_2 + Filter: ((a % 2) = 0) + -> Seq Scan on t3 t1_3 + Filter: ((a % 2) = 0) +(8 rows) + +SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy + id | a | b +-----+---+----- + 102 | 2 | bbb + 104 | 4 | dad + 202 | 2 | bcd + 204 | 4 | def + 302 | 2 | yyy +(5 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; + QUERY PLAN +----------------------------------------------------- + LockRows + -> Append + -> Seq Scan on t1 t1_1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_3 + Filter: (((a % 2) = 0) AND f_leak(b)) +(8 rows) + +-- union all query +SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; + a | b | tableoid +---+-----+---------- + 1 | abc | t2 + 3 | cde | t2 + 1 | xxx | t3 + 2 | yyy | t3 + 3 | zzz | t3 +(5 rows) + +EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; + QUERY PLAN +------------------------------- + Append + -> Seq Scan on t2 + Filter: ((a % 2) = 1) + -> Seq Scan on t3 +(4 rows) + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- 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); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- +-- Partitioned Tables +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE part_document ( + did int, + cid int, + dlevel int not null, + dauthor name, + dtitle text +) PARTITION BY RANGE (cid); +GRANT ALL ON part_document TO public; +-- Create partitions for document categories +CREATE TABLE part_document_fiction PARTITION OF part_document FOR VALUES FROM (11) to (12); +CREATE TABLE part_document_satire PARTITION OF part_document FOR VALUES FROM (55) to (56); +CREATE TABLE part_document_nonfiction PARTITION OF part_document FOR VALUES FROM (99) to (100); +GRANT ALL ON part_document_fiction TO public; +GRANT ALL ON part_document_satire TO public; +GRANT ALL ON part_document_nonfiction TO public; +INSERT INTO part_document VALUES + ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), + ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), + ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'), + ( 4, 55, 1, 'regress_rls_bob', 'my first satire'), + ( 5, 99, 2, 'regress_rls_bob', 'my history book'), + ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'), + ( 7, 99, 2, 'regress_rls_carol', 'great technology book'), + ( 8, 55, 2, 'regress_rls_carol', 'great satire'), + ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'), + (10, 99, 2, 'regress_rls_dave', 'awesome technology book'); +ALTER TABLE part_document ENABLE ROW LEVEL SECURITY; +-- 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 + Partitioned table "regress_rls_schema.part_document" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +---------+---------+-----------+----------+---------+----------+--------------+------------- + did | integer | | | | plain | | + cid | integer | | | | plain | | + dlevel | integer | | not null | | plain | | + dauthor | name | | | | plain | | + dtitle | text | | | | extended | | +Partition key: RANGE (cid) +Policies: + POLICY "pp1" + USING ((dlevel <= ( SELECT uaccount.seclv + FROM uaccount + WHERE (uaccount.pguser = CURRENT_USER)))) + POLICY "pp1r" AS RESTRICTIVE + TO regress_rls_dave + USING ((cid < 55)) +Partitions: part_document_fiction FOR VALUES FROM (11) TO (12), + part_document_nonfiction FOR VALUES FROM (99) TO (100), + part_document_satire FOR VALUES FROM (55) TO (56) + +SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname; + schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check +--------------------+---------------+------------+-------------+--------------------+-----+--------------------------------------------+------------ + regress_rls_schema | part_document | pp1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| + | | | | | | FROM uaccount +| + | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | + regress_rls_schema | part_document | pp1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55) | +(2 rows) + +-- viewpoint from regress_rls_bob +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 4 | 55 | 1 | regress_rls_bob | my first satire + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_satire part_document_2 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_nonfiction part_document_3 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) +(10 rows) + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book +(10 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_satire part_document_2 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_nonfiction part_document_3 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) +(10 rows) + +-- viewpoint from regress_rls_dave +SET SESSION AUTHORIZATION regress_rls_dave; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on part_document_fiction part_document + Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) + +-- pp1 ERROR +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail +ERROR: new row violates row-level security policy for table "part_document" +-- pp1r ERROR +INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail +ERROR: new row violates row-level security policy "pp1r" for table "part_document" +-- Show that RLS policy does not apply for direct inserts to children +-- This should fail with RLS POLICY pp1r violation. +INSERT INTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +ERROR: new row violates row-level security policy "pp1r" for table "part_document" +-- But this should succeed. +INSERT INTO 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) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +-- But we can if we look directly +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => testing RLS with partitions + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- Turn on RLS and create policy on child to show RLS is checked before constraints +SET SESSION AUTHORIZATION regress_rls_alice; +ALTER TABLE 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; +INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail +ERROR: new row violates row-level security policy for table "part_document_satire" +-- And now we cannot see directly into the partition either, due to RLS +SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+---------+-------- +(0 rows) + +-- The parent looks same as before +-- viewpoint from regress_rls_dave +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 6 | 11 | 1 | regress_rls_carol | great science fiction + 9 | 11 | 1 | regress_rls_dave | awesome science fiction +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +-------------------------------------------------------------- + Seq Scan on part_document_fiction part_document + Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle)) + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) +(5 rows) + +-- viewpoint from regress_rls_carol +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => great science fiction +NOTICE: f_leak => awesome science fiction +NOTICE: f_leak => my first satire +NOTICE: f_leak => great satire +NOTICE: f_leak => testing RLS with partitions +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book +NOTICE: f_leak => great technology book +NOTICE: f_leak => awesome technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +------------------------------------------------------------ + Append + InitPlan 1 (returns $0) + -> Index Scan using uaccount_pkey on uaccount + Index Cond: (pguser = CURRENT_USER) + -> Seq Scan on part_document_fiction part_document_1 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_satire part_document_2 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) + -> Seq Scan on part_document_nonfiction part_document_3 + Filter: ((dlevel <= $0) AND f_leak(dtitle)) +(10 rows) + +-- only owner can change policies +ALTER POLICY pp1 ON part_document USING (true); --fail +ERROR: must be owner of table part_document +DROP POLICY pp1 ON part_document; --fail +ERROR: must be owner of relation part_document +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) ORDER BY did; +NOTICE: f_leak => my first novel +NOTICE: f_leak => my second novel +NOTICE: f_leak => my first satire +NOTICE: f_leak => my science textbook +NOTICE: f_leak => my history book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+--------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book +(5 rows) + +-- viewpoint from rls_regres_carol again +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; +NOTICE: f_leak => great science fiction +NOTICE: f_leak => great satire +NOTICE: f_leak => great technology book + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------- + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); + QUERY PLAN +--------------------------------------------------------------- + Append + -> Seq Scan on part_document_fiction part_document_1 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Seq Scan on part_document_satire part_document_2 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) + -> Seq Scan on part_document_nonfiction part_document_3 + Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) +(7 rows) + +-- database superuser does bypass RLS policy when enabled +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +SELECT * FROM part_document ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- 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 ORDER BY did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- RLS policy does not apply to table owner when RLS enabled. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +SELECT * FROM part_document ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 1 | 11 | 1 | regress_rls_bob | my first novel + 2 | 11 | 2 | regress_rls_bob | my second novel + 3 | 99 | 2 | regress_rls_bob | my science textbook + 4 | 55 | 1 | regress_rls_bob | my first satire + 5 | 99 | 2 | regress_rls_bob | my history book + 6 | 11 | 1 | regress_rls_carol | great science fiction + 7 | 99 | 2 | regress_rls_carol | great technology book + 8 | 55 | 2 | regress_rls_carol | great satire + 9 | 11 | 1 | regress_rls_dave | awesome science fiction + 10 | 99 | 2 | regress_rls_dave | awesome technology book + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(11 rows) + +SELECT * FROM part_document_satire ORDER by did; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-------------------+----------------------------- + 4 | 55 | 1 | regress_rls_bob | my first satire + 8 | 55 | 2 | regress_rls_carol | great satire + 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions +(3 rows) + +-- When RLS disabled, other users get ERROR. +SET SESSION AUTHORIZATION regress_rls_dave; +SET row_security TO OFF; +SELECT * FROM part_document ORDER by did; +ERROR: query would be affected by row-level security policy for table "part_document" +SELECT * FROM part_document_satire ORDER by did; +ERROR: query would be affected by row-level security policy for table "part_document_satire" +-- Check behavior with a policy that uses a SubPlan not an InitPlan. +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE POLICY pp3 ON part_document AS RESTRICTIVE + USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user)); +SET SESSION AUTHORIZATION regress_rls_carol; +INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail +ERROR: new row violates row-level security policy "pp3" for table "part_document" +----- Dependencies ----- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security TO ON; +CREATE TABLE dependee (x integer, y integer); +CREATE TABLE dependent (x integer, y integer); +CREATE POLICY d1 ON dependent FOR ALL + TO PUBLIC + USING (x = (SELECT d.x FROM dependee d WHERE d.y = y)); +DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual? +ERROR: cannot drop table dependee because other objects depend on it +DETAIL: policy d1 on table dependent depends on table dependee +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE dependee CASCADE; +NOTICE: drop cascades to policy d1 on table dependent +EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified + QUERY PLAN +----------------------- + Seq Scan on dependent +(1 row) + +----- RECURSION ---- +-- +-- Simple recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rec1 (x integer, y integer); +CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y)); +ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, direct recursion +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE 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)); +ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM rec1; -- fail, mutual recursion +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion via views +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rec1v AS SELECT * FROM rec1; +CREATE VIEW rec2v AS SELECT * 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 +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- Mutual recursion via .s.b views +-- +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rec1v, rec2v CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to policy r1 on table rec1 +drop cascades to policy r2 on table rec2 +CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1; +CREATE VIEW rec2v WITH (security_barrier) AS SELECT * 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 +ERROR: infinite recursion detected in policy for relation "rec1" +-- +-- recursive RLS and VIEWs in policy +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE s1 (a int, b text); +INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); +CREATE TABLE s2 (x int, y text); +INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x); +GRANT SELECT ON 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 FOR INSERT WITH CHECK (a = (SELECT a FROM s1)); +ALTER TABLE s1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE s2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%'; +SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion) +ERROR: infinite recursion detected in policy for relation "s1" +INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion) +ERROR: infinite recursion detected in policy for relation "s1" +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 +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c + a | b +---+---------------------------------- + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text)) +(5 rows) + +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 +NOTICE: f_leak => 0267aaf632e87a63288a08331f22c7c3 +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc + a | b +----+---------------------------------- + -4 | 0267aaf632e87a63288a08331f22c7c3 + 6 | 1679091c5a880faf6fb5e6087eb1b2dc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------------------- + Seq Scan on s1 + Filter: ((hashed SubPlan 1) AND f_leak(b)) + SubPlan 1 + -> Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(5 rows) + +SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; + xx | x | y +----+----+---------------------------------- + -6 | -6 | 596a3d04481816330f07e4f97510c28f + -4 | -4 | 0267aaf632e87a63288a08331f22c7c3 + 2 | 2 | c81e728d9d4c2f636f067f89cc14862c +(3 rows) + +EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; + QUERY PLAN +------------------------------------------------------------------------- + Seq Scan on s2 + Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text)) + SubPlan 2 + -> Limit + -> Seq Scan on s1 + Filter: (hashed SubPlan 1) + SubPlan 1 + -> Seq Scan on s2 s2_1 + Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) +(9 rows) + +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) +ERROR: infinite recursion detected in policy for relation "s1" +-- prepared statement with regress_rls_alice privilege +PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1; +EXECUTE p1(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p1(2); + QUERY PLAN +---------------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: ((a <= 2) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2 + Filter: ((a <= 2) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_3 + Filter: ((a <= 2) AND ((a % 2) = 0)) +(7 rows) + +-- superuser is allowed to bypass RLS checks +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => cde +NOTICE: f_leak => def +NOTICE: f_leak => xxx +NOTICE: f_leak => yyy +NOTICE: f_leak => zzz + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 103 | 3 | ccc + 104 | 4 | dad + 201 | 1 | abc + 202 | 2 | bcd + 203 | 3 | cde + 204 | 4 | def + 301 | 1 | xxx + 302 | 2 | yyy + 303 | 3 | zzz +(11 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: f_leak(b) + -> Seq Scan on t2 t1_2 + Filter: f_leak(b) + -> Seq Scan on t3 t1_3 + Filter: f_leak(b) +(7 rows) + +-- plan cache should be invalidated +EXECUTE p1(2); + id | a | b +-----+---+----- + 101 | 1 | aba + 102 | 2 | bbb + 201 | 1 | abc + 202 | 2 | bcd + 301 | 1 | xxx + 302 | 2 | yyy +(6 rows) + +EXPLAIN (COSTS OFF) EXECUTE p1(2); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: (a <= 2) + -> Seq Scan on t2 t1_2 + Filter: (a <= 2) + -> Seq Scan on t3 t1_3 + Filter: (a <= 2) +(7 rows) + +PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1; +EXECUTE p2(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p2(2); + QUERY PLAN +--------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: (a = 2) + -> Seq Scan on t2 t1_2 + Filter: (a = 2) + -> Seq Scan on t3 t1_3 + Filter: (a = 2) +(7 rows) + +-- also, case when privilege switch from superuser +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXECUTE p2(2); + id | a | b +-----+---+----- + 102 | 2 | bbb + 202 | 2 | bcd + 302 | 2 | yyy +(3 rows) + +EXPLAIN (COSTS OFF) EXECUTE p2(2); + QUERY PLAN +--------------------------------------------- + Append + -> Seq Scan on t1 t1_1 + Filter: ((a = 2) AND ((a % 2) = 0)) + -> Seq Scan on t2 t1_2 + Filter: ((a = 2) AND ((a % 2) = 0)) + -> Seq Scan on t3 t1_3 + Filter: ((a = 2) AND ((a % 2) = 0)) +(7 rows) + +-- +-- 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); + QUERY PLAN +----------------------------------------------- + Update on t1 + Update on t1 + Update on t2 t1_1 + Update on t3 t1_2 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2 + Filter: (((a % 2) = 0) AND f_leak(b)) +(10 rows) + +UPDATE t1 SET b = b || b WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => yyy +EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); + QUERY PLAN +----------------------------------------------- + Update on t1 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(3 rows) + +UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); +NOTICE: f_leak => bbbbbb +NOTICE: f_leak => daddad +-- returning clause with system column +UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt + tableoid | id | a | b | t1 +----------+-----+---+-------------+--------------------- + t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) + t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) +(2 rows) + +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => bcdbcd +NOTICE: f_leak => defdef +NOTICE: f_leak => yyyyyy + id | a | b +-----+---+------------- + 102 | 2 | bbbbbb_updt + 104 | 4 | daddad_updt + 202 | 2 | bcdbcd + 204 | 4 | defdef + 302 | 2 | yyyyyy +(5 rows) + +UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => bcdbcd +NOTICE: f_leak => defdef +NOTICE: f_leak => yyyyyy + tableoid | id | a | b | t1 +----------+-----+---+-------------+--------------------- + t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) + t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) + t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd) + t2 | 204 | 4 | defdef | (204,4,defdef) + t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy) +(5 rows) + +-- updates with from clause +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); + QUERY PLAN +----------------------------------------------------------------- + Update on t2 + -> Nested Loop + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Seq Scan on t3 + Filter: ((a = 2) AND f_leak(b)) +(6 rows) + +UPDATE t2 SET b=t2.b FROM t3 +WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); +NOTICE: f_leak => cde +NOTICE: f_leak => yyyyyy +EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + QUERY PLAN +----------------------------------------------------------------- + Update on t1 + Update on t1 + Update on t2 t1_1 + Update on t3 t1_2 + -> Nested Loop + -> Seq Scan on t1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Nested Loop + -> Seq Scan on t2 t1_1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Nested Loop + -> Seq Scan on t3 t1_2 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) +(19 rows) + +UPDATE t1 SET b=t1.b FROM t2 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); +EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); + QUERY PLAN +----------------------------------------------------------------------- + Update on t2 + -> Nested Loop + -> Seq Scan on t2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_1 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_3 + Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) +(11 rows) + +UPDATE t2 SET b=t2.b FROM t1 +WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); +NOTICE: f_leak => cde +-- 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 = 3 AND 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; + QUERY PLAN +----------------------------------------------------------------- + Update on t2 t2_1 + -> Nested Loop + Join Filter: (t2_1.b = t2_2.b) + -> Seq Scan on t2 t2_1 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) + -> Seq Scan on t2 t2_2 + Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) +(7 rows) + +UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 +WHERE t2_1.a = 3 AND 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; +NOTICE: f_leak => cde +NOTICE: f_leak => cde + id | a | b | c | id | a | b | c | t2_1 | t2_2 +-----+---+-----+-----+-----+---+-----+-----+-----------------+----------------- + 203 | 3 | cde | 3.3 | 203 | 3 | cde | 3.3 | (203,3,cde,3.3) | (203,3,cde,3.3) +(1 row) + +EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND 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; + QUERY PLAN +----------------------------------------------------------------------- + Update on t1 t1_1 + Update on t1 t1_1 + Update on t2 t1_1_1 + Update on t3 t1_1_2 + -> Nested Loop + Join Filter: (t1_1.b = t1_2.b) + -> Seq Scan on t1 t1_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_3 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Nested Loop + Join Filter: (t1_1_1.b = t1_2.b) + -> Seq Scan on t2 t1_1_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_3 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Nested Loop + Join Filter: (t1_1_2.b = t1_2.b) + -> Seq Scan on t3 t1_1_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Append + -> Seq Scan on t1 t1_2_1 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_2_2 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2_3 + Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) +(37 rows) + +UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 +WHERE t1_1.a = 4 AND 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; +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => defdef +NOTICE: f_leak => defdef +NOTICE: f_leak => daddad_updt +NOTICE: f_leak => defdef + id | a | b | id | a | b | t1_1 | t1_2 +-----+---+-------------+-----+---+-------------+---------------------+--------------------- + 104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt) + 204 | 4 | defdef | 204 | 4 | defdef | (204,4,defdef) | (204,4,defdef) +(2 rows) + +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +SELECT * FROM t1 ORDER BY a,b; + id | a | b +-----+---+------------- + 101 | 1 | aba + 201 | 1 | abc + 301 | 1 | xxx + 102 | 2 | bbbbbb_updt + 202 | 2 | bcdbcd + 302 | 2 | yyyyyy + 103 | 3 | ccc + 203 | 3 | cde + 303 | 3 | zzz + 104 | 4 | daddad_updt + 204 | 4 | defdef +(11 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------- + Delete on t1 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(3 rows) + +EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------- + Delete on t1 + Delete on t1 + Delete on t2 t1_1 + Delete on t3 t1_2 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t2 t1_1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> Seq Scan on t3 t1_2 + Filter: (((a % 2) = 0) AND f_leak(b)) +(10 rows) + +DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bbbbbb_updt +NOTICE: f_leak => daddad_updt + tableoid | id | a | b | t1 +----------+-----+---+-------------+--------------------- + t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) + t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) +(2 rows) + +DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; +NOTICE: f_leak => bcdbcd +NOTICE: f_leak => defdef +NOTICE: f_leak => yyyyyy + tableoid | id | a | b | t1 +----------+-----+---+--------+---------------- + t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd) + t2 | 204 | 4 | defdef | (204,4,defdef) + t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy) +(3 rows) + +-- +-- S.b. view on top of Row-level security +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE b1 (a int, b text); +INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x); +CREATE POLICY p1 ON b1 USING (a % 2 = 0); +ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; +GRANT ALL ON b1 TO regress_rls_bob; +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; +GRANT ALL ON bv1 TO regress_rls_carol; +SET SESSION AUTHORIZATION regress_rls_carol; +EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b); + QUERY PLAN +--------------------------------------------- + Subquery Scan on bv1 + Filter: f_leak(bv1.b) + -> Seq Scan on b1 + Filter: ((a > 0) AND ((a % 2) = 0)) +(4 rows) + +SELECT * FROM bv1 WHERE f_leak(b); +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 + a | b +----+---------------------------------- + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 +(5 rows) + +INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO +ERROR: new row violates row-level security policy for table "b1" +INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check +ERROR: new row violates row-level security policy for table "b1" +INSERT INTO bv1 VALUES (12, 'xxx'); -- ok +EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); + QUERY PLAN +----------------------------------------------------------------------- + Update on b1 + -> Seq Scan on b1 + Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0) AND f_leak(b)) +(3 rows) + +UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b); + QUERY PLAN +----------------------------------------------------------------------- + Delete on b1 + -> Seq Scan on b1 + Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0) AND f_leak(b)) +(3 rows) + +DELETE FROM bv1 WHERE a = 6 AND f_leak(b); +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM b1; + a | b +-----+---------------------------------- + -10 | 1b0fd9efa5279c4203b7c70233f86dbf + -9 | 252e691406782824eec43d7eadc3d256 + -8 | a8d2ec85eaf98407310b72eb73dda247 + -7 | 74687a12d3915d3c4d83f1af7b3683d5 + -6 | 596a3d04481816330f07e4f97510c28f + -5 | 47c1b025fa18ea96c33fbb6718688c0f + -4 | 0267aaf632e87a63288a08331f22c7c3 + -3 | b3149ecea4628efd23d2f86e5a723472 + -2 | 5d7b9adcbe1c629ec722529dd12e5129 + -1 | 6bb61e3b7bce0931da574d19d1d82c88 + 0 | cfcd208495d565ef66e7dff9f98764da + 1 | c4ca4238a0b923820dcc509a6f75849b + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 + 12 | xxx + 4 | yyy +(21 rows) + +-- +-- 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 FOR SELECT USING (true); +CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); +CREATE POLICY p3 ON document FOR UPDATE + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Exists... +SELECT * FROM document WHERE did = 2; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------- + 2 | 11 | 2 | regress_rls_bob | my second novel +(1 row) + +-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since +-- alternative UPDATE path happens to be taken): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor; +ERROR: new row violates row-level security policy for table "document" +-- 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): +INSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement +INSERT INTO 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 UPDATE SET dtitle = EXCLUDED.dtitle; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs +-- not violated): +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+---------------- + 2 | 11 | 2 | regress_rls_bob | my first novel +(1 row) + +-- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------------- + 78 | 11 | 1 | regress_rls_bob | some technology novel +(1 row) + +-- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the +-- case in respect of *existing* tuple): +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+----------------------- + 78 | 33 | 1 | regress_rls_bob | some technology novel +(1 row) + +-- Same query a third time, but now fails due to existing tuple finally not +-- passing quals: +INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- 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: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; + did | cid | dlevel | dauthor | dtitle +-----+-----+--------+-----------------+---------------------------------- + 79 | 33 | 1 | regress_rls_bob | technology book, can only insert +(1 row) + +-- 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. +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- 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 FOR UPDATE + 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: +INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy for table "document" +-- 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: +INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *; +ERROR: new row violates row-level security policy for table "document" +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 FOR ALL + USING (cid = (SELECT cid from category WHERE cname = 'novel')) + WITH CHECK (dauthor = current_user); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Fails, since ALL WCO is enforced in insert path: +INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33; +ERROR: new row violates row-level security policy for table "document" +-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in +-- violation, since it has the "manga" cid): +INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; +ERROR: new row violates row-level security policy (USING expression) for table "document" +-- Fails, since ALL WCO are enforced: +INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') + ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol'; +ERROR: new row violates row-level security policy for table "document" +-- +-- ROLE/GROUP +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE z1 (a int, b text); +CREATE TABLE z2 (a int, b text); +GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2, + regress_rls_bob, regress_rls_carol; +INSERT INTO z1 VALUES + (1, 'aba'), + (2, 'bbb'), + (3, 'ccc'), + (4, 'dad'); +CREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0); +CREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1); +ALTER TABLE z1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2; +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(7 rows) + +PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b); +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +----------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) + +SET ROLE regress_rls_group1; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +----------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(7 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +----------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(7 rows) + +SET ROLE regress_rls_group2; +SELECT * FROM z1 WHERE f_leak(b); +NOTICE: f_leak => aba +NOTICE: f_leak => ccc + a | b +---+----- + 1 | aba + 3 | ccc +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(2 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test2; + QUERY PLAN +------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) + -> CTE Scan on q + -> Materialize + -> Seq Scan on z2 +(7 rows) + +EXPLAIN (COSTS OFF) EXECUTE plancache_test3; + QUERY PLAN +----------------------------------------------------- + Nested Loop + CTE q + -> Seq Scan on z2 + -> CTE Scan on q + -> Materialize + -> Seq Scan on z1 + Filter: (((a % 2) = 1) AND f_leak(b)) +(7 rows) + +-- +-- Views should follow policy for view owner. +-- +-- View and Table owner are the same. +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON 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; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +-- Query as view/table owner. Should return all records. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM rls_view; +NOTICE: f_leak => aba +NOTICE: f_leak => bbb +NOTICE: f_leak => ccc +NOTICE: f_leak => dad + a | b +---+----- + 1 | aba + 2 | bbb + 3 | ccc + 4 | dad +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +--------------------- + Seq Scan on z1 + Filter: f_leak(b) +(2 rows) + +DROP VIEW rls_view; +-- View and Table owners are different. +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); +GRANT SELECT ON 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; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +-- 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; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +-- 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. +ERROR: permission denied for view rls_view +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. +ERROR: permission denied for view rls_view +-- Query as role that is not the owner of the table or view with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_carol; +SELECT * FROM rls_view; +NOTICE: f_leak => bbb +NOTICE: f_leak => dad + a | b +---+----- + 2 | bbb + 4 | dad +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM rls_view; + QUERY PLAN +----------------------------------------- + Seq Scan on z1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +DROP VIEW rls_view; +-- +-- Command specific +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE x1 (a int, b text, c text); +GRANT ALL ON x1 TO PUBLIC; +INSERT INTO x1 VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_carol'), + (4, 'def', 'regress_rls_carol'), + (5, 'efg', 'regress_rls_bob'), + (6, 'fgh', 'regress_rls_bob'), + (7, 'fgh', 'regress_rls_carol'), + (8, 'fgh', 'regress_rls_carol'); +CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user); +CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0); +CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1); +CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0); +CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8); +ALTER TABLE x1 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => efg +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh + a | b | c +---+-----+------------------- + 1 | abc | regress_rls_bob + 2 | bcd | regress_rls_bob + 4 | def | regress_rls_carol + 5 | efg | regress_rls_bob + 6 | fgh | regress_rls_bob + 8 | fgh | regress_rls_carol +(6 rows) + +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => abc +NOTICE: f_leak => bcd +NOTICE: f_leak => def +NOTICE: f_leak => efg +NOTICE: f_leak => fgh +NOTICE: f_leak => fgh + a | b | c +---+----------+------------------- + 1 | abc_updt | regress_rls_bob + 2 | bcd_updt | regress_rls_bob + 4 | def_updt | regress_rls_carol + 5 | efg_updt | regress_rls_bob + 6 | fgh_updt | regress_rls_bob + 8 | fgh_updt | regress_rls_carol +(6 rows) + +SET SESSION AUTHORIZATION regress_rls_carol; +SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; +NOTICE: f_leak => cde +NOTICE: f_leak => fgh +NOTICE: f_leak => bcd_updt +NOTICE: f_leak => def_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => fgh_updt + a | b | c +---+----------+------------------- + 2 | bcd_updt | regress_rls_bob + 3 | cde | regress_rls_carol + 4 | def_updt | regress_rls_carol + 6 | fgh_updt | regress_rls_bob + 7 | fgh | regress_rls_carol + 8 | fgh_updt | regress_rls_carol +(6 rows) + +UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => cde +NOTICE: f_leak => fgh +NOTICE: f_leak => bcd_updt +NOTICE: f_leak => def_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => fgh_updt + a | b | c +---+---------------+------------------- + 3 | cde_updt | regress_rls_carol + 7 | fgh_updt | regress_rls_carol + 2 | bcd_updt_updt | regress_rls_bob + 4 | def_updt_updt | regress_rls_carol + 6 | fgh_updt_updt | regress_rls_bob + 8 | fgh_updt_updt | regress_rls_carol +(6 rows) + +DELETE FROM x1 WHERE f_leak(b) RETURNING *; +NOTICE: f_leak => cde_updt +NOTICE: f_leak => fgh_updt +NOTICE: f_leak => bcd_updt_updt +NOTICE: f_leak => def_updt_updt +NOTICE: f_leak => fgh_updt_updt +NOTICE: f_leak => fgh_updt_updt + a | b | c +---+---------------+------------------- + 3 | cde_updt | regress_rls_carol + 7 | fgh_updt | regress_rls_carol + 2 | bcd_updt_updt | regress_rls_bob + 4 | def_updt_updt | regress_rls_carol + 6 | fgh_updt_updt | regress_rls_bob + 8 | fgh_updt_updt | regress_rls_carol +(6 rows) + +-- +-- Duplicate Policy Names +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE y1 (a int, b text); +CREATE TABLE y2 (a int, b text); +GRANT ALL ON y1, y2 TO regress_rls_bob; +CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0); +CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2); +CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1); --fail +ERROR: policy "p1" for table "y1" already exists +CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0); --OK +ALTER TABLE y1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE y2 ENABLE ROW LEVEL SECURITY; +-- +-- 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); + QUERY PLAN +----------------------------------- + Seq Scan on y1 + Filter: (f_leak(b) AND (a = 1)) +(2 rows) + +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); + QUERY PLAN +------------------------------------------------------------------ + Seq Scan on y1 + Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b)) +(2 rows) + +DROP VIEW rls_sbv; +-- +-- Expression structure +-- +SET SESSION AUTHORIZATION regress_rls_alice; +INSERT INTO y2 (SELECT x, md5(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); +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3 +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26 +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(14 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b); + QUERY PLAN +----------------------------------------------------------------------------- + Seq Scan on y2 + Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) +(2 rows) + +-- +-- Qual push-down of leaky functions, when not referring to table +-- +SELECT * FROM y2 WHERE f_leak('abc'); +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc +NOTICE: f_leak => abc + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(14 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc'); + QUERY PLAN +--------------------------------------------------------------------------------------- + Seq Scan on y2 + Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))) +(2 rows) + +CREATE TABLE test_qual_pushdown ( + abc text +); +INSERT INTO test_qual_pushdown VALUES ('abc'),('def'); +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); +NOTICE: f_leak => abc +NOTICE: f_leak => def + a | b | abc +---+---+----- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); + QUERY PLAN +------------------------------------------------------------------------- + Hash Join + Hash Cond: (test_qual_pushdown.abc = y2.b) + -> Seq Scan on test_qual_pushdown + Filter: f_leak(abc) + -> Hash + -> Seq Scan on y2 + Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) +(7 rows) + +SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3 +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26 +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b | abc +---+---+----- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); + QUERY PLAN +----------------------------------------------------------------------------------------- + Hash Join + Hash Cond: (test_qual_pushdown.abc = y2.b) + -> Seq Scan on test_qual_pushdown + -> Hash + -> Seq Scan on y2 + Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) +(6 rows) + +DROP TABLE test_qual_pushdown; +-- +-- Plancache invalidate on user change. +-- +RESET SESSION AUTHORIZATION; +DROP TABLE t1 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table t2 +drop cascades to table t3 +CREATE TABLE t1 (a integer); +GRANT SELECT ON 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); +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +-- Prepare as regress_rls_bob +SET ROLE regress_rls_bob; +PREPARE role_inval AS SELECT * FROM t1; +-- Check plan +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +------------------------- + Seq Scan on t1 + Filter: ((a % 2) = 0) +(2 rows) + +-- Change to regress_rls_carol +SET ROLE regress_rls_carol; +-- Check plan- should be different +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +------------------------- + Seq Scan on t1 + Filter: ((a % 4) = 0) +(2 rows) + +-- Change back to regress_rls_bob +SET ROLE regress_rls_bob; +-- Check plan- should be back to original +EXPLAIN (COSTS OFF) EXECUTE role_inval; + QUERY PLAN +------------------------- + Seq Scan on t1 + Filter: ((a % 2) = 0) +(2 rows) + +-- +-- CTE and RLS +-- +RESET SESSION AUTHORIZATION; +DROP TABLE t1 CASCADE; +CREATE TABLE t1 (a integer, b text); +CREATE POLICY p1 ON t1 USING (a % 2 = 0); +ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; +GRANT ALL ON t1 TO regress_rls_bob; +INSERT INTO t1 (SELECT x, md5(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; +NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da +NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c +NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c +NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc +NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d +NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 +NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710 +NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56 +NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf +NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23 +NOTICE: f_leak => 98f13708210194c475687be6106a3b84 + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(11 rows) + +EXPLAIN (COSTS OFF) +WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; + QUERY PLAN +------------------------------------------------- + CTE Scan on cte1 + CTE cte1 + -> Seq Scan on t1 + Filter: (((a % 2) = 0) AND f_leak(b)) +(4 rows) + +WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail +ERROR: new row violates row-level security policy for table "t1" +WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 +(11 rows) + +WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail +ERROR: new row violates row-level security policy for table "t1" +WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok + a | b +----+--------- + 20 | Success +(1 row) + +-- +-- Rename Policy +-- +RESET SESSION AUTHORIZATION; +ALTER POLICY p1 ON t1 RENAME TO p1; --fail +ERROR: policy "p1" for table "t1" already exists +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + polname | relname +---------+--------- + p1 | t1 +(1 row) + +ALTER POLICY p1 ON t1 RENAME TO p2; --ok +SELECT polname, relname + FROM pg_policy pol + JOIN pg_class pc ON (pc.oid = pol.polrelid) + WHERE relname = 't1'; + polname | relname +---------+--------- + p2 | t1 +(1 row) + +-- +-- Check INSERT SELECT +-- +SET SESSION AUTHORIZATION regress_rls_bob; +CREATE TABLE t2 (a integer, b text); +INSERT INTO t2 (SELECT * FROM t1); +EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1); + QUERY PLAN +------------------------------- + Insert on t2 + -> Seq Scan on t1 + Filter: ((a % 2) = 0) +(3 rows) + +SELECT * FROM t2; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t2; + QUERY PLAN +---------------- + Seq Scan on t2 +(1 row) + +CREATE TABLE t3 AS SELECT * FROM t1; +SELECT * FROM t3; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +SELECT * INTO t4 FROM t1; +SELECT * FROM t4; + a | b +----+---------------------------------- + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(12 rows) + +-- +-- RLS with JOIN +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE blog (id integer, author text, post text); +CREATE TABLE comment (blog_id integer, message text); +GRANT ALL ON blog, comment TO regress_rls_bob; +CREATE POLICY blog_1 ON blog USING (id % 2 = 0); +ALTER TABLE blog ENABLE ROW LEVEL SECURITY; +INSERT INTO blog VALUES + (1, 'alice', 'blog #1'), + (2, 'bob', 'blog #1'), + (3, 'alice', 'blog #2'), + (4, 'alice', 'blog #3'), + (5, 'john', 'blog #1'); +INSERT INTO comment VALUES + (1, 'cool blog'), + (1, 'fun blog'), + (3, 'crazy blog'), + (5, 'what?'), + (4, 'insane!'), + (2, 'who did it?'); +SET SESSION AUTHORIZATION regress_rls_bob; +-- Check RLS JOIN with Non-RLS. +SELECT id, author, message FROM blog JOIN comment ON id = blog_id; + id | author | message +----+--------+------------- + 4 | alice | insane! + 2 | bob | who did it? +(2 rows) + +-- Check Non-RLS JOIN with RLS. +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + id | author | message +----+--------+------------- + 4 | alice | insane! + 2 | bob | who did it? +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE POLICY comment_1 ON comment USING (blog_id < 4); +ALTER TABLE 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; + id | author | message +----+--------+------------- + 2 | bob | who did it? +(1 row) + +SELECT id, author, message FROM comment JOIN blog ON id = blog_id; + id | author | message +----+--------+------------- + 2 | bob | who did it? +(1 row) + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE blog, comment; +-- +-- Default Deny Policy +-- +RESET SESSION AUTHORIZATION; +DROP POLICY p2 ON t1; +ALTER TABLE t1 OWNER TO regress_rls_alice; +-- Check that default deny does not apply to superuser. +RESET SESSION AUTHORIZATION; +SELECT * FROM t1; + a | b +----+---------------------------------- + 1 | c4ca4238a0b923820dcc509a6f75849b + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 11 | 6512bd43d9caa6e02c990b0a82652dca + 13 | c51ce410c124a10e0db5e4b97fc2af39 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 17 | 70efdf2ec9b086079795c442636b55fb + 19 | 1f0e3dad99908345f7439f8ffabdffc4 + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(22 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +---------------- + Seq Scan on t1 +(1 row) + +-- Check that default deny does not apply to table owner. +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM t1; + a | b +----+---------------------------------- + 1 | c4ca4238a0b923820dcc509a6f75849b + 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + 5 | e4da3b7fbbce2345d7772b0674a318d5 + 7 | 8f14e45fceea167a5a36dedd4bea2543 + 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + 11 | 6512bd43d9caa6e02c990b0a82652dca + 13 | c51ce410c124a10e0db5e4b97fc2af39 + 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + 17 | 70efdf2ec9b086079795c442636b55fb + 19 | 1f0e3dad99908345f7439f8ffabdffc4 + 0 | cfcd208495d565ef66e7dff9f98764da + 2 | c81e728d9d4c2f636f067f89cc14862c + 4 | a87ff679a2f3e71d9181a67b7542122c + 6 | 1679091c5a880faf6fb5e6087eb1b2dc + 8 | c9f0f895fb98ab9159f51fd0297e236d + 10 | d3d9446802a44259755d38e6d163e820 + 12 | c20ad4d76fe97759aa27a0c99bff6710 + 14 | aab3238922bcc25a6f606eb525ffdc56 + 16 | c74d97b01eae257e44aa9d5bade97baf + 18 | 6f4922f45568161a8cdf4ad2299f6d23 + 20 | 98f13708210194c475687be6106a3b84 + 20 | Success +(22 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +---------------- + Seq Scan on t1 +(1 row) + +-- Check that default deny applies to non-owner/non-superuser when RLS on. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO ON; +SELECT * FROM t1; + a | b +---+--- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM t1; + a | b +---+--- +(0 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM t1; + QUERY PLAN +-------------------------- + Result + One-Time Filter: false +(2 rows) + +-- +-- COPY TO/FROM +-- +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t CASCADE; +ERROR: table "copy_t" does not exist +CREATE TABLE copy_t (a integer, b text); +CREATE POLICY p1 ON copy_t USING (a % 2 = 0); +ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY; +GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user; +INSERT INTO copy_t (SELECT x, md5(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 ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +-- Check COPY TO as user with permissions. +SET SESSION AUTHORIZATION regress_rls_bob; +SET row_security TO OFF; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +2,c81e728d9d4c2f636f067f89cc14862c +4,a87ff679a2f3e71d9181a67b7542122c +6,1679091c5a880faf6fb5e6087eb1b2dc +8,c9f0f895fb98ab9159f51fd0297e236d +10,d3d9446802a44259755d38e6d163e820 +-- 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 ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok +0,cfcd208495d565ef66e7dff9f98764da +1,c4ca4238a0b923820dcc509a6f75849b +2,c81e728d9d4c2f636f067f89cc14862c +3,eccbc87e4b5ce2fe28308fd9f2a7baf3 +4,a87ff679a2f3e71d9181a67b7542122c +5,e4da3b7fbbce2345d7772b0674a318d5 +6,1679091c5a880faf6fb5e6087eb1b2dc +7,8f14e45fceea167a5a36dedd4bea2543 +8,c9f0f895fb98ab9159f51fd0297e236d +9,45c48cce2e2d7fbdea1afc51c7c6ad26 +10,d3d9446802a44259755d38e6d163e820 +-- 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 ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied +ERROR: permission denied for table copy_t +-- Check COPY relation TO; keep it just one row to avoid reordering issues +RESET SESSION AUTHORIZATION; +SET row_security TO ON; +CREATE TABLE copy_rel_to (a integer, b text); +CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0); +ALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY; +GRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user; +INSERT INTO copy_rel_to VALUES (1, md5('1')); +-- Check COPY TO as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; +1,c4ca4238a0b923820dcc509a6f75849b +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; +1,c4ca4238a0b923820dcc509a6f75849b +-- 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 +ERROR: query would be affected by row-level security policy for table "copy_rel_to" +SET row_security TO ON; +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 +1,c4ca4238a0b923820dcc509a6f75849b +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok +1,c4ca4238a0b923820dcc509a6f75849b +-- 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 +ERROR: permission denied for table copy_rel_to +SET row_security TO ON; +COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied +ERROR: permission denied for table copy_rel_to +-- Check COPY FROM as Superuser/owner. +RESET SESSION AUTHORIZATION; +SET row_security TO OFF; +COPY copy_t FROM STDIN; --ok +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +-- 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. +ERROR: query would be affected by row-level security policy for table "copy_t" +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS. +ERROR: COPY FROM not supported with row-level security +HINT: Use INSERT statements instead. +-- Check COPY FROM as user with permissions and BYPASSRLS +SET SESSION AUTHORIZATION regress_rls_exempt_user; +SET row_security TO ON; +COPY copy_t FROM STDIN; --ok +-- 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. +ERROR: permission denied for table copy_t +SET row_security TO ON; +COPY copy_t FROM STDIN; --fail - permission denied. +ERROR: permission denied for table copy_t +RESET SESSION AUTHORIZATION; +DROP TABLE copy_t; +DROP TABLE copy_rel_to CASCADE; +-- Check WHERE CURRENT OF +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE current_check (currentid int, payload text, rlsuser text); +GRANT ALL ON current_check TO PUBLIC; +INSERT INTO current_check VALUES + (1, 'abc', 'regress_rls_bob'), + (2, 'bcd', 'regress_rls_bob'), + (3, 'cde', 'regress_rls_bob'), + (4, 'def', 'regress_rls_bob'); +CREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0); +CREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user); +CREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user); +ALTER TABLE current_check ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +-- Can SELECT even rows +SELECT * FROM current_check; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob + 4 | def | regress_rls_bob +(2 rows) + +-- Cannot UPDATE row 2 +UPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *; + currentid | payload | rlsuser +-----------+---------+--------- +(0 rows) + +BEGIN; +DECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check; +-- Returns rows that can be seen according to SELECT policy, like plain SELECT +-- above (even rows) +FETCH ABSOLUTE 1 FROM current_check_cursor; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob +(1 row) + +-- Still cannot UPDATE row 2 through cursor +UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *; + currentid | payload | rlsuser +-----------+---------+--------- +(0 rows) + +-- Can update row 4 through cursor, which is the next visible row +FETCH RELATIVE 1 FROM current_check_cursor; + currentid | payload | rlsuser +-----------+---------+----------------- + 4 | def | regress_rls_bob +(1 row) + +UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *; + currentid | payload | rlsuser +-----------+---------+----------------- + 4 | def_new | regress_rls_bob +(1 row) + +SELECT * FROM current_check; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob + 4 | def_new | regress_rls_bob +(2 rows) + +-- Plan should be a subquery TID scan +EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor; + QUERY PLAN +------------------------------------------------------------- + Update on current_check + -> Tid Scan on current_check + TID Cond: CURRENT OF current_check_cursor + Filter: ((currentid = 4) AND ((currentid % 2) = 0)) +(4 rows) + +-- Similarly can only delete row 4 +FETCH ABSOLUTE 1 FROM current_check_cursor; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob +(1 row) + +DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; + currentid | payload | rlsuser +-----------+---------+--------- +(0 rows) + +FETCH RELATIVE 1 FROM current_check_cursor; + currentid | payload | rlsuser +-----------+---------+----------------- + 4 | def | regress_rls_bob +(1 row) + +DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; + currentid | payload | rlsuser +-----------+---------+----------------- + 4 | def_new | regress_rls_bob +(1 row) + +SELECT * FROM current_check; + currentid | payload | rlsuser +-----------+---------+----------------- + 2 | bcd | regress_rls_bob +(1 row) + +COMMIT; +-- +-- check pg_stats view filtering +-- +SET row_security TO ON; +SET SESSION AUTHORIZATION regress_rls_alice; +ANALYZE current_check; +-- Stats visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + f +(1 row) + +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + attname | most_common_vals +-----------+------------------- + currentid | + payload | + rlsuser | {regress_rls_bob} +(3 rows) + +SET SESSION AUTHORIZATION regress_rls_bob; +-- Stats not visible +SELECT row_security_active('current_check'); + row_security_active +--------------------- + t +(1 row) + +SELECT attname, most_common_vals FROM pg_stats + WHERE tablename = 'current_check' + ORDER BY 1; + attname | most_common_vals +---------+------------------ +(0 rows) + +-- +-- Collation support +-- +BEGIN; +CREATE TABLE coll_t (c) AS VALUES ('bar'::text); +CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); +ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON coll_t TO regress_rls_alice; +SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass; + inputcollid +------------------ + inputcollid 950 +(1 row) + +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM coll_t; + c +----- + bar +(1 row) + +ROLLBACK; +-- +-- Shared Object Dependencies +-- +RESET SESSION AUTHORIZATION; +BEGIN; +CREATE ROLE regress_rls_eve; +CREATE ROLE regress_rls_frank; +CREATE TABLE tbl1 (c) AS VALUES ('bar'::text); +GRANT SELECT ON TABLE 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; + refclassid | deptype +------------+--------- + pg_class | a +(1 row) + +SELECT refclassid::regclass, deptype + FROM pg_shdepend + WHERE classid = 'pg_policy'::regclass + AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole); + refclassid | deptype +------------+--------- + pg_authid | r + pg_authid | r +(2 rows) + +SAVEPOINT q; +DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p +ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it +DETAIL: privileges for table tbl1 +target of policy p on table tbl1 +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 +ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it +DETAIL: privileges for table tbl1 +ROLLBACK TO q; +REVOKE ALL ON TABLE 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 +ERROR: role "regress_rls_frank" cannot be dropped because some objects depend on it +DETAIL: target of policy p on table tbl1 +ROLLBACK TO q; +DROP POLICY p ON tbl1; +SAVEPOINT q; +DROP ROLE regress_rls_frank; -- succeeds +ROLLBACK TO q; +ROLLBACK; -- cleanup +-- +-- Converting table to view +-- +BEGIN; +CREATE TABLE t (c int); +CREATE POLICY p ON t USING (c % 2 = 1); +ALTER TABLE t ENABLE ROW LEVEL SECURITY; +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled +ERROR: could not convert table "t" to a view because it has row security enabled +ROLLBACK TO q; +ALTER TABLE t DISABLE ROW LEVEL SECURITY; +SAVEPOINT q; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t +ERROR: could not convert table "t" to a view because it has row security policies +ROLLBACK TO q; +DROP POLICY p ON t; +CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD + SELECT * FROM generate_series(1,5) t0(c); -- succeeds +ROLLBACK; +-- +-- Policy expression handling +-- +BEGIN; +CREATE TABLE t (c) AS VALUES ('bar'::text); +CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions +ERROR: aggregate functions are not allowed in policy expressions +ROLLBACK; +-- +-- Non-target relations are only subject to SELECT policies +-- +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE r1 (a int); +CREATE TABLE r2 (a int); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO r2 VALUES (10), (20); +GRANT ALL ON r1, r2 TO regress_rls_bob; +CREATE POLICY p1 ON r1 USING (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON r2 FOR SELECT USING (true); +CREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false); +CREATE POLICY p3 ON r2 FOR UPDATE USING (false); +CREATE POLICY p4 ON r2 FOR DELETE USING (false); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +SET SESSION AUTHORIZATION regress_rls_bob; +SELECT * FROM r1; + a +---- + 10 + 20 +(2 rows) + +SELECT * FROM r2; + a +---- + 10 + 20 +(2 rows) + +-- r2 is read-only +INSERT INTO r2 VALUES (2); -- Not allowed +ERROR: new row violates row-level security policy for table "r2" +UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing + a +--- +(0 rows) + +DELETE FROM r2 RETURNING *; -- Deletes nothing + a +--- +(0 rows) + +-- r2 can be used as a non-target relation in DML +INSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK + a +---- + 11 + 21 +(2 rows) + +UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK + a | a +----+---- + 12 | 10 + 22 | 20 +(2 rows) + +DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK + a | a +----+---- + 12 | 10 + 22 | 20 +(2 rows) + +SELECT * FROM r1; + a +---- + 11 + 21 +(2 rows) + +SELECT * FROM r2; + a +---- + 10 + 20 +(2 rows) + +SET SESSION AUTHORIZATION regress_rls_alice; +DROP TABLE r1; +DROP TABLE r2; +-- +-- FORCE ROW LEVEL SECURITY applies RLS to owners too +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); +INSERT INTO r1 VALUES (10), (20); +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- No error, but no rows +TABLE r1; + a +--- +(0 rows) + +-- RLS error +INSERT INTO r1 VALUES (1); +ERROR: new row violates row-level security policy for table "r1" +-- No error (unable to see any rows to update) +UPDATE r1 SET a = 1; +TABLE r1; + a +--- +(0 rows) + +-- No error (unable to see any rows to delete) +DELETE FROM r1; +TABLE r1; + a +--- +(0 rows) + +SET row_security = off; +-- these all fail, would be affected by RLS +TABLE r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +UPDATE r1 SET a = 1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +DELETE FROM r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +DROP TABLE r1; +-- +-- FORCE ROW LEVEL SECURITY does not break RI +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO 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); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Errors due to rows in r2 +DELETE FROM r1; +ERROR: update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2" +DETAIL: Key (a)=(10) is still referenced from table "r2". +-- Reset r2 to no-RLS +DROP POLICY p1 ON r2; +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +ALTER TABLE r2 DISABLE ROW LEVEL SECURITY; +-- clean out r2 for INSERT test below +DELETE FROM r2; +-- Change r1 to not allow rows to be seen +CREATE POLICY p1 ON r1 USING (false); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- No rows seen +TABLE r1; + a +--- +(0 rows) + +-- No error, RI still sees that row exists in r1 +INSERT INTO r2 VALUES (10); +DROP TABLE r2; +DROP TABLE r1; +-- Ensure cascaded DELETE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO 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); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Deletes all records from both +DELETE FROM r1; +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +-- As owner, we now bypass RLS +-- verify no rows in r2 now +TABLE r2; + a +--- +(0 rows) + +DROP TABLE r2; +DROP TABLE r1; +-- Ensure cascaded UPDATE works +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE); +INSERT INTO r1 VALUES (10), (20); +INSERT INTO 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); +ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r2 FORCE ROW LEVEL SECURITY; +-- Updates records in both +UPDATE r1 SET a = a+5; +-- Remove FORCE from r2 +ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; +-- As owner, we now bypass RLS +-- verify records in r2 updated +TABLE r2; + a +---- + 15 + 25 +(2 rows) + +DROP TABLE r2; +DROP TABLE r1; +-- +-- Test INSERT+RETURNING applies SELECT policies as +-- WithCheckOptions (meaning an error is thrown) +-- +SET SESSION AUTHORIZATION regress_rls_alice; +SET row_security = on; +CREATE TABLE r1 (a int); +CREATE POLICY p1 ON r1 FOR SELECT USING (false); +CREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Works fine +INSERT INTO r1 VALUES (10), (20); +-- No error, but no rows +TABLE r1; + a +--- +(0 rows) + +SET row_security = off; +-- fail, would be affected by RLS +TABLE r1; +ERROR: query would be affected by row-level security policy for table "r1" +HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. +SET row_security = on; +-- Error +INSERT INTO r1 VALUES (10), (20) RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +DROP 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; +CREATE TABLE r1 (a int PRIMARY KEY); +CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20); +CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true); +CREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true); +INSERT INTO r1 VALUES (10); +ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Works fine +UPDATE r1 SET a = 30; +-- Show updated rows +ALTER TABLE r1 NO FORCE ROW LEVEL SECURITY; +TABLE r1; + a +---- + 30 +(1 row) + +-- reset value in r1 for test with RETURNING +UPDATE r1 SET a = 10; +-- Verify row reset +TABLE r1; + a +---- + 10 +(1 row) + +ALTER TABLE r1 FORCE ROW LEVEL SECURITY; +-- Error +UPDATE r1 SET a = 30 RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *; +ERROR: new row violates row-level security policy for table "r1" +-- Should still error out without RETURNING (use of arbiter always requires +-- SELECT permissions) +INSERT INTO r1 VALUES (10) + ON CONFLICT (a) DO UPDATE SET a = 30; +ERROR: new row violates row-level security policy for table "r1" +INSERT INTO r1 VALUES (10) + ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30; +ERROR: new row violates row-level security policy for table "r1" +DROP TABLE r1; +-- Check dependency handling +RESET SESSION AUTHORIZATION; +CREATE TABLE dep1 (c1 int); +CREATE TABLE dep2 (c1 int); +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(*) = 1 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + ?column? +---------- + t +(1 row) + +ALTER POLICY dep_p1 ON dep1 USING (true); +-- Should return one +SELECT count(*) = 1 FROM 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'); + ?column? +---------- + t +(1 row) + +-- Should return one +SELECT count(*) = 1 FROM 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'); + ?column? +---------- + t +(1 row) + +-- Should return zero +SELECT count(*) = 0 FROM pg_depend + WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') + AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); + ?column? +---------- + t +(1 row) + +-- DROP OWNED BY testing +RESET SESSION AUTHORIZATION; +CREATE ROLE regress_rls_dob_role1; +CREATE ROLE regress_rls_dob_role2; +CREATE TABLE dob_t1 (c1 int); +CREATE TABLE 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 +ERROR: policy "p1" for table "dob_t1" does not exist +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 +ERROR: policy "p1" for table "dob_t1" does not exist +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 +CREATE TABLE ref_tbl (a int); +INSERT INTO ref_tbl VALUES (1); +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl VALUES (10); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT 1 FROM ref_tbl)); +GRANT SELECT ON ref_tbl TO regress_rls_bob; +GRANT SELECT ON rls_tbl TO regress_rls_bob; +CREATE VIEW rls_view AS SELECT * FROM rls_tbl; +ALTER VIEW rls_view OWNER TO regress_rls_bob; +GRANT SELECT ON rls_view TO regress_rls_alice; +SET SESSION AUTHORIZATION regress_rls_alice; +SELECT * FROM ref_tbl; -- Permission denied +ERROR: permission denied for table ref_tbl +SELECT * FROM rls_tbl; -- Permission denied +ERROR: permission denied for table rls_tbl +SELECT * FROM rls_view; -- OK + a +---- + 10 +(1 row) + +RESET SESSION AUTHORIZATION; +DROP VIEW rls_view; +DROP TABLE rls_tbl; +DROP TABLE ref_tbl; +-- Leaky operator test +CREATE TABLE rls_tbl (a int); +INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; +ANALYZE rls_tbl; +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +GRANT SELECT ON rls_tbl TO regress_rls_alice; +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE FUNCTION op_leak(int, int) RETURNS bool + AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' + LANGUAGE plpgsql; +CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, + restrict = scalarltsel); +SELECT * FROM rls_tbl WHERE a <<< 1000; + a +--- +(0 rows) + +DROP OPERATOR <<< (int, int); +DROP FUNCTION op_leak(int, int); +RESET SESSION AUTHORIZATION; +DROP TABLE rls_tbl; +-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects +SET SESSION AUTHORIZATION regress_rls_alice; +CREATE TABLE rls_tbl (a int, b int, c int); +CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1)); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY; +INSERT INTO rls_tbl SELECT 10, 20, 30; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; + QUERY PLAN +-------------------------------------------------------------------- + Insert on regress_rls_schema.rls_tbl + -> Subquery Scan on ss + Output: ss.b, ss.c, NULL::integer + -> Sort + Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a + Sort Key: rls_tbl_1.a + -> Seq Scan on regress_rls_schema.rls_tbl rls_tbl_1 + Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a + Filter: (rls_tbl_1.* >= '(1,1,1)'::record) +(9 rows) + +INSERT INTO rls_tbl + SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; +SELECT * FROM rls_tbl; + a | b | c +----+----+---- + 10 | 20 | 30 + 20 | 30 | +(2 rows) + +DROP TABLE rls_tbl; +RESET SESSION AUTHORIZATION; +-- +-- Clean up objects +-- +RESET SESSION AUTHORIZATION; +DROP SCHEMA regress_rls_schema CASCADE; +NOTICE: drop cascades to 29 other objects +DETAIL: drop cascades to function f_leak(text) +drop cascades to table uaccount +drop cascades to table category +drop cascades to table document +drop cascades to table part_document +drop cascades to table dependent +drop cascades to table rec1 +drop cascades to table rec2 +drop cascades to view rec1v +drop cascades to view rec2v +drop cascades to table s1 +drop cascades to table s2 +drop cascades to view v2 +drop cascades to table b1 +drop cascades to view bv1 +drop cascades to table z1 +drop cascades to table z2 +drop cascades to table x1 +drop cascades to table y1 +drop cascades to table y2 +drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 +drop cascades to table t4 +drop cascades to table current_check +drop cascades to table dep1 +drop cascades to table dep2 +drop cascades to table dob_t1 +drop cascades to table dob_t2 +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 +CREATE SCHEMA regress_rls_schema; +CREATE TABLE rls_tbl (c1 int); +ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl USING (c1 > 5); +CREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3); +CREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5); +CREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3); +CREATE TABLE rls_tbl_force (c1 int); +ALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY; +ALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY; +CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5); +CREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8); +CREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5); +CREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8); |