4580 lines
166 KiB
Plaintext
4580 lines
166 KiB
Plaintext
--
|
|
-- 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=arwdDxtm/regress_rls_alice+| |
|
|
| | | =arwdDxtm/regress_rls_alice | |
|
|
regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | p1: +
|
|
| | | =arwdDxtm/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=arwdDxtm/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))
|
|
Not-null constraints:
|
|
"part_document_dlevel_not_null" NOT NULL "dlevel"
|
|
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, public.fipshash(x::text) FROM generate_series(-10,10) x);
|
|
CREATE TABLE s2 (x int, y text);
|
|
INSERT INTO s2 (SELECT x, public.fipshash(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 => 03b26944890929ff751653acb2f2af79
|
|
a | b
|
|
----+----------------------------------
|
|
-6 | 03b26944890929ff751653acb2f2af79
|
|
(1 row)
|
|
|
|
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 => 03b26944890929ff751653acb2f2af79
|
|
a | b
|
|
----+----------------------------------
|
|
-6 | 03b26944890929ff751653acb2f2af79
|
|
(1 row)
|
|
|
|
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
|
|
----+----+----------------------------------
|
|
-4 | -4 | e5e0093f285a4fb94c3fcc2ad7fd04ed
|
|
(1 row)
|
|
|
|
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 t1_1
|
|
Update on t2 t1_2
|
|
Update on t3 t1_3
|
|
-> Result
|
|
-> 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))
|
|
(12 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 t1_1
|
|
Update on t2 t1_2
|
|
Update on t3 t1_3
|
|
-> 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))
|
|
(14 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);
|
|
NOTICE: f_leak => cde
|
|
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_1
|
|
Update on t2 t1_1_2
|
|
Update on t3 t1_1_3
|
|
-> Nested Loop
|
|
Join Filter: (t1_1.b = t1_2.b)
|
|
-> Append
|
|
-> Seq Scan on t1 t1_1_1
|
|
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
|
|
-> Seq Scan on t2 t1_1_2
|
|
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
|
|
-> Seq Scan on t3 t1_1_3
|
|
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
|
|
-> Materialize
|
|
-> 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))
|
|
(21 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
|
|
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 t1_1
|
|
Delete on t2 t1_2
|
|
Delete on t3 t1_3
|
|
-> 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))
|
|
(11 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, public.fipshash(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 => d4735e3a265e16eee03f59718b9b5d03
|
|
NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02
|
|
NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f
|
|
NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00
|
|
NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1
|
|
a | b
|
|
----+----------------------------------
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
(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 => 4b227777d4dd1fc61c6f884f48641d02
|
|
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 => e7f6c011776e8db7cd330b54174fd76f
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
SELECT * FROM b1;
|
|
a | b
|
|
-----+----------------------------------
|
|
-10 | c171d4ec282b23db89a99880cd624e9b
|
|
-9 | d5c534fde62beb89c745a59952c8efed
|
|
-8 | e91592205d3881e3ea35d66973bb4898
|
|
-7 | a770d3270c9dcdedf12ed9fd70444f7c
|
|
-6 | 03b26944890929ff751653acb2f2af79
|
|
-5 | 37aa1ccf80e481832b2db282d4d4f895
|
|
-4 | e5e0093f285a4fb94c3fcc2ad7fd04ed
|
|
-3 | 615bdd17c2556f82f384392ea8557f8c
|
|
-2 | cf3bae39dd692048a8bf961182e6a34d
|
|
-1 | 1bad6b8cf97131fceab8543e81f77571
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
1 | 6b86b273ff34fce19d6b804eff5a3f57
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
3 | 4e07408562bedb8b60ce05c1decfe3ad
|
|
5 | ef2d127de37b942baad06145e54b0c61
|
|
7 | 7902699be42c8a8e46fbbb4501726517
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
9 | 19581e27de7ced00ff1ce50b2047e7a5
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
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"
|
|
--
|
|
-- MERGE
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP POLICY p3_with_all ON document;
|
|
ALTER TABLE document ADD COLUMN dnotes text DEFAULT '';
|
|
-- all documents are readable
|
|
CREATE POLICY p1 ON document FOR SELECT USING (true);
|
|
-- one may insert documents only authored by them
|
|
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
|
|
-- one may only update documents in 'novel' category and new dlevel must be > 0
|
|
CREATE POLICY p3 ON document FOR UPDATE
|
|
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
|
|
WITH CHECK (dlevel > 0);
|
|
-- one may only delete documents in 'manga' category
|
|
CREATE POLICY p4 ON document FOR DELETE
|
|
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
|
|
SELECT * FROM document;
|
|
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
-----+-----+--------+-------------------+----------------------------------+--------
|
|
1 | 11 | 1 | regress_rls_bob | my first 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 |
|
|
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
|
2 | 11 | 2 | regress_rls_bob | my first novel |
|
|
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
|
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
|
(14 rows)
|
|
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
-- Fails, since update violates WITH CHECK qual on dlevel
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dlevel = 0;
|
|
ERROR: new row violates row-level security policy for table "document"
|
|
-- Should be OK since USING and WITH CHECK quals pass
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
|
|
-- Even when dlevel is updated explicitly, but to the existing value
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dlevel = 1;
|
|
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
|
|
-- updating an item in category 'science fiction'
|
|
MERGE INTO document d
|
|
USING (SELECT 3 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge ';
|
|
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
-- The same thing with DELETE action, but fails again because no permissions
|
|
-- to delete items in 'science fiction' category that did 3 belongs to.
|
|
MERGE INTO document d
|
|
USING (SELECT 3 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
-- Document with did 4 belongs to 'manga' category which is allowed for
|
|
-- deletion. But this fails because the UPDATE action is matched first and
|
|
-- UPDATE policy does not allow updation in the category.
|
|
MERGE INTO document d
|
|
USING (SELECT 4 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED AND dnotes = '' THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
-- UPDATE action is not matched this time because of the WHEN qual.
|
|
-- DELETE still fails because role regress_rls_bob does not have SELECT
|
|
-- privileges on 'manga' category row in the category table.
|
|
MERGE INTO document d
|
|
USING (SELECT 4 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED AND dnotes <> '' THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
ERROR: target row violates row-level security policy (USING expression) for table "document"
|
|
-- OK if DELETE is replaced with DO NOTHING
|
|
MERGE INTO document d
|
|
USING (SELECT 4 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED AND dnotes <> '' THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
WHEN MATCHED THEN
|
|
DO NOTHING;
|
|
SELECT * FROM document WHERE did = 4;
|
|
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
-----+-----+--------+-----------------+----------------+--------
|
|
4 | 44 | 1 | regress_rls_bob | my first manga |
|
|
(1 row)
|
|
|
|
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
|
|
-- this time
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
MERGE INTO document d
|
|
USING (SELECT 4 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED AND dnotes <> '' THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge '
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
-- Switch back to regress_rls_bob role
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
-- Try INSERT action. This fails because we are trying to insert
|
|
-- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow
|
|
-- that
|
|
MERGE INTO document d
|
|
USING (SELECT 12 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel');
|
|
ERROR: new row violates row-level security policy for table "document"
|
|
-- This should be fine
|
|
MERGE INTO document d
|
|
USING (SELECT 12 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
-- ok
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge4 '
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
-- drop and create a new SELECT policy which prevents us from reading
|
|
-- any document except with category 'novel'
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP POLICY p1 ON document;
|
|
CREATE POLICY p1 ON document FOR SELECT
|
|
USING (cid = (SELECT cid from category WHERE cname = 'novel'));
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
-- MERGE can no longer see the matching row and hence attempts the
|
|
-- NOT MATCHED action, which results in unique key violation
|
|
MERGE INTO document d
|
|
USING (SELECT 7 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
|
|
ERROR: duplicate key value violates unique constraint "document_pkey"
|
|
-- UPDATE action fails if new row is not visible
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge6 ',
|
|
cid = (SELECT cid from category WHERE cname = 'technology');
|
|
ERROR: new row violates row-level security policy for table "document"
|
|
-- but OK if new row is visible
|
|
MERGE INTO document d
|
|
USING (SELECT 1 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge7 ',
|
|
cid = (SELECT cid from category WHERE cname = 'novel');
|
|
-- OK to insert a new row that is not visible
|
|
MERGE INTO document d
|
|
USING (SELECT 13 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge8 '
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga');
|
|
SELECT * FROM document WHERE did = 13;
|
|
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
-----+-----+--------+---------+--------+--------
|
|
(0 rows)
|
|
|
|
-- but not OK if RETURNING is used
|
|
MERGE INTO document d
|
|
USING (SELECT 14 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge9 '
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (14, 44, 1, 'regress_rls_bob', 'new manga')
|
|
RETURNING *;
|
|
ERROR: new row violates row-level security policy for table "document"
|
|
-- but OK if new row is visible
|
|
MERGE INTO document d
|
|
USING (SELECT 14 as sdid) s
|
|
ON did = s.sdid
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dnotes = dnotes || ' notes added by merge10 '
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (14, 11, 1, 'regress_rls_bob', 'new novel')
|
|
RETURNING *;
|
|
sdid | did | cid | dlevel | dauthor | dtitle | dnotes
|
|
------+-----+-----+--------+-----------------+-----------+--------
|
|
14 | 14 | 11 | 1 | regress_rls_bob | new novel |
|
|
(1 row)
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
-- drop the restrictive SELECT policy so that we can look at the
|
|
-- final state of the table
|
|
DROP POLICY p1 ON document;
|
|
-- Just check everything went per plan
|
|
SELECT * FROM document;
|
|
did | cid | dlevel | dauthor | dtitle | dnotes
|
|
-----+-----+--------+-------------------+----------------------------------+----------------------------------------------------------------------------------------------
|
|
3 | 22 | 2 | regress_rls_bob | my science fiction |
|
|
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 |
|
|
33 | 22 | 1 | regress_rls_bob | okay science fiction |
|
|
2 | 11 | 2 | regress_rls_bob | my first novel |
|
|
78 | 33 | 1 | regress_rls_bob | some technology novel |
|
|
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
|
|
12 | 11 | 1 | regress_rls_bob | another novel |
|
|
1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4 notes added by merge7
|
|
13 | 44 | 1 | regress_rls_bob | new manga |
|
|
14 | 11 | 1 | regress_rls_bob | new novel |
|
|
(16 rows)
|
|
|
|
--
|
|
-- 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;
|
|
SET SESSION AUTHORIZATION 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)
|
|
|
|
-- Policy requiring access to another table.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
CREATE TABLE z1_blacklist (a int);
|
|
INSERT INTO z1_blacklist VALUES (3), (4);
|
|
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
|
|
-- Query as role that is not owner of table but is owner of view without permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
-- 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 table z1_blacklist
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
-- Query as role that is not owner of table but is owner of view with permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
(1 row)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------
|
|
Seq Scan on z1
|
|
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
|
|
SubPlan 1
|
|
-> Seq Scan on z1_blacklist
|
|
(4 rows)
|
|
|
|
-- Query as role that is not the owner of the table or view with permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
(1 row)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------
|
|
Seq Scan on z1
|
|
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
|
|
SubPlan 1
|
|
-> Seq Scan on z1_blacklist
|
|
(4 rows)
|
|
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
REVOKE SELECT ON z1_blacklist FROM regress_rls_bob;
|
|
DROP POLICY p3 ON z1;
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
DROP VIEW rls_view;
|
|
--
|
|
-- Security invoker views should follow policy for current user.
|
|
--
|
|
-- View and table owner are the same.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
CREATE VIEW rls_view WITH (security_invoker) AS
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
GRANT SELECT ON rls_view TO regress_rls_bob;
|
|
GRANT SELECT ON rls_view TO regress_rls_carol;
|
|
-- Query as table owner. Should return all records.
|
|
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)
|
|
|
|
-- Queries as other users.
|
|
-- Should return records based on current user's 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)
|
|
|
|
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => aba
|
|
NOTICE: f_leak => ccc
|
|
a | b
|
|
---+-----
|
|
1 | aba
|
|
3 | ccc
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
-----------------------------------------
|
|
Seq Scan on z1
|
|
Filter: (((a % 2) = 1) AND f_leak(b))
|
|
(2 rows)
|
|
|
|
-- View and table owners are different.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
DROP VIEW rls_view;
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
CREATE VIEW rls_view WITH (security_invoker) AS
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
GRANT SELECT ON rls_view TO regress_rls_alice;
|
|
GRANT SELECT ON rls_view TO regress_rls_carol;
|
|
-- Query as 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)
|
|
|
|
-- Queries as other users.
|
|
-- Should return records based on current user's 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)
|
|
|
|
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => aba
|
|
NOTICE: f_leak => ccc
|
|
a | b
|
|
---+-----
|
|
1 | aba
|
|
3 | ccc
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
-----------------------------------------
|
|
Seq Scan on z1
|
|
Filter: (((a % 2) = 1) AND f_leak(b))
|
|
(2 rows)
|
|
|
|
-- Policy requiring access to another table.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
|
|
-- Query as role that is not owner of table but is owner of view without permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
-- 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 table z1_blacklist
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
-- Query as role that is not owner of table but is owner of view with permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
(1 row)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------
|
|
Seq Scan on z1
|
|
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
|
|
SubPlan 1
|
|
-> Seq Scan on z1_blacklist
|
|
(4 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 table z1_blacklist
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for table z1_blacklist
|
|
-- Query as role that is not the owner of the table or view with permissions.
|
|
SET SESSION AUTHORIZATION regress_rls_alice;
|
|
GRANT SELECT ON z1_blacklist TO regress_rls_carol;
|
|
SET SESSION AUTHORIZATION regress_rls_carol;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => aba
|
|
a | b
|
|
---+-----
|
|
1 | aba
|
|
(1 row)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------
|
|
Seq Scan on z1
|
|
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 1) AND f_leak(b))
|
|
SubPlan 1
|
|
-> Seq Scan on z1_blacklist
|
|
(4 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, public.fipshash(x::text) FROM generate_series(0,20) x);
|
|
CREATE POLICY p2 ON y2 USING (a % 3 = 0);
|
|
CREATE POLICY p3 ON y2 USING (a % 4 = 0);
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
SELECT * FROM y2 WHERE f_leak(b);
|
|
NOTICE: f_leak => 5feceb66ffc86f38d952786c6d696c79
|
|
NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03
|
|
NOTICE: f_leak => 4e07408562bedb8b60ce05c1decfe3ad
|
|
NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02
|
|
NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f
|
|
NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00
|
|
NOTICE: f_leak => 19581e27de7ced00ff1ce50b2047e7a5
|
|
NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1
|
|
NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d
|
|
NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc
|
|
NOTICE: f_leak => e629fa6598d732768f7c726b4b621285
|
|
NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc
|
|
NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19
|
|
NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
|
|
a | b
|
|
----+----------------------------------
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
3 | 4e07408562bedb8b60ce05c1decfe3ad
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
9 | 19581e27de7ced00ff1ce50b2047e7a5
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
15 | e629fa6598d732768f7c726b4b621285
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
(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 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
3 | 4e07408562bedb8b60ce05c1decfe3ad
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
9 | 19581e27de7ced00ff1ce50b2047e7a5
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
15 | e629fa6598d732768f7c726b4b621285
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
(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 => 5feceb66ffc86f38d952786c6d696c79
|
|
NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03
|
|
NOTICE: f_leak => 4e07408562bedb8b60ce05c1decfe3ad
|
|
NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02
|
|
NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f
|
|
NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00
|
|
NOTICE: f_leak => 19581e27de7ced00ff1ce50b2047e7a5
|
|
NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1
|
|
NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d
|
|
NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc
|
|
NOTICE: f_leak => e629fa6598d732768f7c726b4b621285
|
|
NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc
|
|
NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19
|
|
NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
|
|
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, public.fipshash(x::text) FROM generate_series(0,20) x);
|
|
SET SESSION AUTHORIZATION regress_rls_bob;
|
|
WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
|
|
NOTICE: f_leak => 5feceb66ffc86f38d952786c6d696c79
|
|
NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03
|
|
NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02
|
|
NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f
|
|
NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00
|
|
NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1
|
|
NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d
|
|
NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc
|
|
NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc
|
|
NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19
|
|
NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
|
|
a | b
|
|
----+----------------------------------
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
(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 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
(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 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
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 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
20 | Success
|
|
(12 rows)
|
|
|
|
SELECT * INTO t4 FROM t1;
|
|
SELECT * FROM t4;
|
|
a | b
|
|
----+----------------------------------
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
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 | 6b86b273ff34fce19d6b804eff5a3f57
|
|
3 | 4e07408562bedb8b60ce05c1decfe3ad
|
|
5 | ef2d127de37b942baad06145e54b0c61
|
|
7 | 7902699be42c8a8e46fbbb4501726517
|
|
9 | 19581e27de7ced00ff1ce50b2047e7a5
|
|
11 | 4fc82b26aecb47d2868c4efbe3581732
|
|
13 | 3fdba35f04dc8c462986c992bcf87554
|
|
15 | e629fa6598d732768f7c726b4b621285
|
|
17 | 4523540f1504cd17100c4835e85b7eef
|
|
19 | 9400f1b21cb527d7fa3d3eabba93557a
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
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 | 6b86b273ff34fce19d6b804eff5a3f57
|
|
3 | 4e07408562bedb8b60ce05c1decfe3ad
|
|
5 | ef2d127de37b942baad06145e54b0c61
|
|
7 | 7902699be42c8a8e46fbbb4501726517
|
|
9 | 19581e27de7ced00ff1ce50b2047e7a5
|
|
11 | 4fc82b26aecb47d2868c4efbe3581732
|
|
13 | 3fdba35f04dc8c462986c992bcf87554
|
|
15 | e629fa6598d732768f7c726b4b621285
|
|
17 | 4523540f1504cd17100c4835e85b7eef
|
|
19 | 9400f1b21cb527d7fa3d3eabba93557a
|
|
0 | 5feceb66ffc86f38d952786c6d696c79
|
|
2 | d4735e3a265e16eee03f59718b9b5d03
|
|
4 | 4b227777d4dd1fc61c6f884f48641d02
|
|
6 | e7f6c011776e8db7cd330b54174fd76f
|
|
8 | 2c624232cdd221771294dfbb310aca00
|
|
10 | 4a44dc15364204a80fe80e9039455cc1
|
|
12 | 6b51d431df5d7f141cbececcf79edf3d
|
|
14 | 8527a891e224136950ff32ca212b45bc
|
|
16 | b17ef6d19c7a5b1ee83b907c595526dc
|
|
18 | 4ec9599fc203d176a301536c2e091a19
|
|
20 | f5ca38f748a1d6eaf726b8a42fb575c3
|
|
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, public.fipshash(x::text) FROM generate_series(0,10) x);
|
|
-- Check COPY TO as Superuser/owner.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
|
|
0,5feceb66ffc86f38d952786c6d696c79
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
2,d4735e3a265e16eee03f59718b9b5d03
|
|
3,4e07408562bedb8b60ce05c1decfe3ad
|
|
4,4b227777d4dd1fc61c6f884f48641d02
|
|
5,ef2d127de37b942baad06145e54b0c61
|
|
6,e7f6c011776e8db7cd330b54174fd76f
|
|
7,7902699be42c8a8e46fbbb4501726517
|
|
8,2c624232cdd221771294dfbb310aca00
|
|
9,19581e27de7ced00ff1ce50b2047e7a5
|
|
10,4a44dc15364204a80fe80e9039455cc1
|
|
SET row_security TO ON;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
|
|
0,5feceb66ffc86f38d952786c6d696c79
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
2,d4735e3a265e16eee03f59718b9b5d03
|
|
3,4e07408562bedb8b60ce05c1decfe3ad
|
|
4,4b227777d4dd1fc61c6f884f48641d02
|
|
5,ef2d127de37b942baad06145e54b0c61
|
|
6,e7f6c011776e8db7cd330b54174fd76f
|
|
7,7902699be42c8a8e46fbbb4501726517
|
|
8,2c624232cdd221771294dfbb310aca00
|
|
9,19581e27de7ced00ff1ce50b2047e7a5
|
|
10,4a44dc15364204a80fe80e9039455cc1
|
|
-- 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,5feceb66ffc86f38d952786c6d696c79
|
|
2,d4735e3a265e16eee03f59718b9b5d03
|
|
4,4b227777d4dd1fc61c6f884f48641d02
|
|
6,e7f6c011776e8db7cd330b54174fd76f
|
|
8,2c624232cdd221771294dfbb310aca00
|
|
10,4a44dc15364204a80fe80e9039455cc1
|
|
-- 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,5feceb66ffc86f38d952786c6d696c79
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
2,d4735e3a265e16eee03f59718b9b5d03
|
|
3,4e07408562bedb8b60ce05c1decfe3ad
|
|
4,4b227777d4dd1fc61c6f884f48641d02
|
|
5,ef2d127de37b942baad06145e54b0c61
|
|
6,e7f6c011776e8db7cd330b54174fd76f
|
|
7,7902699be42c8a8e46fbbb4501726517
|
|
8,2c624232cdd221771294dfbb310aca00
|
|
9,19581e27de7ced00ff1ce50b2047e7a5
|
|
10,4a44dc15364204a80fe80e9039455cc1
|
|
SET row_security TO ON;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
|
|
0,5feceb66ffc86f38d952786c6d696c79
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
2,d4735e3a265e16eee03f59718b9b5d03
|
|
3,4e07408562bedb8b60ce05c1decfe3ad
|
|
4,4b227777d4dd1fc61c6f884f48641d02
|
|
5,ef2d127de37b942baad06145e54b0c61
|
|
6,e7f6c011776e8db7cd330b54174fd76f
|
|
7,7902699be42c8a8e46fbbb4501726517
|
|
8,2c624232cdd221771294dfbb310aca00
|
|
9,19581e27de7ced00ff1ce50b2047e7a5
|
|
10,4a44dc15364204a80fe80e9039455cc1
|
|
-- 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, public.fipshash('1'));
|
|
-- Check COPY TO as Superuser/owner.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
-- 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,6b86b273ff34fce19d6b804eff5a3f57
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
-- 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 behavior with a child table.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO ON;
|
|
CREATE TABLE copy_rel_to_child () INHERITS (copy_rel_to);
|
|
INSERT INTO copy_rel_to_child VALUES (1, 'one'), (2, 'two');
|
|
-- Check COPY TO as Superuser/owner.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
-- 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,6b86b273ff34fce19d6b804eff5a3f57
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
|
|
1,6b86b273ff34fce19d6b804eff5a3f57
|
|
-- 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;
|
|
NOTICE: drop cascades to table copy_rel_to_child
|
|
-- 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
|
|
--
|
|
-- 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;
|
|
-- CVE-2023-2455: inlining an SRF may introduce an RLS dependency
|
|
create table rls_t (c text);
|
|
insert into rls_t values ('invisible to bob');
|
|
alter table rls_t enable row level security;
|
|
grant select on rls_t to regress_rls_alice, regress_rls_bob;
|
|
create policy p1 on rls_t for select to regress_rls_alice using (true);
|
|
create policy p2 on rls_t for select to regress_rls_bob using (false);
|
|
create function rls_f () returns setof rls_t
|
|
stable language sql
|
|
as $$ select * from rls_t $$;
|
|
prepare q as select current_user, * from rls_f();
|
|
set role regress_rls_alice;
|
|
execute q;
|
|
current_user | c
|
|
-------------------+------------------
|
|
regress_rls_alice | invisible to bob
|
|
(1 row)
|
|
|
|
set role regress_rls_bob;
|
|
execute q;
|
|
current_user | c
|
|
--------------+---
|
|
(0 rows)
|
|
|
|
RESET ROLE;
|
|
DROP FUNCTION rls_f();
|
|
DROP TABLE rls_t;
|
|
--
|
|
-- Clean up objects
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP SCHEMA regress_rls_schema CASCADE;
|
|
NOTICE: drop cascades to 30 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 z1_blacklist
|
|
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);
|