3469 lines
114 KiB
Plaintext
3469 lines
114 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 rls_regress_user0;
|
|
DROP USER IF EXISTS rls_regress_user1;
|
|
DROP USER IF EXISTS rls_regress_user2;
|
|
DROP USER IF EXISTS rls_regress_exempt_user;
|
|
DROP ROLE IF EXISTS rls_regress_group1;
|
|
DROP ROLE IF EXISTS rls_regress_group2;
|
|
DROP SCHEMA IF EXISTS rls_regress_schema CASCADE;
|
|
RESET client_min_messages;
|
|
-- initial setup
|
|
CREATE USER rls_regress_user0;
|
|
CREATE USER rls_regress_user1;
|
|
CREATE USER rls_regress_user2;
|
|
CREATE USER rls_regress_exempt_user BYPASSRLS;
|
|
CREATE ROLE rls_regress_group1 NOLOGIN;
|
|
CREATE ROLE rls_regress_group2 NOLOGIN;
|
|
GRANT rls_regress_group1 TO rls_regress_user1;
|
|
GRANT rls_regress_group2 TO rls_regress_user2;
|
|
CREATE SCHEMA rls_regress_schema;
|
|
GRANT ALL ON SCHEMA rls_regress_schema to public;
|
|
SET search_path = rls_regress_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 rls_regress_user0;
|
|
CREATE TABLE uaccount (
|
|
pguser name primary key,
|
|
seclv int
|
|
);
|
|
GRANT SELECT ON uaccount TO public;
|
|
INSERT INTO uaccount VALUES
|
|
('rls_regress_user0', 99),
|
|
('rls_regress_user1', 1),
|
|
('rls_regress_user2', 2),
|
|
('rls_regress_user3', 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, 'rls_regress_user1', 'my first novel'),
|
|
( 2, 11, 2, 'rls_regress_user1', 'my second novel'),
|
|
( 3, 22, 2, 'rls_regress_user1', 'my science fiction'),
|
|
( 4, 44, 1, 'rls_regress_user1', 'my first manga'),
|
|
( 5, 44, 2, 'rls_regress_user1', 'my second manga'),
|
|
( 6, 22, 1, 'rls_regress_user2', 'great science fiction'),
|
|
( 7, 33, 2, 'rls_regress_user2', 'great technology book'),
|
|
( 8, 44, 1, 'rls_regress_user2', 'great manga');
|
|
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
|
|
USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
|
|
-- viewpoint from rls_regress_user1
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
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
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
(4 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
|
|
cid | did | dlevel | dauthor | dtitle | cname
|
|
-----+-----+--------+-------------------+-----------------------+-----------------
|
|
11 | 1 | 1 | rls_regress_user1 | my first novel | novel
|
|
44 | 4 | 1 | rls_regress_user1 | my first manga | manga
|
|
22 | 6 | 1 | rls_regress_user2 | great science fiction | science fiction
|
|
44 | 8 | 1 | rls_regress_user2 | great manga | manga
|
|
(4 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
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
(3 rows)
|
|
|
|
-- viewpoint from rls_regress_user2
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
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
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
(8 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
|
|
cid | did | dlevel | dauthor | dtitle | cname
|
|
-----+-----+--------+-------------------+-----------------------+-----------------
|
|
11 | 1 | 1 | rls_regress_user1 | my first novel | novel
|
|
11 | 2 | 2 | rls_regress_user1 | my second novel | novel
|
|
22 | 3 | 2 | rls_regress_user1 | my science fiction | science fiction
|
|
44 | 4 | 1 | rls_regress_user1 | my first manga | manga
|
|
44 | 5 | 2 | rls_regress_user1 | my second manga | manga
|
|
22 | 6 | 1 | rls_regress_user2 | great science fiction | science fiction
|
|
33 | 7 | 2 | rls_regress_user2 | great technology book | technology
|
|
44 | 8 | 1 | rls_regress_user2 | great manga | manga
|
|
(8 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
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
|
|
QUERY PLAN
|
|
----------------------------------------------------------
|
|
Subquery Scan on document
|
|
Filter: f_leak(document.dtitle)
|
|
-> Seq Scan on document document_1
|
|
Filter: (dlevel <= $0)
|
|
InitPlan 1 (returns $0)
|
|
-> Index Scan using uaccount_pkey on uaccount
|
|
Index Cond: (pguser = "current_user"())
|
|
(7 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
|
|
QUERY PLAN
|
|
----------------------------------------------------------------------
|
|
Hash Join
|
|
Hash Cond: (category.cid = document.cid)
|
|
-> Seq Scan on category
|
|
-> Hash
|
|
-> Subquery Scan on document
|
|
Filter: f_leak(document.dtitle)
|
|
-> Seq Scan on document document_1
|
|
Filter: (dlevel <= $0)
|
|
InitPlan 1 (returns $0)
|
|
-> Index Scan using uaccount_pkey on uaccount
|
|
Index Cond: (pguser = "current_user"())
|
|
(11 rows)
|
|
|
|
-- only owner can change policies
|
|
ALTER POLICY p1 ON document USING (true); --fail
|
|
ERROR: must be owner of relation document
|
|
DROP POLICY p1 ON document; --fail
|
|
ERROR: must be owner of relation document
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
ALTER POLICY p1 ON document USING (dauthor = current_user);
|
|
-- viewpoint from rls_regress_user1 again
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
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 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | 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 | rls_regress_user1 | my first novel | novel
|
|
11 | 2 | 2 | rls_regress_user1 | my second novel | novel
|
|
22 | 3 | 2 | rls_regress_user1 | my science fiction | science fiction
|
|
44 | 4 | 1 | rls_regress_user1 | my first manga | manga
|
|
44 | 5 | 2 | rls_regress_user1 | my second manga | manga
|
|
(5 rows)
|
|
|
|
-- viewpoint from rls_regres_user2 again
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
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 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | 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 | rls_regress_user2 | great science fiction | science fiction
|
|
33 | 7 | 2 | rls_regress_user2 | great technology book | technology
|
|
44 | 8 | 1 | rls_regress_user2 | great manga | manga
|
|
(3 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
|
|
QUERY PLAN
|
|
----------------------------------------------
|
|
Subquery Scan on document
|
|
Filter: f_leak(document.dtitle)
|
|
-> Seq Scan on document document_1
|
|
Filter: (dauthor = "current_user"())
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
|
|
QUERY PLAN
|
|
----------------------------------------------------
|
|
Nested Loop
|
|
-> Subquery Scan on document
|
|
Filter: f_leak(document.dtitle)
|
|
-> Seq Scan on document document_1
|
|
Filter: (dauthor = "current_user"())
|
|
-> Index Scan using category_pkey on category
|
|
Index Cond: (cid = document.cid)
|
|
(7 rows)
|
|
|
|
-- interaction of FK/PK constraints
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE POLICY p2 ON category
|
|
USING (CASE WHEN current_user = 'rls_regress_user1' THEN cid IN (11, 33)
|
|
WHEN current_user = 'rls_regress_user2' 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 rls_regress_user1;
|
|
SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
|
|
did | cid | dlevel | dauthor | dtitle | cid | cname
|
|
-----+-----+--------+-------------------+--------------------+-----+------------
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel | 11 | novel
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel | 11 | novel
|
|
| | | | | 33 | technology
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga | |
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga | |
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction | |
|
|
(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 rls_regress_user2;
|
|
SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
|
|
did | cid | dlevel | dauthor | dtitle | cid | cname
|
|
-----+-----+--------+-------------------+-----------------------+-----+-----------------
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction | 22 | science fiction
|
|
8 | 44 | 1 | rls_regress_user2 | great manga | 44 | manga
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book | |
|
|
(3 rows)
|
|
|
|
INSERT INTO document VALUES (10, 33, 1, current_user, 'hoge');
|
|
-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', '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, 'rls_regress_user2', '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 = 'rls_regress_user2' 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 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
10 | 33 | 1 | rls_regress_user2 | hoge
|
|
(9 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 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
10 | 33 | 1 | rls_regress_user2 | hoge
|
|
(9 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 rls_regress_exempt_user;
|
|
SET row_security TO OFF;
|
|
SELECT * FROM document;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
10 | 33 | 1 | rls_regress_user2 | hoge
|
|
(9 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 rls_regress_user0;
|
|
SET row_security TO ON;
|
|
SELECT * FROM document;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
10 | 33 | 1 | rls_regress_user2 | hoge
|
|
(9 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 rls_regress_user0;
|
|
SET row_security TO OFF;
|
|
SELECT * FROM document;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
1 | 11 | 1 | rls_regress_user1 | my first novel
|
|
2 | 11 | 2 | rls_regress_user1 | my second novel
|
|
3 | 22 | 2 | rls_regress_user1 | my science fiction
|
|
4 | 44 | 1 | rls_regress_user1 | my first manga
|
|
5 | 44 | 2 | rls_regress_user1 | my second manga
|
|
6 | 22 | 1 | rls_regress_user2 | great science fiction
|
|
7 | 33 | 2 | rls_regress_user2 | great technology book
|
|
8 | 44 | 1 | rls_regress_user2 | great manga
|
|
10 | 33 | 1 | rls_regress_user2 | hoge
|
|
(9 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 rls_regress_user0;
|
|
SET row_security TO ON;
|
|
CREATE TABLE t1 (a int, junk1 text, b text) WITH OIDS;
|
|
ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor
|
|
GRANT ALL ON t1 TO public;
|
|
COPY t1 FROM stdin WITH (oids);
|
|
CREATE TABLE t2 (c float) INHERITS (t1);
|
|
GRANT ALL ON t2 TO public;
|
|
COPY t2 FROM stdin WITH (oids);
|
|
CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
|
|
ALTER TABLE t3 INHERIT t1;
|
|
GRANT ALL ON t3 TO public;
|
|
COPY t3(a,b,c) FROM stdin WITH (oids);
|
|
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 rls_regress_user1;
|
|
SELECT * FROM t1;
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
2 | bcd
|
|
4 | def
|
|
2 | yyy
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(7 rows)
|
|
|
|
SELECT * FROM t1 WHERE f_leak(b);
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
NOTICE: f_leak => bcd
|
|
NOTICE: f_leak => def
|
|
NOTICE: f_leak => yyy
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
2 | bcd
|
|
4 | def
|
|
2 | yyy
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------
|
|
Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> Append
|
|
-> Seq Scan on t1 t1_1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(9 rows)
|
|
|
|
-- reference to system column
|
|
SELECT oid, * FROM t1;
|
|
oid | a | b
|
|
-----+---+-----
|
|
102 | 2 | bbb
|
|
104 | 4 | ddd
|
|
202 | 2 | bcd
|
|
204 | 4 | def
|
|
302 | 2 | yyy
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(7 rows)
|
|
|
|
-- reference to whole-row reference
|
|
SELECT *, t1 FROM t1;
|
|
a | b | t1
|
|
---+-----+---------
|
|
2 | bbb | (2,bbb)
|
|
4 | ddd | (4,ddd)
|
|
2 | bcd | (2,bcd)
|
|
4 | def | (4,def)
|
|
2 | yyy | (2,yyy)
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(7 rows)
|
|
|
|
-- for share/update lock
|
|
SELECT * FROM t1 FOR SHARE;
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
2 | bcd
|
|
4 | def
|
|
2 | yyy
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
|
|
QUERY PLAN
|
|
-------------------------------------------------------
|
|
LockRows
|
|
-> Subquery Scan on t1
|
|
-> LockRows
|
|
-> Result
|
|
-> Append
|
|
-> Seq Scan on t1 t1_1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(11 rows)
|
|
|
|
SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
NOTICE: f_leak => bcd
|
|
NOTICE: f_leak => def
|
|
NOTICE: f_leak => yyy
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
2 | bcd
|
|
4 | def
|
|
2 | yyy
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
|
|
QUERY PLAN
|
|
-------------------------------------------------------
|
|
LockRows
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Result
|
|
-> Append
|
|
-> Seq Scan on t1 t1_1
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(12 rows)
|
|
|
|
-- union all query
|
|
SELECT a, b, oid FROM t2 UNION ALL SELECT a, b, oid FROM t3;
|
|
a | b | oid
|
|
---+-----+-----
|
|
1 | abc | 201
|
|
3 | cde | 203
|
|
1 | xxx | 301
|
|
2 | yyy | 302
|
|
3 | zzz | 303
|
|
(5 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT a, b, oid FROM t2 UNION ALL SELECT a, b, oid 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 => aaa
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ccc
|
|
NOTICE: f_leak => ddd
|
|
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
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
3 | ccc
|
|
4 | ddd
|
|
1 | abc
|
|
2 | bcd
|
|
3 | cde
|
|
4 | def
|
|
1 | xxx
|
|
2 | yyy
|
|
3 | zzz
|
|
(11 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t2
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t3
|
|
Filter: f_leak(b)
|
|
(7 rows)
|
|
|
|
-- non-superuser with bypass privilege can bypass RLS policy when disabled
|
|
SET SESSION AUTHORIZATION rls_regress_exempt_user;
|
|
SET row_security TO OFF;
|
|
SELECT * FROM t1 WHERE f_leak(b);
|
|
NOTICE: f_leak => aaa
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ccc
|
|
NOTICE: f_leak => ddd
|
|
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
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
3 | ccc
|
|
4 | ddd
|
|
1 | abc
|
|
2 | bcd
|
|
3 | cde
|
|
4 | def
|
|
1 | xxx
|
|
2 | yyy
|
|
3 | zzz
|
|
(11 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t2
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t3
|
|
Filter: f_leak(b)
|
|
(7 rows)
|
|
|
|
----- Dependencies -----
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
SELECT * FROM rec1; -- fail, direct recursion
|
|
ERROR: infinite recursion detected in policy for relation "rec1"
|
|
--
|
|
-- Mutual recursion
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
SELECT * FROM rec1; -- fail, mutual recursion
|
|
ERROR: infinite recursion detected in policy for relation "rec1"
|
|
--
|
|
-- Mutual recursion via views
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
CREATE VIEW rec1v AS SELECT * FROM rec1;
|
|
CREATE VIEW rec2v AS SELECT * FROM rec2;
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user1;
|
|
-- Suppress NOTICE messages when doing a cascaded drop.
|
|
SET client_min_messages TO 'warning';
|
|
DROP VIEW rec1v, rec2v CASCADE;
|
|
RESET client_min_messages;
|
|
CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
|
|
CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user0;
|
|
CREATE TABLE s1 (a int, b text);
|
|
INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
|
|
CREATE TABLE s2 (x int, y text);
|
|
INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
|
|
GRANT SELECT ON s1, s2 TO rls_regress_user1;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user0;
|
|
DROP POLICY p3 on s1;
|
|
ALTER POLICY p2 ON s2 USING (x % 2 = 0);
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM s1 WHERE f_leak(b); -- OK
|
|
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
a | b
|
|
---+----------------------------------
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
----------------------------------------------------------
|
|
Subquery Scan on s1
|
|
Filter: f_leak(s1.b)
|
|
-> Hash Join
|
|
Hash Cond: (s1_1.a = s2.x)
|
|
-> Seq Scan on s1 s1_1
|
|
-> Hash
|
|
-> HashAggregate
|
|
Group Key: s2.x
|
|
-> Subquery Scan on s2
|
|
Filter: (s2.y ~~ '%2f%'::text)
|
|
-> Seq Scan on s2 s2_1
|
|
Filter: ((x % 2) = 0)
|
|
(12 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM s1 WHERE f_leak(b); -- OK
|
|
NOTICE: f_leak => 0267aaf632e87a63288a08331f22c7c3
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
a | b
|
|
----+----------------------------------
|
|
-4 | 0267aaf632e87a63288a08331f22c7c3
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
----------------------------------------------------------
|
|
Subquery Scan on s1
|
|
Filter: f_leak(s1.b)
|
|
-> Hash Join
|
|
Hash Cond: (s1_1.a = s2.x)
|
|
-> Seq Scan on s1 s1_1
|
|
-> Hash
|
|
-> HashAggregate
|
|
Group Key: s2.x
|
|
-> Subquery Scan on s2
|
|
Filter: (s2.y ~~ '%af%'::text)
|
|
-> Seq Scan on s2 s2_1
|
|
Filter: ((x % 2) = 0)
|
|
(12 rows)
|
|
|
|
SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
|
|
xx | x | y
|
|
----+----+----------------------------------
|
|
-6 | -6 | 596a3d04481816330f07e4f97510c28f
|
|
-4 | -4 | 0267aaf632e87a63288a08331f22c7c3
|
|
2 | 2 | c81e728d9d4c2f636f067f89cc14862c
|
|
(3 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
|
|
QUERY PLAN
|
|
--------------------------------------------------------------------
|
|
Subquery Scan on s2
|
|
Filter: (s2.y ~~ '%28%'::text)
|
|
-> Seq Scan on s2 s2_1
|
|
Filter: ((x % 2) = 0)
|
|
SubPlan 1
|
|
-> Limit
|
|
-> Subquery Scan on s1
|
|
-> Nested Loop Semi Join
|
|
Join Filter: (s1_1.a = s2_2.x)
|
|
-> Seq Scan on s1 s1_1
|
|
-> Materialize
|
|
-> Subquery Scan on s2_2
|
|
Filter: (s2_2.y ~~ '%af%'::text)
|
|
-> Seq Scan on s2 s2_3
|
|
Filter: ((x % 2) = 0)
|
|
(15 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view)
|
|
ERROR: infinite recursion detected in policy for relation "s1"
|
|
-- prepared statement with rls_regress_user0 privilege
|
|
PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
|
|
EXECUTE p1(2);
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
2 | bcd
|
|
2 | yyy
|
|
(3 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE p1(2);
|
|
QUERY PLAN
|
|
----------------------------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: ((a <= 2) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2
|
|
Filter: ((a <= 2) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3
|
|
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 => aaa
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ccc
|
|
NOTICE: f_leak => ddd
|
|
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
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
3 | ccc
|
|
4 | ddd
|
|
1 | abc
|
|
2 | bcd
|
|
3 | cde
|
|
4 | def
|
|
1 | xxx
|
|
2 | yyy
|
|
3 | zzz
|
|
(11 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t2
|
|
Filter: f_leak(b)
|
|
-> Seq Scan on t3
|
|
Filter: f_leak(b)
|
|
(7 rows)
|
|
|
|
-- plan cache should be invalidated
|
|
EXECUTE p1(2);
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
1 | abc
|
|
2 | bcd
|
|
1 | xxx
|
|
2 | yyy
|
|
(6 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE p1(2);
|
|
QUERY PLAN
|
|
--------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: (a <= 2)
|
|
-> Seq Scan on t2
|
|
Filter: (a <= 2)
|
|
-> Seq Scan on t3
|
|
Filter: (a <= 2)
|
|
(7 rows)
|
|
|
|
PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
|
|
EXECUTE p2(2);
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
2 | bcd
|
|
2 | yyy
|
|
(3 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE p2(2);
|
|
QUERY PLAN
|
|
-------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: (a = 2)
|
|
-> Seq Scan on t2
|
|
Filter: (a = 2)
|
|
-> Seq Scan on t3
|
|
Filter: (a = 2)
|
|
(7 rows)
|
|
|
|
-- also, case when privilege switch from superuser
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SET row_security TO ON;
|
|
EXECUTE p2(2);
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
2 | bcd
|
|
2 | yyy
|
|
(3 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE p2(2);
|
|
QUERY PLAN
|
|
---------------------------------------------
|
|
Append
|
|
-> Seq Scan on t1
|
|
Filter: ((a = 2) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2
|
|
Filter: ((a = 2) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3
|
|
Filter: ((a = 2) AND ((a % 2) = 0))
|
|
(7 rows)
|
|
|
|
--
|
|
-- UPDATE / DELETE and Row-level security
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Update on t1 t1_3
|
|
Update on t1 t1_3
|
|
Update on t2 t1
|
|
Update on t3 t1
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_4
|
|
Filter: ((a % 2) = 0)
|
|
-> Subquery Scan on t1_1
|
|
Filter: f_leak(t1_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Subquery Scan on t1_2
|
|
Filter: f_leak(t1_2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(19 rows)
|
|
|
|
UPDATE t1 SET b = b || b WHERE f_leak(b);
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
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 t1_1
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_2
|
|
Filter: ((a % 2) = 0)
|
|
(6 rows)
|
|
|
|
UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
|
|
NOTICE: f_leak => bbbbbb
|
|
NOTICE: f_leak => dddddd
|
|
-- returning clause with system column
|
|
UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
|
|
NOTICE: f_leak => bbbbbb_updt
|
|
NOTICE: f_leak => dddddd_updt
|
|
oid | a | b | t1
|
|
-----+---+-------------+-----------------
|
|
102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
|
|
104 | 4 | dddddd_updt | (4,dddddd_updt)
|
|
(2 rows)
|
|
|
|
UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
|
|
NOTICE: f_leak => bbbbbb_updt
|
|
NOTICE: f_leak => dddddd_updt
|
|
NOTICE: f_leak => bcdbcd
|
|
NOTICE: f_leak => defdef
|
|
NOTICE: f_leak => yyyyyy
|
|
a | b
|
|
---+-------------
|
|
2 | bbbbbb_updt
|
|
4 | dddddd_updt
|
|
2 | bcdbcd
|
|
4 | defdef
|
|
2 | yyyyyy
|
|
(5 rows)
|
|
|
|
UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
|
|
NOTICE: f_leak => bbbbbb_updt
|
|
NOTICE: f_leak => dddddd_updt
|
|
NOTICE: f_leak => bcdbcd
|
|
NOTICE: f_leak => defdef
|
|
NOTICE: f_leak => yyyyyy
|
|
oid | a | b | t1
|
|
-----+---+-------------+-----------------
|
|
102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
|
|
104 | 4 | dddddd_updt | (4,dddddd_updt)
|
|
202 | 2 | bcdbcd | (2,bcdbcd)
|
|
204 | 4 | defdef | (4,defdef)
|
|
302 | 2 | yyyyyy | (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 t2_1
|
|
-> Nested Loop
|
|
-> Subquery Scan on t2
|
|
Filter: f_leak(t2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2 t2_2
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
-> Seq Scan on t3
|
|
Filter: (f_leak(b) AND (a = 2))
|
|
(9 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 => xxx
|
|
NOTICE: f_leak => zzz
|
|
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 t1_3
|
|
Update on t1 t1_3
|
|
Update on t2 t1
|
|
Update on t3 t1
|
|
-> Nested Loop
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_4
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t2
|
|
Filter: f_leak(t2.b)
|
|
-> Seq Scan on t2 t2_3
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
-> Nested Loop
|
|
-> Subquery Scan on t1_1
|
|
Filter: f_leak(t1_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2 t2_4
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t2_1
|
|
Filter: f_leak(t2_1.b)
|
|
-> Seq Scan on t2 t2_5
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
-> Nested Loop
|
|
-> Subquery Scan on t1_2
|
|
Filter: f_leak(t1_2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t3
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t2_2
|
|
Filter: f_leak(t2_2.b)
|
|
-> Seq Scan on t2 t2_6
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
(34 rows)
|
|
|
|
UPDATE t1 SET b=t1.b FROM t2
|
|
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
|
|
EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
|
|
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
|
|
QUERY PLAN
|
|
---------------------------------------------------------------------
|
|
Update on t2 t2_1
|
|
-> Nested Loop
|
|
-> Subquery Scan on t2
|
|
Filter: f_leak(t2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2 t2_2
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> Result
|
|
-> Append
|
|
-> Seq Scan on t1 t1_1
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2 t2_3
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3
|
|
Filter: ((a = 3) AND ((a % 2) = 0))
|
|
(17 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_1
|
|
-> Nested Loop
|
|
Join Filter: (t2_1.b = t2_2.b)
|
|
-> Subquery Scan on t2_1
|
|
Filter: f_leak(t2_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2 t2_1_2
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
-> Subquery Scan on t2_2
|
|
Filter: f_leak(t2_2.b)
|
|
-> Seq Scan on t2 t2_2_1
|
|
Filter: ((a = 3) AND ((a % 2) = 1))
|
|
(12 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
|
|
a | b | c | a | b | c | t2_1 | t2_2
|
|
---+-----+-----+---+-----+-----+-------------+-------------
|
|
3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (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_3
|
|
Update on t1 t1_1_3
|
|
Update on t2 t1_1
|
|
Update on t3 t1_1
|
|
-> Nested Loop
|
|
Join Filter: (t1_1.b = t1_2.b)
|
|
-> Subquery Scan on t1_1
|
|
Filter: f_leak(t1_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_1_4
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t1_2
|
|
Filter: f_leak(t1_2.b)
|
|
-> Append
|
|
-> Seq Scan on t1 t1_2_3
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2 t1_2_4
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3 t1_2_5
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Nested Loop
|
|
Join Filter: (t1_1_1.b = t1_2_1.b)
|
|
-> Subquery Scan on t1_1_1
|
|
Filter: f_leak(t1_1_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2 t1_1_5
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t1_2_1
|
|
Filter: f_leak(t1_2_1.b)
|
|
-> Append
|
|
-> Seq Scan on t1 t1_2_6
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2 t1_2_7
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3 t1_2_8
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Nested Loop
|
|
Join Filter: (t1_1_2.b = t1_2_2.b)
|
|
-> Subquery Scan on t1_1_2
|
|
Filter: f_leak(t1_1_2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t3 t1_1_6
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Subquery Scan on t1_2_2
|
|
Filter: f_leak(t1_2_2.b)
|
|
-> Append
|
|
-> Seq Scan on t1 t1_2_9
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t2 t1_2_10
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
-> Seq Scan on t3 t1_2_11
|
|
Filter: ((a = 4) AND ((a % 2) = 0))
|
|
(52 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 => dddddd_updt
|
|
NOTICE: f_leak => dddddd_updt
|
|
NOTICE: f_leak => defdef
|
|
NOTICE: f_leak => defdef
|
|
NOTICE: f_leak => dddddd_updt
|
|
NOTICE: f_leak => defdef
|
|
a | b | a | b | t1_1 | t1_2
|
|
---+-------------+---+-------------+-----------------+-----------------
|
|
4 | dddddd_updt | 4 | dddddd_updt | (4,dddddd_updt) | (4,dddddd_updt)
|
|
4 | defdef | 4 | defdef | (4,defdef) | (4,defdef)
|
|
(2 rows)
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
SELECT * FROM t1 ORDER BY a,b;
|
|
a | b
|
|
---+-------------
|
|
1 | aaa
|
|
1 | abc
|
|
1 | xxx
|
|
2 | bbbbbb_updt
|
|
2 | bcdbcd
|
|
2 | yyyyyy
|
|
3 | ccc
|
|
3 | cde
|
|
3 | zzz
|
|
4 | dddddd_updt
|
|
4 | defdef
|
|
(11 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SET row_security TO ON;
|
|
EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Delete on t1 t1_1
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_2
|
|
Filter: ((a % 2) = 0)
|
|
(6 rows)
|
|
|
|
EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Delete on t1 t1_3
|
|
Delete on t1 t1_3
|
|
Delete on t2 t1
|
|
Delete on t3 t1
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t1 t1_4
|
|
Filter: ((a % 2) = 0)
|
|
-> Subquery Scan on t1_1
|
|
Filter: f_leak(t1_1.b)
|
|
-> LockRows
|
|
-> Seq Scan on t2
|
|
Filter: ((a % 2) = 0)
|
|
-> Subquery Scan on t1_2
|
|
Filter: f_leak(t1_2.b)
|
|
-> LockRows
|
|
-> Seq Scan on t3
|
|
Filter: ((a % 2) = 0)
|
|
(19 rows)
|
|
|
|
DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
|
|
NOTICE: f_leak => bbbbbb_updt
|
|
NOTICE: f_leak => dddddd_updt
|
|
oid | a | b | t1
|
|
-----+---+-------------+-----------------
|
|
102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
|
|
104 | 4 | dddddd_updt | (4,dddddd_updt)
|
|
(2 rows)
|
|
|
|
DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
|
|
NOTICE: f_leak => bcdbcd
|
|
NOTICE: f_leak => defdef
|
|
NOTICE: f_leak => yyyyyy
|
|
oid | a | b | t1
|
|
-----+---+--------+------------
|
|
202 | 2 | bcdbcd | (2,bcdbcd)
|
|
204 | 4 | defdef | (4,defdef)
|
|
302 | 2 | yyyyyy | (2,yyyyyy)
|
|
(3 rows)
|
|
|
|
--
|
|
-- S.b. view on top of Row-level security
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE b1 (a int, b text);
|
|
INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
|
|
CREATE POLICY p1 ON b1 USING (a % 2 = 0);
|
|
ALTER TABLE b1 ENABLE ROW LEVEL SECURITY;
|
|
GRANT ALL ON b1 TO rls_regress_user1;
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
|
|
GRANT ALL ON bv1 TO rls_regress_user2;
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------------------------
|
|
Subquery Scan on bv1
|
|
Filter: f_leak(bv1.b)
|
|
-> Seq Scan on b1
|
|
Filter: ((a > 0) AND ((a % 2) = 0))
|
|
(4 rows)
|
|
|
|
SELECT * FROM bv1 WHERE f_leak(b);
|
|
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
|
|
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
|
|
a | b
|
|
----+----------------------------------
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
(5 rows)
|
|
|
|
INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
|
|
ERROR: new row violates row-level security policy for table "b1"
|
|
INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
|
|
ERROR: new row violates row-level security policy for table "b1"
|
|
INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
|
|
EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------------------------------------------------------
|
|
Update on b1 b1_1
|
|
-> Subquery Scan on b1
|
|
Filter: f_leak(b1.b)
|
|
-> Subquery Scan on b1_2
|
|
-> LockRows
|
|
-> Seq Scan on b1 b1_3
|
|
Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0))
|
|
(7 rows)
|
|
|
|
UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
|
|
QUERY PLAN
|
|
---------------------------------------------------------------------------
|
|
Delete on b1 b1_1
|
|
-> Subquery Scan on b1
|
|
Filter: f_leak(b1.b)
|
|
-> Subquery Scan on b1_2
|
|
-> LockRows
|
|
-> Seq Scan on b1 b1_3
|
|
Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0))
|
|
(7 rows)
|
|
|
|
DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
SELECT * FROM b1;
|
|
a | b
|
|
-----+----------------------------------
|
|
-10 | 1b0fd9efa5279c4203b7c70233f86dbf
|
|
-9 | 252e691406782824eec43d7eadc3d256
|
|
-8 | a8d2ec85eaf98407310b72eb73dda247
|
|
-7 | 74687a12d3915d3c4d83f1af7b3683d5
|
|
-6 | 596a3d04481816330f07e4f97510c28f
|
|
-5 | 47c1b025fa18ea96c33fbb6718688c0f
|
|
-4 | 0267aaf632e87a63288a08331f22c7c3
|
|
-3 | b3149ecea4628efd23d2f86e5a723472
|
|
-2 | 5d7b9adcbe1c629ec722529dd12e5129
|
|
-1 | 6bb61e3b7bce0931da574d19d1d82c88
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
1 | c4ca4238a0b923820dcc509a6f75849b
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
5 | e4da3b7fbbce2345d7772b0674a318d5
|
|
7 | 8f14e45fceea167a5a36dedd4bea2543
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | xxx
|
|
4 | yyy
|
|
(21 rows)
|
|
|
|
--
|
|
-- INSERT ... ON CONFLICT DO UPDATE and Row-level security
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
DROP POLICY p1 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 rls_regress_user1;
|
|
-- Exists...
|
|
SELECT * FROM document WHERE did = 2;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------
|
|
2 | 11 | 2 | rls_regress_user1 | 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, 'rls_regress_user2', '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, 'rls_regress_user1', 'okay science fiction'); -- preparation for next statement
|
|
INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user1', '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, 'rls_regress_user1', 'my first novel')
|
|
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+----------------
|
|
2 | 11 | 2 | rls_regress_user1 | 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, 'rls_regress_user1', 'some technology novel')
|
|
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
78 | 11 | 1 | rls_regress_user1 | 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, 'rls_regress_user1', 'some technology novel')
|
|
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+-----------------------
|
|
78 | 33 | 1 | rls_regress_user1 | 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, 'rls_regress_user1', '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, 'rls_regress_user1', 'technology book, can only insert')
|
|
ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
|
|
did | cid | dlevel | dauthor | dtitle
|
|
-----+-----+--------+-------------------+----------------------------------
|
|
79 | 33 | 1 | rls_regress_user1 | 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, 'rls_regress_user1', '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 rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
-- 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, 'rls_regress_user1', '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 it's 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, 'rls_regress_user1', '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 rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
-- Fails, since ALL WCO is enforced in insert path:
|
|
INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'rls_regress_user2', '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, 'rls_regress_user1', '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, 'rls_regress_user1', 'my first novel')
|
|
ON CONFLICT (did) DO UPDATE SET dauthor = 'rls_regress_user2';
|
|
ERROR: new row violates row-level security policy for table "document"
|
|
--
|
|
-- ROLE/GROUP
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE z1 (a int, b text);
|
|
CREATE TABLE z2 (a int, b text);
|
|
GRANT SELECT ON z1,z2 TO rls_regress_group1, rls_regress_group2,
|
|
rls_regress_user1, rls_regress_user2;
|
|
INSERT INTO z1 VALUES
|
|
(1, 'aaa'),
|
|
(2, 'bbb'),
|
|
(3, 'ccc'),
|
|
(4, 'ddd');
|
|
CREATE POLICY p1 ON z1 TO rls_regress_group1 USING (a % 2 = 0);
|
|
CREATE POLICY p2 ON z1 TO rls_regress_group2 USING (a % 2 = 1);
|
|
ALTER TABLE z1 ENABLE ROW LEVEL SECURITY;
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
PREPARE plancache_test2 AS WITH q AS (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
|
|
QUERY PLAN
|
|
---------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Seq Scan on z2
|
|
(9 rows)
|
|
|
|
PREPARE plancache_test3 AS WITH q AS (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
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(9 rows)
|
|
|
|
SET ROLE rls_regress_group1;
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
|
|
QUERY PLAN
|
|
---------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Seq Scan on z2
|
|
(9 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Seq Scan on z2
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(9 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
NOTICE: f_leak => aaa
|
|
NOTICE: f_leak => ccc
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
3 | ccc
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
|
|
QUERY PLAN
|
|
---------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Seq Scan on z2
|
|
(9 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Seq Scan on z2
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(9 rows)
|
|
|
|
SET ROLE rls_regress_group2;
|
|
SELECT * FROM z1 WHERE f_leak(b);
|
|
NOTICE: f_leak => aaa
|
|
NOTICE: f_leak => ccc
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
3 | ccc
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(4 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
|
|
QUERY PLAN
|
|
---------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Seq Scan on z2
|
|
(9 rows)
|
|
|
|
EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
|
|
QUERY PLAN
|
|
-------------------------------------------
|
|
Nested Loop
|
|
CTE q
|
|
-> Seq Scan on z2
|
|
-> CTE Scan on q
|
|
-> Materialize
|
|
-> Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 1)
|
|
(9 rows)
|
|
|
|
--
|
|
-- Views should follow policy for view owner.
|
|
--
|
|
-- View and Table owner are the same.
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
|
|
GRANT SELECT ON rls_view TO rls_regress_user1;
|
|
-- Query as role that is not owner of view or table. Should return all records.
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => aaa
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ccc
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
3 | ccc
|
|
4 | ddd
|
|
(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 rls_regress_user0;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => aaa
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ccc
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
1 | aaa
|
|
2 | bbb
|
|
3 | ccc
|
|
4 | ddd
|
|
(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 rls_regress_user1;
|
|
CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
|
|
GRANT SELECT ON rls_view TO rls_regress_user0;
|
|
-- 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 rls_regress_user0;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 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 rls_regress_user1;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
-- Query as role that is not the owner of the table or view without permissions.
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for relation rls_view
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
|
|
ERROR: permission denied for relation rls_view
|
|
-- Query as role that is not the owner of the table or view with permissions.
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
GRANT SELECT ON rls_view TO rls_regress_user2;
|
|
SELECT * FROM rls_view;
|
|
NOTICE: f_leak => bbb
|
|
NOTICE: f_leak => ddd
|
|
a | b
|
|
---+-----
|
|
2 | bbb
|
|
4 | ddd
|
|
(2 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Subquery Scan on z1
|
|
Filter: f_leak(z1.b)
|
|
-> Seq Scan on z1 z1_1
|
|
Filter: ((a % 2) = 0)
|
|
(4 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
DROP VIEW rls_view;
|
|
--
|
|
-- Command specific
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE x1 (a int, b text, c text);
|
|
GRANT ALL ON x1 TO PUBLIC;
|
|
INSERT INTO x1 VALUES
|
|
(1, 'abc', 'rls_regress_user1'),
|
|
(2, 'bcd', 'rls_regress_user1'),
|
|
(3, 'cde', 'rls_regress_user2'),
|
|
(4, 'def', 'rls_regress_user2'),
|
|
(5, 'efg', 'rls_regress_user1'),
|
|
(6, 'fgh', 'rls_regress_user1'),
|
|
(7, 'fgh', 'rls_regress_user2'),
|
|
(8, 'fgh', 'rls_regress_user2');
|
|
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 rls_regress_user1;
|
|
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 | rls_regress_user1
|
|
2 | bcd | rls_regress_user1
|
|
4 | def | rls_regress_user2
|
|
5 | efg | rls_regress_user1
|
|
6 | fgh | rls_regress_user1
|
|
8 | fgh | rls_regress_user2
|
|
(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 | rls_regress_user1
|
|
2 | bcd_updt | rls_regress_user1
|
|
4 | def_updt | rls_regress_user2
|
|
5 | efg_updt | rls_regress_user1
|
|
6 | fgh_updt | rls_regress_user1
|
|
8 | fgh_updt | rls_regress_user2
|
|
(6 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
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 | rls_regress_user1
|
|
3 | cde | rls_regress_user2
|
|
4 | def_updt | rls_regress_user2
|
|
6 | fgh_updt | rls_regress_user1
|
|
7 | fgh | rls_regress_user2
|
|
8 | fgh_updt | rls_regress_user2
|
|
(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 | rls_regress_user2
|
|
7 | fgh_updt | rls_regress_user2
|
|
2 | bcd_updt_updt | rls_regress_user1
|
|
4 | def_updt_updt | rls_regress_user2
|
|
6 | fgh_updt_updt | rls_regress_user1
|
|
8 | fgh_updt_updt | rls_regress_user2
|
|
(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 | rls_regress_user2
|
|
7 | fgh_updt | rls_regress_user2
|
|
2 | bcd_updt_updt | rls_regress_user1
|
|
4 | def_updt_updt | rls_regress_user2
|
|
6 | fgh_updt_updt | rls_regress_user1
|
|
8 | fgh_updt_updt | rls_regress_user2
|
|
(6 rows)
|
|
|
|
--
|
|
-- Duplicate Policy Names
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE y1 (a int, b text);
|
|
CREATE TABLE y2 (a int, b text);
|
|
GRANT ALL ON y1, y2 TO rls_regress_user1;
|
|
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 rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
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
|
|
----------------------------------------------------------
|
|
Subquery Scan on y1
|
|
Filter: f_leak(y1.b)
|
|
-> Seq Scan on y1 y1_1
|
|
Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)))
|
|
(4 rows)
|
|
|
|
DROP VIEW rls_sbv;
|
|
--
|
|
-- Expression structure
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
|
|
CREATE POLICY p2 ON y2 USING (a % 3 = 0);
|
|
CREATE POLICY p3 ON y2 USING (a % 4 = 0);
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SELECT * FROM y2 WHERE f_leak(b);
|
|
NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
|
|
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
|
|
NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
|
|
NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
|
|
NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
|
|
NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
|
|
NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
|
|
NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
|
|
NOTICE: f_leak => 98f13708210194c475687be6106a3b84
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
(14 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------------------------------------
|
|
Subquery Scan on y2
|
|
Filter: f_leak(y2.b)
|
|
-> Seq Scan on y2 y2_1
|
|
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
|
|
(4 rows)
|
|
|
|
--
|
|
-- Qual push-down of leaky functions, when not referring to table
|
|
--
|
|
SELECT * FROM y2 WHERE f_leak('abc');
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => abc
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
(14 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
|
|
QUERY PLAN
|
|
---------------------------------------------------------------------------------------
|
|
Seq Scan on y2
|
|
Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
|
|
(2 rows)
|
|
|
|
CREATE TABLE test_qual_pushdown (
|
|
abc text
|
|
);
|
|
INSERT INTO test_qual_pushdown VALUES ('abc'),('def');
|
|
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
|
|
NOTICE: f_leak => abc
|
|
NOTICE: f_leak => def
|
|
a | b | abc
|
|
---+---+-----
|
|
(0 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
|
|
QUERY PLAN
|
|
-------------------------------------------------------------------------
|
|
Hash Join
|
|
Hash Cond: (test_qual_pushdown.abc = y2.b)
|
|
-> Seq Scan on test_qual_pushdown
|
|
Filter: f_leak(abc)
|
|
-> Hash
|
|
-> Seq Scan on y2
|
|
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
|
|
(7 rows)
|
|
|
|
SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
|
|
NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
|
|
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
|
|
NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
|
|
NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
|
|
NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
|
|
NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
|
|
NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
|
|
NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
|
|
NOTICE: f_leak => 98f13708210194c475687be6106a3b84
|
|
a | b | abc
|
|
---+---+-----
|
|
(0 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
|
|
QUERY PLAN
|
|
-------------------------------------------------------------------------------
|
|
Hash Join
|
|
Hash Cond: (test_qual_pushdown.abc = y2.b)
|
|
-> Seq Scan on test_qual_pushdown
|
|
-> Hash
|
|
-> Subquery Scan on y2
|
|
Filter: f_leak(y2.b)
|
|
-> Seq Scan on y2 y2_1
|
|
Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
|
|
(8 rows)
|
|
|
|
DROP TABLE test_qual_pushdown;
|
|
--
|
|
-- Plancache invalidate on user change.
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
-- Suppress NOTICE messages when doing a cascaded drop.
|
|
SET client_min_messages TO 'warning';
|
|
DROP TABLE t1 CASCADE;
|
|
RESET client_min_messages;
|
|
CREATE TABLE t1 (a integer);
|
|
GRANT SELECT ON t1 TO rls_regress_user1, rls_regress_user2;
|
|
CREATE POLICY p1 ON t1 TO rls_regress_user1 USING ((a % 2) = 0);
|
|
CREATE POLICY p2 ON t1 TO rls_regress_user2 USING ((a % 4) = 0);
|
|
ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
|
|
SET ROLE rls_regress_user1;
|
|
PREPARE role_inval AS SELECT * FROM t1;
|
|
EXPLAIN (COSTS OFF) EXECUTE role_inval;
|
|
QUERY PLAN
|
|
-------------------------
|
|
Seq Scan on t1
|
|
Filter: ((a % 2) = 0)
|
|
(2 rows)
|
|
|
|
SET ROLE rls_regress_user2;
|
|
EXPLAIN (COSTS OFF) EXECUTE role_inval;
|
|
QUERY PLAN
|
|
-------------------------
|
|
Seq Scan on t1
|
|
Filter: ((a % 4) = 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 rls_regress_user1;
|
|
INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
|
|
NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
|
|
NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
|
|
NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
|
|
NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
|
|
NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
|
|
NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
|
|
NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
|
|
NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
|
|
NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
|
|
NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
|
|
NOTICE: f_leak => 98f13708210194c475687be6106a3b84
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
(11 rows)
|
|
|
|
EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
|
|
QUERY PLAN
|
|
---------------------------------------
|
|
CTE Scan on cte1
|
|
CTE cte1
|
|
-> Subquery Scan on t1
|
|
Filter: f_leak(t1.b)
|
|
-> Seq Scan on t1 t1_1
|
|
Filter: ((a % 2) = 0)
|
|
(6 rows)
|
|
|
|
WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
|
|
ERROR: new row violates row-level security policy for table "t1"
|
|
WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
(11 rows)
|
|
|
|
WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
|
|
ERROR: new row violates row-level security policy for table "t1"
|
|
WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
|
|
a | b
|
|
----+---------
|
|
20 | Success
|
|
(1 row)
|
|
|
|
--
|
|
-- Rename Policy
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
ALTER POLICY p1 ON t1 RENAME TO p1; --fail
|
|
ERROR: policy "p1" for table "t1" already exists
|
|
SELECT polname, relname
|
|
FROM pg_policy pol
|
|
JOIN pg_class pc ON (pc.oid = pol.polrelid)
|
|
WHERE relname = 't1';
|
|
polname | relname
|
|
---------+---------
|
|
p1 | t1
|
|
(1 row)
|
|
|
|
ALTER POLICY p1 ON t1 RENAME TO p2; --ok
|
|
SELECT polname, relname
|
|
FROM pg_policy pol
|
|
JOIN pg_class pc ON (pc.oid = pol.polrelid)
|
|
WHERE relname = 't1';
|
|
polname | relname
|
|
---------+---------
|
|
p2 | t1
|
|
(1 row)
|
|
|
|
--
|
|
-- Check INSERT SELECT
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
CREATE TABLE t2 (a integer, b text);
|
|
INSERT INTO t2 (SELECT * FROM t1);
|
|
EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
|
|
QUERY PLAN
|
|
-------------------------------
|
|
Insert on t2
|
|
-> Seq Scan on t1
|
|
Filter: ((a % 2) = 0)
|
|
(3 rows)
|
|
|
|
SELECT * FROM t2;
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
20 | Success
|
|
(12 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t2;
|
|
QUERY PLAN
|
|
----------------
|
|
Seq Scan on t2
|
|
(1 row)
|
|
|
|
CREATE TABLE t3 AS SELECT * FROM t1;
|
|
SELECT * FROM t3;
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
20 | Success
|
|
(12 rows)
|
|
|
|
SELECT * INTO t4 FROM t1;
|
|
SELECT * FROM t4;
|
|
a | b
|
|
----+----------------------------------
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
20 | Success
|
|
(12 rows)
|
|
|
|
--
|
|
-- RLS with JOIN
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE blog (id integer, author text, post text);
|
|
CREATE TABLE comment (blog_id integer, message text);
|
|
GRANT ALL ON blog, comment TO rls_regress_user1;
|
|
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 rls_regress_user1;
|
|
-- 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 rls_regress_user0;
|
|
CREATE POLICY comment_1 ON comment USING (blog_id < 4);
|
|
ALTER TABLE comment ENABLE ROW LEVEL SECURITY;
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
-- 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 rls_regress_user0;
|
|
DROP TABLE blog, comment;
|
|
--
|
|
-- Default Deny Policy
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP POLICY p2 ON t1;
|
|
ALTER TABLE t1 OWNER TO rls_regress_user0;
|
|
-- Check that default deny does not apply to superuser.
|
|
RESET SESSION AUTHORIZATION;
|
|
SELECT * FROM t1;
|
|
a | b
|
|
----+----------------------------------
|
|
1 | c4ca4238a0b923820dcc509a6f75849b
|
|
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
5 | e4da3b7fbbce2345d7772b0674a318d5
|
|
7 | 8f14e45fceea167a5a36dedd4bea2543
|
|
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
11 | 6512bd43d9caa6e02c990b0a82652dca
|
|
13 | c51ce410c124a10e0db5e4b97fc2af39
|
|
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
17 | 70efdf2ec9b086079795c442636b55fb
|
|
19 | 1f0e3dad99908345f7439f8ffabdffc4
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
20 | Success
|
|
(22 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1;
|
|
QUERY PLAN
|
|
----------------
|
|
Seq Scan on t1
|
|
(1 row)
|
|
|
|
-- Check that default deny does not apply to table owner.
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
SELECT * FROM t1;
|
|
a | b
|
|
----+----------------------------------
|
|
1 | c4ca4238a0b923820dcc509a6f75849b
|
|
3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
5 | e4da3b7fbbce2345d7772b0674a318d5
|
|
7 | 8f14e45fceea167a5a36dedd4bea2543
|
|
9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
11 | 6512bd43d9caa6e02c990b0a82652dca
|
|
13 | c51ce410c124a10e0db5e4b97fc2af39
|
|
15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
|
|
17 | 70efdf2ec9b086079795c442636b55fb
|
|
19 | 1f0e3dad99908345f7439f8ffabdffc4
|
|
0 | cfcd208495d565ef66e7dff9f98764da
|
|
2 | c81e728d9d4c2f636f067f89cc14862c
|
|
4 | a87ff679a2f3e71d9181a67b7542122c
|
|
6 | 1679091c5a880faf6fb5e6087eb1b2dc
|
|
8 | c9f0f895fb98ab9159f51fd0297e236d
|
|
10 | d3d9446802a44259755d38e6d163e820
|
|
12 | c20ad4d76fe97759aa27a0c99bff6710
|
|
14 | aab3238922bcc25a6f606eb525ffdc56
|
|
16 | c74d97b01eae257e44aa9d5bade97baf
|
|
18 | 6f4922f45568161a8cdf4ad2299f6d23
|
|
20 | 98f13708210194c475687be6106a3b84
|
|
20 | Success
|
|
(22 rows)
|
|
|
|
EXPLAIN (COSTS OFF) SELECT * FROM t1;
|
|
QUERY PLAN
|
|
----------------
|
|
Seq Scan on t1
|
|
(1 row)
|
|
|
|
-- Check that default deny applies to non-owner/non-superuser when RLS on.
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user1, rls_regress_exempt_user;
|
|
INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);
|
|
-- Check COPY TO as Superuser/owner.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
|
|
0,cfcd208495d565ef66e7dff9f98764da
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
2,c81e728d9d4c2f636f067f89cc14862c
|
|
3,eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4,a87ff679a2f3e71d9181a67b7542122c
|
|
5,e4da3b7fbbce2345d7772b0674a318d5
|
|
6,1679091c5a880faf6fb5e6087eb1b2dc
|
|
7,8f14e45fceea167a5a36dedd4bea2543
|
|
8,c9f0f895fb98ab9159f51fd0297e236d
|
|
9,45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10,d3d9446802a44259755d38e6d163e820
|
|
SET row_security TO ON;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
|
|
0,cfcd208495d565ef66e7dff9f98764da
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
2,c81e728d9d4c2f636f067f89cc14862c
|
|
3,eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4,a87ff679a2f3e71d9181a67b7542122c
|
|
5,e4da3b7fbbce2345d7772b0674a318d5
|
|
6,1679091c5a880faf6fb5e6087eb1b2dc
|
|
7,8f14e45fceea167a5a36dedd4bea2543
|
|
8,c9f0f895fb98ab9159f51fd0297e236d
|
|
9,45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10,d3d9446802a44259755d38e6d163e820
|
|
-- Check COPY TO as user with permissions.
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
SET row_security TO OFF;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
|
|
ERROR: query would be affected by row-level security policy for table "copy_t"
|
|
SET row_security TO ON;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
|
|
0,cfcd208495d565ef66e7dff9f98764da
|
|
2,c81e728d9d4c2f636f067f89cc14862c
|
|
4,a87ff679a2f3e71d9181a67b7542122c
|
|
6,1679091c5a880faf6fb5e6087eb1b2dc
|
|
8,c9f0f895fb98ab9159f51fd0297e236d
|
|
10,d3d9446802a44259755d38e6d163e820
|
|
-- Check COPY TO as user with permissions and BYPASSRLS
|
|
SET SESSION AUTHORIZATION rls_regress_exempt_user;
|
|
SET row_security TO OFF;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
|
|
0,cfcd208495d565ef66e7dff9f98764da
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
2,c81e728d9d4c2f636f067f89cc14862c
|
|
3,eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4,a87ff679a2f3e71d9181a67b7542122c
|
|
5,e4da3b7fbbce2345d7772b0674a318d5
|
|
6,1679091c5a880faf6fb5e6087eb1b2dc
|
|
7,8f14e45fceea167a5a36dedd4bea2543
|
|
8,c9f0f895fb98ab9159f51fd0297e236d
|
|
9,45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10,d3d9446802a44259755d38e6d163e820
|
|
SET row_security TO ON;
|
|
COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
|
|
0,cfcd208495d565ef66e7dff9f98764da
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
2,c81e728d9d4c2f636f067f89cc14862c
|
|
3,eccbc87e4b5ce2fe28308fd9f2a7baf3
|
|
4,a87ff679a2f3e71d9181a67b7542122c
|
|
5,e4da3b7fbbce2345d7772b0674a318d5
|
|
6,1679091c5a880faf6fb5e6087eb1b2dc
|
|
7,8f14e45fceea167a5a36dedd4bea2543
|
|
8,c9f0f895fb98ab9159f51fd0297e236d
|
|
9,45c48cce2e2d7fbdea1afc51c7c6ad26
|
|
10,d3d9446802a44259755d38e6d163e820
|
|
-- Check COPY TO as user without permissions. SET row_security TO OFF;
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
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 relation 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 rls_regress_user1, rls_regress_exempt_user;
|
|
INSERT INTO copy_rel_to VALUES (1, md5('1'));
|
|
-- Check COPY TO as Superuser/owner.
|
|
RESET SESSION AUTHORIZATION;
|
|
SET row_security TO OFF;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
-- Check COPY TO as user with permissions.
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
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 rls_regress_exempt_user;
|
|
SET row_security TO OFF;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
|
|
1,c4ca4238a0b923820dcc509a6f75849b
|
|
-- Check COPY TO as user without permissions. SET row_security TO OFF;
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
SET row_security TO OFF;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
|
|
ERROR: permission denied for relation copy_rel_to
|
|
SET row_security TO ON;
|
|
COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
|
|
ERROR: permission denied for relation 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 rls_regress_user1;
|
|
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 rls_regress_exempt_user;
|
|
SET row_security TO ON;
|
|
COPY copy_t FROM STDIN; --ok
|
|
-- Check COPY FROM as user without permissions.
|
|
SET SESSION AUTHORIZATION rls_regress_user2;
|
|
SET row_security TO OFF;
|
|
COPY copy_t FROM STDIN; --fail - permission denied.
|
|
ERROR: permission denied for relation copy_t
|
|
SET row_security TO ON;
|
|
COPY copy_t FROM STDIN; --fail - permission denied.
|
|
ERROR: permission denied for relation copy_t
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP TABLE copy_t;
|
|
DROP TABLE copy_rel_to CASCADE;
|
|
-- Check WHERE CURRENT OF
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
CREATE TABLE current_check (currentid int, payload text, rlsuser text);
|
|
GRANT ALL ON current_check TO PUBLIC;
|
|
INSERT INTO current_check VALUES
|
|
(1, 'abc', 'rls_regress_user1'),
|
|
(2, 'bcd', 'rls_regress_user1'),
|
|
(3, 'cde', 'rls_regress_user1'),
|
|
(4, 'def', 'rls_regress_user1');
|
|
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 rls_regress_user1;
|
|
-- Can SELECT even rows
|
|
SELECT * FROM current_check;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
2 | bcd | rls_regress_user1
|
|
4 | def | rls_regress_user1
|
|
(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 | rls_regress_user1
|
|
(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 | rls_regress_user1
|
|
(1 row)
|
|
|
|
UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
4 | def_new | rls_regress_user1
|
|
(1 row)
|
|
|
|
SELECT * FROM current_check;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
2 | bcd | rls_regress_user1
|
|
4 | def_new | rls_regress_user1
|
|
(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 current_check_1
|
|
-> Subquery Scan on current_check
|
|
-> Subquery Scan on current_check_2
|
|
Filter: ((current_check_2.currentid % 2) = 0)
|
|
-> LockRows
|
|
-> Tid Scan on current_check current_check_3
|
|
TID Cond: CURRENT OF current_check_cursor
|
|
Filter: (currentid = 4)
|
|
(8 rows)
|
|
|
|
-- Similarly can only delete row 4
|
|
FETCH ABSOLUTE 1 FROM current_check_cursor;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
2 | bcd | rls_regress_user1
|
|
(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 | rls_regress_user1
|
|
(1 row)
|
|
|
|
DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
4 | def_new | rls_regress_user1
|
|
(1 row)
|
|
|
|
SELECT * FROM current_check;
|
|
currentid | payload | rlsuser
|
|
-----------+---------+-------------------
|
|
2 | bcd | rls_regress_user1
|
|
(1 row)
|
|
|
|
COMMIT;
|
|
--
|
|
-- check pg_stats view filtering
|
|
--
|
|
SET row_security TO ON;
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 | {rls_regress_user1}
|
|
(3 rows)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user1;
|
|
-- 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 rls_regress_user0;
|
|
SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
|
|
inputcollid
|
|
------------------
|
|
inputcollid 950
|
|
(1 row)
|
|
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
SELECT * FROM coll_t;
|
|
c
|
|
-----
|
|
bar
|
|
(1 row)
|
|
|
|
ROLLBACK;
|
|
--
|
|
-- Shared Object Dependencies
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
BEGIN;
|
|
CREATE ROLE alice;
|
|
CREATE ROLE bob;
|
|
CREATE TABLE tbl1 (c) AS VALUES ('bar'::text);
|
|
GRANT SELECT ON TABLE tbl1 TO alice;
|
|
CREATE POLICY P ON tbl1 TO alice, bob 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 ('alice'::regrole, 'bob'::regrole);
|
|
refclassid | deptype
|
|
------------+---------
|
|
pg_authid | r
|
|
pg_authid | r
|
|
(2 rows)
|
|
|
|
SAVEPOINT q;
|
|
DROP ROLE alice; --fails due to dependency on POLICY p
|
|
ERROR: role "alice" cannot be dropped because some objects depend on it
|
|
DETAIL: target of policy p on table tbl1
|
|
privileges for table tbl1
|
|
ROLLBACK TO q;
|
|
ALTER POLICY p ON tbl1 TO bob USING (true);
|
|
SAVEPOINT q;
|
|
DROP ROLE alice; --fails due to dependency on GRANT SELECT
|
|
ERROR: role "alice" cannot be dropped because some objects depend on it
|
|
DETAIL: privileges for table tbl1
|
|
ROLLBACK TO q;
|
|
REVOKE ALL ON TABLE tbl1 FROM alice;
|
|
SAVEPOINT q;
|
|
DROP ROLE alice; --succeeds
|
|
ROLLBACK TO q;
|
|
SAVEPOINT q;
|
|
DROP ROLE bob; --fails due to dependency on POLICY p
|
|
ERROR: role "bob" 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 bob; -- succeeds
|
|
ROLLBACK TO q;
|
|
ROLLBACK; -- cleanup
|
|
--
|
|
-- Converting table to view
|
|
--
|
|
BEGIN;
|
|
CREATE TABLE t (c int);
|
|
CREATE POLICY p ON t USING (c % 2 = 1);
|
|
ALTER TABLE t ENABLE ROW LEVEL SECURITY;
|
|
SAVEPOINT q;
|
|
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
|
|
SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled
|
|
ERROR: could not convert table "t" to a view because it has row security enabled
|
|
ROLLBACK TO q;
|
|
ALTER TABLE t DISABLE ROW LEVEL SECURITY;
|
|
SAVEPOINT q;
|
|
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
|
|
SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t
|
|
ERROR: could not convert table "t" to a view because it has row security policies
|
|
ROLLBACK TO q;
|
|
DROP POLICY p ON t;
|
|
CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
|
|
SELECT * FROM generate_series(1,5) t0(c); -- succeeds
|
|
ROLLBACK;
|
|
--
|
|
-- Policy expression handling
|
|
--
|
|
BEGIN;
|
|
CREATE TABLE t (c) AS VALUES ('bar'::text);
|
|
CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions
|
|
ERROR: aggregate functions are not allowed in policy expressions
|
|
ROLLBACK;
|
|
--
|
|
-- Non-target relations are only subject to SELECT policies
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user1;
|
|
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 rls_regress_user0;
|
|
DROP TABLE r1;
|
|
DROP TABLE r2;
|
|
--
|
|
-- FORCE ROW LEVEL SECURITY applies RLS to owners too
|
|
--
|
|
SET SESSION AUTHORIZATION rls_regress_user0;
|
|
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 rls_regress_user0;
|
|
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 rls_regress_user0;
|
|
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 rls_regress_user0;
|
|
SET row_security = on;
|
|
CREATE TABLE r1 (a int);
|
|
CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);
|
|
CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) 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"
|
|
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 rls_regress_user1 USING (c1 > (select max(dep2.c1) from dep2));
|
|
ALTER POLICY dep_p1 ON dep1 TO rls_regress_user1,rls_regress_user2;
|
|
-- 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 = 'rls_regress_user1');
|
|
?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 = 'rls_regress_user2');
|
|
?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 dob_role1;
|
|
CREATE ROLE dob_role2;
|
|
CREATE TABLE dob_t1 (c1 int);
|
|
CREATE POLICY p1 ON dob_t1 TO dob_role1 USING (true);
|
|
DROP OWNED BY 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 dob_role1,dob_role2 USING (true);
|
|
DROP OWNED BY dob_role1;
|
|
DROP POLICY p1 ON dob_t1; -- should succeed
|
|
DROP USER dob_role1;
|
|
DROP USER dob_role2;
|
|
--
|
|
-- Clean up objects
|
|
--
|
|
RESET SESSION AUTHORIZATION;
|
|
-- Suppress NOTICE messages when doing a cascaded drop.
|
|
SET client_min_messages TO 'warning';
|
|
DROP SCHEMA rls_regress_schema CASCADE;
|
|
RESET client_min_messages;
|
|
DROP USER rls_regress_user0;
|
|
DROP USER rls_regress_user1;
|
|
DROP USER rls_regress_user2;
|
|
DROP USER rls_regress_exempt_user;
|
|
DROP ROLE rls_regress_group1;
|
|
DROP ROLE rls_regress_group2;
|
|
-- Arrange to have a few policies left over, for testing
|
|
-- pg_dump/pg_restore
|
|
CREATE SCHEMA rls_regress_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);
|