* UPDATE/DELETE RLS policies. If those checks fail, we throw an
* error.
*
- * The WITH CHECK quals are applied in ExecUpdate() and hence we need
- * not do anything special to handle them.
+ * The WITH CHECK quals for UPDATE RLS policies are applied in
+ * ExecUpdateAct() and hence we need not do anything special to handle
+ * them.
*
* NOTE: We must do this after WHEN quals are evaluated, so that we
* check policies only when they matter.
*/
- if (resultRelInfo->ri_WithCheckOptions)
+ if (resultRelInfo->ri_WithCheckOptions && commandType != CMD_NOTHING)
{
ExecWithCheckOptions(commandType == CMD_UPDATE ?
WCO_RLS_MERGE_UPDATE_CHECK : WCO_RLS_MERGE_DELETE_CHECK,
* and set them up so that we can enforce the appropriate policy depending
* on the final action we take.
*
- * We already fetched the SELECT policies above.
+ * We already fetched the SELECT policies above, to check existing rows,
+ * but we must also check that new rows created by UPDATE actions are
+ * visible, if SELECT rights are required for this relation. We don't do
+ * this for INSERT actions, since an INSERT command would only do this
+ * check if it had a RETURNING list, and MERGE does not support RETURNING.
*
* We don't push the UPDATE/DELETE USING quals to the RTE because we don't
* really want to apply them while scanning the relation since we don't
*/
if (commandType == CMD_MERGE)
{
- List *merge_permissive_policies;
- List *merge_restrictive_policies;
+ List *merge_update_permissive_policies;
+ List *merge_update_restrictive_policies;
+ List *merge_delete_permissive_policies;
+ List *merge_delete_restrictive_policies;
+ List *merge_insert_permissive_policies;
+ List *merge_insert_restrictive_policies;
/*
* Fetch the UPDATE policies and set them up to execute on the
* existing target row before doing UPDATE.
*/
get_policies_for_relation(rel, CMD_UPDATE, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
+ &merge_update_permissive_policies,
+ &merge_update_restrictive_policies);
/*
* WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on
*/
add_with_check_options(rel, rt_index,
WCO_RLS_MERGE_UPDATE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
+ merge_update_permissive_policies,
+ merge_update_restrictive_policies,
withCheckOptions,
hasSubLinks,
true);
+ /* Enforce the WITH CHECK clauses of the UPDATE policies */
+ add_with_check_options(rel, rt_index,
+ WCO_RLS_UPDATE_CHECK,
+ merge_update_permissive_policies,
+ merge_update_restrictive_policies,
+ withCheckOptions,
+ hasSubLinks,
+ false);
+
+ /*
+ * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure
+ * that the updated row is visible when executing an UPDATE action, if
+ * SELECT rights are required for this relation.
+ */
+ if (perminfo->requiredPerms & ACL_SELECT)
+ {
+ List *merge_select_permissive_policies;
+ List *merge_select_restrictive_policies;
+
+ get_policies_for_relation(rel, CMD_SELECT, user_id,
+ &merge_select_permissive_policies,
+ &merge_select_restrictive_policies);
+ add_with_check_options(rel, rt_index,
+ WCO_RLS_UPDATE_CHECK,
+ merge_select_permissive_policies,
+ merge_select_restrictive_policies,
+ withCheckOptions,
+ hasSubLinks,
+ true);
+ }
+
/*
- * Same with DELETE policies.
+ * Fetch the DELETE policies and set them up to execute on the
+ * existing target row before doing DELETE.
*/
get_policies_for_relation(rel, CMD_DELETE, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
+ &merge_delete_permissive_policies,
+ &merge_delete_restrictive_policies);
+ /*
+ * WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on
+ * the existing target row.
+ */
add_with_check_options(rel, rt_index,
WCO_RLS_MERGE_DELETE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
+ merge_delete_permissive_policies,
+ merge_delete_restrictive_policies,
withCheckOptions,
hasSubLinks,
true);
* withCheckOptions.
*/
get_policies_for_relation(rel, CMD_INSERT, user_id,
- &merge_permissive_policies,
- &merge_restrictive_policies);
+ &merge_insert_permissive_policies,
+ &merge_insert_restrictive_policies);
add_with_check_options(rel, rt_index,
WCO_RLS_INSERT_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
- withCheckOptions,
- hasSubLinks,
- false);
-
- /* Enforce the WITH CHECK clauses of the UPDATE policies */
- add_with_check_options(rel, rt_index,
- WCO_RLS_UPDATE_CHECK,
- merge_permissive_policies,
- merge_restrictive_policies,
+ merge_insert_permissive_policies,
+ merge_insert_restrictive_policies,
withCheckOptions,
hasSubLinks,
false);
CREATE POLICY p1 ON document FOR SELECT USING (true);
-- one may insert documents only authored by them
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
--- one may only update documents in 'novel' category
+-- one may only update documents in 'novel' category and new dlevel must be > 0
CREATE POLICY p3 ON document FOR UPDATE
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
- WITH CHECK (dauthor = current_user);
+ WITH CHECK (dlevel > 0);
-- one may only delete documents in 'manga' category
CREATE POLICY p4 ON document FOR DELETE
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
(14 rows)
SET SESSION AUTHORIZATION regress_rls_bob;
--- Fails, since update violates WITH CHECK qual on dauthor
+-- Fails, since update violates WITH CHECK qual on dlevel
MERGE INTO document d
USING (SELECT 1 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
- UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
+ UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dlevel = 0;
ERROR: new row violates row-level security policy for table "document"
-- Should be OK since USING and WITH CHECK quals pass
MERGE INTO document d
ON did = s.sdid
WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
--- Even when dauthor is updated explicitly, but to the existing value
+-- Even when dlevel is updated explicitly, but to the existing value
MERGE INTO document d
USING (SELECT 1 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
- UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
+ UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dlevel = 1;
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
-- updating an item in category 'science fiction'
MERGE INTO document d
WHEN MATCHED THEN
DELETE;
ERROR: target row violates row-level security policy (USING expression) for table "document"
+-- OK if DELETE is replaced with DO NOTHING
+MERGE INTO document d
+USING (SELECT 4 as sdid) s
+ON did = s.sdid
+WHEN MATCHED AND dnotes <> '' THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge '
+WHEN MATCHED THEN
+ DO NOTHING;
SELECT * FROM document WHERE did = 4;
did | cid | dlevel | dauthor | dtitle | dnotes
-----+-----+--------+-----------------+----------------+--------
WHEN NOT MATCHED THEN
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-- drop and create a new SELECT policy which prevents us from reading
--- any document except with category 'magna'
+-- any document except with category 'novel'
RESET SESSION AUTHORIZATION;
DROP POLICY p1 ON document;
CREATE POLICY p1 ON document FOR SELECT
- USING (cid = (SELECT cid from category WHERE cname = 'manga'));
+ USING (cid = (SELECT cid from category WHERE cname = 'novel'));
SET SESSION AUTHORIZATION regress_rls_bob;
-- MERGE can no longer see the matching row and hence attempts the
-- NOT MATCHED action, which results in unique key violation
MERGE INTO document d
-USING (SELECT 1 as sdid) s
+USING (SELECT 7 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
WHEN NOT MATCHED THEN
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
ERROR: duplicate key value violates unique constraint "document_pkey"
+-- UPDATE action fails if new row is not visible
+MERGE INTO document d
+USING (SELECT 1 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge6 ',
+ cid = (SELECT cid from category WHERE cname = 'technology');
+ERROR: new row violates row-level security policy for table "document"
+-- but OK if new row is visible
+MERGE INTO document d
+USING (SELECT 1 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge7 ',
+ cid = (SELECT cid from category WHERE cname = 'novel');
+-- OK to insert a new row that is not visible
+MERGE INTO document d
+USING (SELECT 13 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge8 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga');
RESET SESSION AUTHORIZATION;
-- drop the restrictive SELECT policy so that we can look at the
-- final state of the table
DROP POLICY p1 ON document;
-- Just check everything went per plan
SELECT * FROM document;
- did | cid | dlevel | dauthor | dtitle | dnotes
------+-----+--------+-------------------+----------------------------------+-----------------------------------------------------------------------
+ did | cid | dlevel | dauthor | dtitle | dnotes
+-----+-----+--------+-------------------+----------------------------------+----------------------------------------------------------------------------------------------
3 | 22 | 2 | regress_rls_bob | my science fiction |
5 | 44 | 2 | regress_rls_bob | my second manga |
6 | 22 | 1 | regress_rls_carol | great science fiction |
78 | 33 | 1 | regress_rls_bob | some technology novel |
79 | 33 | 1 | regress_rls_bob | technology book, can only insert |
12 | 11 | 1 | regress_rls_bob | another novel |
- 1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4
-(14 rows)
+ 1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4 notes added by merge7
+ 13 | 44 | 1 | regress_rls_bob | new manga |
+(15 rows)
--
-- ROLE/GROUP
CREATE POLICY p1 ON document FOR SELECT USING (true);
-- one may insert documents only authored by them
CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
--- one may only update documents in 'novel' category
+-- one may only update documents in 'novel' category and new dlevel must be > 0
CREATE POLICY p3 ON document FOR UPDATE
USING (cid = (SELECT cid from category WHERE cname = 'novel'))
- WITH CHECK (dauthor = current_user);
+ WITH CHECK (dlevel > 0);
-- one may only delete documents in 'manga' category
CREATE POLICY p4 ON document FOR DELETE
USING (cid = (SELECT cid from category WHERE cname = 'manga'));
SET SESSION AUTHORIZATION regress_rls_bob;
--- Fails, since update violates WITH CHECK qual on dauthor
+-- Fails, since update violates WITH CHECK qual on dlevel
MERGE INTO document d
USING (SELECT 1 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
- UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dauthor = 'regress_rls_alice';
+ UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dlevel = 0;
-- Should be OK since USING and WITH CHECK quals pass
MERGE INTO document d
WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge2 ';
--- Even when dauthor is updated explicitly, but to the existing value
+-- Even when dlevel is updated explicitly, but to the existing value
MERGE INTO document d
USING (SELECT 1 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
- UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dauthor = 'regress_rls_bob';
+ UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dlevel = 1;
-- There is a MATCH for did = 3, but UPDATE's USING qual does not allow
-- updating an item in category 'science fiction'
WHEN MATCHED THEN
DELETE;
+-- OK if DELETE is replaced with DO NOTHING
+MERGE INTO document d
+USING (SELECT 4 as sdid) s
+ON did = s.sdid
+WHEN MATCHED AND dnotes <> '' THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge '
+WHEN MATCHED THEN
+ DO NOTHING;
+
SELECT * FROM document WHERE did = 4;
-- Switch to regress_rls_carol role and try the DELETE again. It should succeed
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
-- drop and create a new SELECT policy which prevents us from reading
--- any document except with category 'magna'
+-- any document except with category 'novel'
RESET SESSION AUTHORIZATION;
DROP POLICY p1 ON document;
CREATE POLICY p1 ON document FOR SELECT
- USING (cid = (SELECT cid from category WHERE cname = 'manga'));
+ USING (cid = (SELECT cid from category WHERE cname = 'novel'));
SET SESSION AUTHORIZATION regress_rls_bob;
-- MERGE can no longer see the matching row and hence attempts the
-- NOT MATCHED action, which results in unique key violation
MERGE INTO document d
-USING (SELECT 1 as sdid) s
+USING (SELECT 7 as sdid) s
ON did = s.sdid
WHEN MATCHED THEN
UPDATE SET dnotes = dnotes || ' notes added by merge5 '
WHEN NOT MATCHED THEN
INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel');
+-- UPDATE action fails if new row is not visible
+MERGE INTO document d
+USING (SELECT 1 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge6 ',
+ cid = (SELECT cid from category WHERE cname = 'technology');
+
+-- but OK if new row is visible
+MERGE INTO document d
+USING (SELECT 1 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge7 ',
+ cid = (SELECT cid from category WHERE cname = 'novel');
+
+-- OK to insert a new row that is not visible
+MERGE INTO document d
+USING (SELECT 13 as sdid) s
+ON did = s.sdid
+WHEN MATCHED THEN
+ UPDATE SET dnotes = dnotes || ' notes added by merge8 '
+WHEN NOT MATCHED THEN
+ INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga');
+
RESET SESSION AUTHORIZATION;
-- drop the restrictive SELECT policy so that we can look at the
-- final state of the table