<entry>Check new row</entry>
<entry>—</entry>
</row>
+ <row>
+ <entry><command>UPDATE ... FOR PORTION OF</command></entry>
+ <entry>
+ Filter existing row & check new row
+ </entry>
+ <entry>
+ Check leftover rows <footnote id="rls-fpo-leftover">
+ <para>
+ <literal>FOR PORTION OF</literal> re-inserts the portions of the
+ affected row that fall outside the targeted range, to preserve them.
+ These leftover rows are checked against the <command>INSERT</command>
+ policy's <literal>WITH CHECK</literal> expression, even though no
+ <literal>INSERT</literal> privilege is required to store them.
+ </para>
+ </footnote>
+ </entry>
+ <entry>Filter existing row</entry>
+ <entry>Check new row</entry>
+ <entry>—</entry>
+ </row>
<row>
<entry><command>DELETE</command></entry>
<entry>
<entry>—</entry>
<entry>Filter existing row</entry>
</row>
+ <row>
+ <entry><command>DELETE ... FOR PORTION OF</command></entry>
+ <entry>
+ Filter existing row
+ </entry>
+ <entry>
+ Check leftover rows <footnoteref linkend="rls-fpo-leftover"/>
+ </entry>
+ <entry>—</entry>
+ <entry>—</entry>
+ <entry>Filter existing row</entry>
+ </row>
<row>
<entry><command>INSERT ... ON CONFLICT</command></entry>
<entry>
When <literal>FOR PORTION OF</literal> is used, the secondary inserts do
not require <literal>INSERT</literal> privilege on the table. (This is
because conceptually no new information is being added; the inserted rows
- only preserve existing data about the untargeted time period.)
+ only preserve existing data about the untargeted time period.) Row-level
+ security INSERT policies are still checked for these leftover inserts.
</para>
</refsect1>
When <literal>FOR PORTION OF</literal> is used, the secondary inserts do
not require <literal>INSERT</literal> privilege on the table. (This is
because conceptually no new information is being added; the inserted rows
- only preserve existing data about the untargeted time period.)
+ only preserve existing data about the untargeted time period.) Row-level
+ security INSERT policies are still checked for these leftover inserts.
</para>
</refsect1>
}
}
+ /*
+ * UPDATE/DELETE FOR PORTION OF may insert leftover rows to preserve the
+ * portions of the old row not covered by the target range. Those hidden
+ * inserts go through ExecInsert(), so they need the same INSERT RLS WITH
+ * CHECK options as ordinary INSERTs. SELECT rights are never needed for
+ * the leftover rows, because they are not considered by RETURNING.
+ */
+ if (root->forPortionOf != NULL && rt_index == root->resultRelation &&
+ (commandType == CMD_UPDATE || commandType == CMD_DELETE))
+ {
+ List *insert_permissive_policies;
+ List *insert_restrictive_policies;
+
+ get_policies_for_relation(rel, CMD_INSERT, user_id,
+ &insert_permissive_policies,
+ &insert_restrictive_policies);
+ add_with_check_options(rel, rt_index,
+ WCO_RLS_INSERT_CHECK,
+ insert_permissive_policies,
+ insert_restrictive_policies,
+ withCheckOptions,
+ hasSubLinks,
+ false);
+ }
+
/*
* FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL)
* and set them up so that we can enforce the appropriate policy depending
(1 row)
DROP TABLE fpo_cursed;
+-- UPDATE/DELETE FOR PORTION OF leftover rows must satisfy RLS INSERT checks.
+CREATE ROLE regress_fpo_rls;
+CREATE TABLE fpo_rls (
+ id int,
+ valid_at int4range
+);
+ALTER TABLE fpo_rls ENABLE ROW LEVEL SECURITY;
+CREATE POLICY fpo_rls_select ON fpo_rls
+ FOR SELECT TO regress_fpo_rls
+ USING (true);
+CREATE POLICY fpo_rls_update ON fpo_rls
+ FOR UPDATE TO regress_fpo_rls
+ USING (lower(valid_at) < 50)
+ WITH CHECK (lower(valid_at) < 50);
+CREATE POLICY fpo_rls_delete ON fpo_rls
+ FOR DELETE TO regress_fpo_rls
+ USING (lower(valid_at) < 50);
+CREATE POLICY fpo_rls_insert ON fpo_rls
+ FOR INSERT TO regress_fpo_rls
+ WITH CHECK (lower(valid_at) < 50);
+GRANT SELECT, UPDATE, DELETE ON fpo_rls TO regress_fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+UPDATE fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 100
+ SET id = 2;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+ id | valid_at
+----+----------
+ 1 | [10,30)
+ 2 | [30,100)
+(2 rows)
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+DELETE FROM fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 100;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+ id | valid_at
+----+----------
+ 1 | [10,30)
+(1 row)
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+UPDATE fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+ERROR: new row violates row-level security policy for table "fpo_rls"
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+ id | valid_at
+----+----------
+ 1 | [10,100)
+(1 row)
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+DELETE FROM fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 70;
+ERROR: new row violates row-level security policy for table "fpo_rls"
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+ id | valid_at
+----+----------
+ 1 | [10,100)
+(1 row)
+
+DROP TABLE fpo_rls;
+DROP ROLE regress_fpo_rls;
RESET datestyle;
WITH CHECK (rls_test_policy_fn('UPDATE CHECK on rls_test_tgt', rls_test_tgt));
CREATE POLICY del_pol ON rls_test_tgt FOR DELETE
USING (rls_test_policy_fn('DELETE USING on rls_test_tgt', rls_test_tgt));
+-- setup temporal target table (for FOR PORTION OF operations)
+CREATE TABLE rls_test_fpo_tgt (a int, b text, valid_at int4range);
+ALTER TABLE rls_test_fpo_tgt ENABLE ROW LEVEL SECURITY;
+GRANT SELECT, UPDATE, DELETE ON rls_test_fpo_tgt TO public;
+INSERT INTO rls_test_fpo_tgt VALUES (1, 'fpo a', '[1,10)');
+CREATE POLICY sel_pol ON rls_test_fpo_tgt FOR SELECT
+ USING (rls_test_policy_fn('SELECT USING on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY ins_pol ON rls_test_fpo_tgt FOR INSERT
+ WITH CHECK (rls_test_policy_fn('INSERT CHECK on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY upd_pol ON rls_test_fpo_tgt FOR UPDATE
+ USING (rls_test_policy_fn('UPDATE USING on rls_test_fpo_tgt', rls_test_fpo_tgt))
+ WITH CHECK (rls_test_policy_fn('UPDATE CHECK on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY del_pol ON rls_test_fpo_tgt FOR DELETE
+ USING (rls_test_policy_fn('DELETE USING on rls_test_fpo_tgt', rls_test_fpo_tgt));
-- test policies applied to regress_rls_bob
SET SESSION AUTHORIZATION regress_rls_bob;
-- SELECT, COPY ... TO, and TABLE should only apply SELECT USING policy clause
1 | tgt d | TGT D
(1 row)
+-- UPDATE ... FOR PORTION OF should also apply SELECT USING policy clauses to
+-- the old and new rows, and INSERT CHECK policy clauses to leftover rows.
+BEGIN;
+UPDATE rls_test_fpo_tgt
+ FOR PORTION OF valid_at FROM 3 TO 7
+ SET b = 'fpo b';
+NOTICE: UPDATE USING on rls_test_fpo_tgt.(1,"fpo a","[1,10)")
+NOTICE: SELECT USING on rls_test_fpo_tgt.(1,"fpo a","[1,10)")
+NOTICE: UPDATE CHECK on rls_test_fpo_tgt.(1,"fpo b","[3,7)")
+NOTICE: SELECT USING on rls_test_fpo_tgt.(1,"fpo b","[3,7)")
+NOTICE: INSERT CHECK on rls_test_fpo_tgt.(1,"fpo a","[1,3)")
+NOTICE: INSERT CHECK on rls_test_fpo_tgt.(1,"fpo a","[7,10)")
+ROLLBACK;
-- DELETE without WHERE or RETURNING should only apply DELETE USING policy clause
BEGIN; DELETE FROM rls_test_tgt; ROLLBACK;
NOTICE: DELETE USING on rls_test_tgt.(1,"tgt d","TGT D")
1 | tgt d | TGT D
(1 row)
+-- DELETE ... FOR PORTION OF should also apply SELECT USING policy clauses to
+-- the old row, and INSERT CHECK policy clauses to leftover rows.
+BEGIN;
+DELETE FROM rls_test_fpo_tgt
+ FOR PORTION OF valid_at FROM 3 TO 7;
+NOTICE: DELETE USING on rls_test_fpo_tgt.(1,"fpo a","[1,10)")
+NOTICE: SELECT USING on rls_test_fpo_tgt.(1,"fpo a","[1,10)")
+NOTICE: INSERT CHECK on rls_test_fpo_tgt.(1,"fpo a","[1,3)")
+NOTICE: INSERT CHECK on rls_test_fpo_tgt.(1,"fpo a","[7,10)")
+ROLLBACK;
-- INSERT ... ON CONFLICT DO NOTHING with an arbiter clause should apply
-- INSERT CHECK and SELECT USING policy clauses (to new value, whether it
-- conflicts or not)
-- Tidy up
RESET SESSION AUTHORIZATION;
-DROP TABLE rls_test_src, rls_test_tgt;
+DROP TABLE rls_test_src, rls_test_tgt, rls_test_fpo_tgt;
DROP FUNCTION rls_test_tgt_set_c;
DROP FUNCTION rls_test_policy_fn;
-- BASIC Row-Level Security Scenario
SELECT * FROM fpo_cursed;
DROP TABLE fpo_cursed;
+-- UPDATE/DELETE FOR PORTION OF leftover rows must satisfy RLS INSERT checks.
+CREATE ROLE regress_fpo_rls;
+CREATE TABLE fpo_rls (
+ id int,
+ valid_at int4range
+);
+ALTER TABLE fpo_rls ENABLE ROW LEVEL SECURITY;
+CREATE POLICY fpo_rls_select ON fpo_rls
+ FOR SELECT TO regress_fpo_rls
+ USING (true);
+CREATE POLICY fpo_rls_update ON fpo_rls
+ FOR UPDATE TO regress_fpo_rls
+ USING (lower(valid_at) < 50)
+ WITH CHECK (lower(valid_at) < 50);
+CREATE POLICY fpo_rls_delete ON fpo_rls
+ FOR DELETE TO regress_fpo_rls
+ USING (lower(valid_at) < 50);
+CREATE POLICY fpo_rls_insert ON fpo_rls
+ FOR INSERT TO regress_fpo_rls
+ WITH CHECK (lower(valid_at) < 50);
+GRANT SELECT, UPDATE, DELETE ON fpo_rls TO regress_fpo_rls;
+
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+UPDATE fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 100
+ SET id = 2;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+DELETE FROM fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 100;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+UPDATE fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+
+TRUNCATE fpo_rls;
+INSERT INTO fpo_rls VALUES (1, '[10,100)');
+SET ROLE regress_fpo_rls;
+DELETE FROM fpo_rls
+ FOR PORTION OF valid_at FROM 30 TO 70;
+RESET ROLE;
+SELECT * FROM fpo_rls ORDER BY valid_at;
+
+DROP TABLE fpo_rls;
+DROP ROLE regress_fpo_rls;
+
RESET datestyle;
CREATE POLICY del_pol ON rls_test_tgt FOR DELETE
USING (rls_test_policy_fn('DELETE USING on rls_test_tgt', rls_test_tgt));
+-- setup temporal target table (for FOR PORTION OF operations)
+CREATE TABLE rls_test_fpo_tgt (a int, b text, valid_at int4range);
+ALTER TABLE rls_test_fpo_tgt ENABLE ROW LEVEL SECURITY;
+GRANT SELECT, UPDATE, DELETE ON rls_test_fpo_tgt TO public;
+INSERT INTO rls_test_fpo_tgt VALUES (1, 'fpo a', '[1,10)');
+
+CREATE POLICY sel_pol ON rls_test_fpo_tgt FOR SELECT
+ USING (rls_test_policy_fn('SELECT USING on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY ins_pol ON rls_test_fpo_tgt FOR INSERT
+ WITH CHECK (rls_test_policy_fn('INSERT CHECK on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY upd_pol ON rls_test_fpo_tgt FOR UPDATE
+ USING (rls_test_policy_fn('UPDATE USING on rls_test_fpo_tgt', rls_test_fpo_tgt))
+ WITH CHECK (rls_test_policy_fn('UPDATE CHECK on rls_test_fpo_tgt', rls_test_fpo_tgt));
+CREATE POLICY del_pol ON rls_test_fpo_tgt FOR DELETE
+ USING (rls_test_policy_fn('DELETE USING on rls_test_fpo_tgt', rls_test_fpo_tgt));
+
-- test policies applied to regress_rls_bob
SET SESSION AUTHORIZATION regress_rls_bob;
UPDATE rls_test_tgt SET b = 'tgt c' WHERE a = 1;
UPDATE rls_test_tgt SET b = 'tgt d' RETURNING *;
+-- UPDATE ... FOR PORTION OF should also apply SELECT USING policy clauses to
+-- the old and new rows, and INSERT CHECK policy clauses to leftover rows.
+BEGIN;
+UPDATE rls_test_fpo_tgt
+ FOR PORTION OF valid_at FROM 3 TO 7
+ SET b = 'fpo b';
+ROLLBACK;
+
-- DELETE without WHERE or RETURNING should only apply DELETE USING policy clause
BEGIN; DELETE FROM rls_test_tgt; ROLLBACK;
BEGIN; DELETE FROM rls_test_tgt WHERE a = 1; ROLLBACK;
DELETE FROM rls_test_tgt RETURNING *;
+-- DELETE ... FOR PORTION OF should also apply SELECT USING policy clauses to
+-- the old row, and INSERT CHECK policy clauses to leftover rows.
+BEGIN;
+DELETE FROM rls_test_fpo_tgt
+ FOR PORTION OF valid_at FROM 3 TO 7;
+ROLLBACK;
+
-- INSERT ... ON CONFLICT DO NOTHING with an arbiter clause should apply
-- INSERT CHECK and SELECT USING policy clauses (to new value, whether it
-- conflicts or not)
-- Tidy up
RESET SESSION AUTHORIZATION;
-DROP TABLE rls_test_src, rls_test_tgt;
+DROP TABLE rls_test_src, rls_test_tgt, rls_test_fpo_tgt;
DROP FUNCTION rls_test_tgt_set_c;
DROP FUNCTION rls_test_policy_fn;