diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml index 868a6c1cd3..49eaadc259 100644 --- a/doc/src/sgml/ref/create_policy.sgml +++ b/doc/src/sgml/ref/create_policy.sgml @@ -60,6 +60,14 @@ CREATE POLICY name ON + + For INSERT and UPDATE queries, WITH CHECK expressions are enforced after + BEFORE triggers are fired, and before any data modifications are made. + Thus a BEFORE ROW trigger may modify the data to be inserted, affecting + the result of the security policy check. WITH CHECK expressions are + enforced before any other constraints. + + Policy names are per-table, therefore one policy name can be used for many different tables and have a definition for each table which is appropriate to diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 90d37b566a..df4da3faa9 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1673,9 +1673,15 @@ ExecConstraints(ResultRelInfo *resultRelInfo, /* * ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs + * of the specified kind. + * + * Note that this needs to be called multiple times to ensure that all kinds of + * WITH CHECK OPTIONs are handled (both those from views which have the WITH + * CHECK OPTION set and from row level security policies). See ExecInsert() + * and ExecUpdate(). */ void -ExecWithCheckOptions(ResultRelInfo *resultRelInfo, +ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { Relation rel = resultRelInfo->ri_RelationDesc; @@ -1700,6 +1706,13 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo, WithCheckOption *wco = (WithCheckOption *) lfirst(l1); ExprState *wcoExpr = (ExprState *) lfirst(l2); + /* + * Skip any WCOs which are not the kind we are looking for at this + * time. + */ + if (wco->kind != kind) + continue; + /* * WITH CHECK OPTION checks are intended to ensure that the new tuple * is visible (in the case of a view) or that it passes the @@ -1714,19 +1727,42 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo, char *val_desc; Bitmapset *modifiedCols; - modifiedCols = GetModifiedColumns(resultRelInfo, estate); - val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), - slot, - tupdesc, - modifiedCols, - 64); + switch (wco->kind) + { + /* + * For WITH CHECK OPTIONs coming from views, we might be able to + * provide the details on the row, depending on the permissions + * on the relation (that is, if the user could view it directly + * anyway). For RLS violations, we don't include the data since + * we don't know if the user should be able to view the tuple as + * as that depends on the USING policy. + */ + case WCO_VIEW_CHECK: + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); - ereport(ERROR, - (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), - errmsg("new row violates WITH CHECK OPTION for \"%s\"", - wco->viewname), - val_desc ? errdetail("Failing row contains %s.", val_desc) : - 0)); + ereport(ERROR, + (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), + errmsg("new row violates WITH CHECK OPTION for \"%s\"", + wco->relname), + val_desc ? errdetail("Failing row contains %s.", + val_desc) : 0)); + break; + case WCO_RLS_INSERT_CHECK: + case WCO_RLS_UPDATE_CHECK: + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("new row violates row level security policy for \"%s\"", + wco->relname))); + break; + default: + elog(ERROR, "unrecognized WCO kind: %u", wco->kind); + break; + } } } } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index f96fb2432b..06ec82e246 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -252,6 +252,16 @@ ExecInsert(TupleTableSlot *slot, */ tuple->t_tableOid = RelationGetRelid(resultRelationDesc); + /* + * Check any RLS INSERT WITH CHECK policies + * + * ExecWithCheckOptions() will skip any WCOs which are not of + * the kind we are looking for at this point. + */ + if (resultRelInfo->ri_WithCheckOptions != NIL) + ExecWithCheckOptions(WCO_RLS_INSERT_CHECK, + resultRelInfo, slot, estate); + /* * Check the constraints of the tuple */ @@ -287,9 +297,21 @@ ExecInsert(TupleTableSlot *slot, list_free(recheckIndexes); - /* Check any WITH CHECK OPTION constraints */ + /* + * Check any WITH CHECK OPTION constraints from parent views. We + * are required to do this after testing all constraints and + * uniqueness violations per the SQL spec, so we do it after actually + * inserting the record into the heap and all indexes. + * + * ExecWithCheckOptions will elog(ERROR) if a violation is found, so + * the tuple will never be seen, if it violates the the WITH CHECK + * OPTION. + * + * ExecWithCheckOptions() will skip any WCOs which are not of + * the kind we are looking for at this point. + */ if (resultRelInfo->ri_WithCheckOptions != NIL) - ExecWithCheckOptions(resultRelInfo, slot, estate); + ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) @@ -653,15 +675,25 @@ ExecUpdate(ItemPointer tupleid, tuple->t_tableOid = RelationGetRelid(resultRelationDesc); /* - * Check the constraints of the tuple + * Check any RLS UPDATE WITH CHECK policies * * If we generate a new candidate tuple after EvalPlanQual testing, we - * must loop back here and recheck constraints. (We don't need to - * redo triggers, however. If there are any BEFORE triggers then - * trigger.c will have done heap_lock_tuple to lock the correct tuple, - * so there's no need to do them again.) + * must loop back here and recheck any RLS policies and constraints. + * (We don't need to redo triggers, however. If there are any BEFORE + * triggers then trigger.c will have done heap_lock_tuple to lock the + * correct tuple, so there's no need to do them again.) + * + * ExecWithCheckOptions() will skip any WCOs which are not of + * the kind we are looking for at this point. */ lreplace:; + if (resultRelInfo->ri_WithCheckOptions != NIL) + ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK, + resultRelInfo, slot, estate); + + /* + * Check the constraints of the tuple + */ if (resultRelationDesc->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); @@ -780,9 +812,17 @@ lreplace:; list_free(recheckIndexes); - /* Check any WITH CHECK OPTION constraints */ + /* + * Check any WITH CHECK OPTION constraints from parent views. We + * are required to do this after testing all constraints and + * uniqueness violations per the SQL spec, so we do it after actually + * updating the record in the heap and all indexes. + * + * ExecWithCheckOptions() will skip any WCOs which are not of + * the kind we are looking for at this point. + */ if (resultRelInfo->ri_WithCheckOptions != NIL) - ExecWithCheckOptions(resultRelInfo, slot, estate); + ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 029761e74f..59c755d7a5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2064,7 +2064,8 @@ _copyWithCheckOption(const WithCheckOption *from) { WithCheckOption *newnode = makeNode(WithCheckOption); - COPY_STRING_FIELD(viewname); + COPY_SCALAR_FIELD(kind); + COPY_STRING_FIELD(relname); COPY_NODE_FIELD(qual); COPY_SCALAR_FIELD(cascaded); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 190e50ab8c..3bc81762af 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2363,7 +2363,8 @@ _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b) static bool _equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b) { - COMPARE_STRING_FIELD(viewname); + COMPARE_SCALAR_FIELD(kind); + COMPARE_STRING_FIELD(relname); COMPARE_NODE_FIELD(qual); COMPARE_SCALAR_FIELD(cascaded); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 385b289bed..e0dca56ea6 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2332,7 +2332,8 @@ _outWithCheckOption(StringInfo str, const WithCheckOption *node) { WRITE_NODE_TYPE("WITHCHECKOPTION"); - WRITE_STRING_FIELD(viewname); + WRITE_ENUM_FIELD(kind, WCOKind); + WRITE_STRING_FIELD(relname); WRITE_NODE_FIELD(qual); WRITE_BOOL_FIELD(cascaded); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 563209c561..b0cd95da06 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -266,7 +266,8 @@ _readWithCheckOption(void) { READ_LOCALS(WithCheckOption); - READ_STRING_FIELD(viewname); + READ_ENUM_FIELD(kind, WCOKind); + READ_STRING_FIELD(relname); READ_NODE_FIELD(qual); READ_BOOL_FIELD(cascaded); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 08ec13ccdd..60c60caf39 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -2947,7 +2947,8 @@ rewriteTargetView(Query *parsetree, Relation view) WithCheckOption *wco; wco = makeNode(WithCheckOption); - wco->viewname = pstrdup(RelationGetRelationName(view)); + wco->kind = WCO_VIEW_CHECK; + wco->relname = pstrdup(RelationGetRelationName(view)); wco->qual = NULL; wco->cascaded = cascaded; diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index e060353bff..b0b308118f 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -259,7 +259,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index, WithCheckOption *wco; wco = (WithCheckOption *) makeNode(WithCheckOption); - wco->viewname = pstrdup(RelationGetRelationName(rel)); + wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK : + WCO_RLS_UPDATE_CHECK; + wco->relname = pstrdup(RelationGetRelationName(rel)); wco->qual = (Node *) hook_with_check_expr_restrictive; wco->cascaded = false; *withCheckOptions = lappend(*withCheckOptions, wco); @@ -274,7 +276,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index, WithCheckOption *wco; wco = (WithCheckOption *) makeNode(WithCheckOption); - wco->viewname = pstrdup(RelationGetRelationName(rel)); + wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK : + WCO_RLS_UPDATE_CHECK; + wco->relname = pstrdup(RelationGetRelationName(rel)); wco->qual = (Node *) rowsec_with_check_expr; wco->cascaded = false; *withCheckOptions = lappend(*withCheckOptions, wco); @@ -285,7 +289,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index, WithCheckOption *wco; wco = (WithCheckOption *) makeNode(WithCheckOption); - wco->viewname = pstrdup(RelationGetRelationName(rel)); + wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK : + WCO_RLS_UPDATE_CHECK; + wco->relname = pstrdup(RelationGetRelationName(rel)); wco->qual = (Node *) hook_with_check_expr_permissive; wco->cascaded = false; *withCheckOptions = lappend(*withCheckOptions, wco); @@ -297,13 +303,18 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index, List *combined_quals = NIL; Expr *combined_qual_eval; - combined_quals = lcons(copyObject(rowsec_with_check_expr), combined_quals); - combined_quals = lcons(copyObject(hook_with_check_expr_permissive), combined_quals); + combined_quals = lcons(copyObject(rowsec_with_check_expr), + combined_quals); + + combined_quals = lcons(copyObject(hook_with_check_expr_permissive), + combined_quals); combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1); wco = (WithCheckOption *) makeNode(WithCheckOption); - wco->viewname = pstrdup(RelationGetRelationName(rel)); + wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK : + WCO_RLS_UPDATE_CHECK; + wco->relname = pstrdup(RelationGetRelationName(rel)); wco->qual = (Node *) combined_qual_eval; wco->cascaded = false; *withCheckOptions = lappend(*withCheckOptions, wco); @@ -332,7 +343,8 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index, Expr *combined_qual_eval; combined_quals = lcons(copyObject(rowsec_expr), combined_quals); - combined_quals = lcons(copyObject(hook_expr_permissive), combined_quals); + combined_quals = lcons(copyObject(hook_expr_permissive), + combined_quals); combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index c2bf9a2940..33c8fad844 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -193,7 +193,7 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids); extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); -extern void ExecWithCheckOptions(ResultRelInfo *resultRelInfo, +extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti); extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ac75f86fef..dac542fbc1 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -303,7 +303,7 @@ typedef struct JunkFilter * TrigInstrument optional runtime measurements for triggers * FdwRoutine FDW callback functions, if foreign table * FdwState available to save private state of FDW - * WithCheckOptions list of WithCheckOption's for views + * WithCheckOptions list of WithCheckOption's to be checked * WithCheckOptionExprs list of WithCheckOption expr states * ConstraintExprs array of constraint-checking expr states * junkFilter for removing junk attributes from tuples diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 0e257ac46c..36e36d5631 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -872,14 +872,23 @@ typedef struct RangeTblFunction /* * WithCheckOption - * representation of WITH CHECK OPTION checks to be applied to new tuples - * when inserting/updating an auto-updatable view. + * when inserting/updating an auto-updatable view, or RLS WITH CHECK + * policies to be applied when inserting/updating a relation with RLS. */ +typedef enum WCOKind +{ + WCO_VIEW_CHECK, /* WCO on an auto-updatable view */ + WCO_RLS_INSERT_CHECK, /* RLS INSERT WITH CHECK policy */ + WCO_RLS_UPDATE_CHECK /* RLS UPDATE WITH CHECK policy */ +} WCOKind; + typedef struct WithCheckOption { NodeTag type; - char *viewname; /* name of view that specified the WCO */ + WCOKind kind; /* kind of WCO */ + char *relname; /* name of relation that specified the WCO */ Node *qual; /* constraint qual to check */ - bool cascaded; /* true = WITH CASCADED CHECK OPTION */ + bool cascaded; /* true for a cascaded WCO on a view */ } WithCheckOption; /* diff --git a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out index 54d3bb7a8c..9427a6fae8 100644 --- a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out +++ b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out @@ -57,7 +57,7 @@ SELECT * FROM rls_test_permissive; INSERT INTO rls_test_permissive VALUES ('r1','s1',10); -- failure INSERT INTO rls_test_permissive VALUES ('r4','s4',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_permissive" +ERROR: new row violates row level security policy for "rls_test_permissive" SET ROLE s1; -- With only the hook's policies, restrictive -- hook's policy is current_user = supervisor @@ -78,7 +78,7 @@ SELECT * FROM rls_test_restrictive; INSERT INTO rls_test_restrictive VALUES ('r1','s1',10); -- failure INSERT INTO rls_test_restrictive VALUES ('r4','s4',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive" +ERROR: new row violates row level security policy for "rls_test_restrictive" SET ROLE s1; -- With only the hook's policies, both -- permissive hook's policy is current_user = username @@ -100,13 +100,13 @@ SELECT * FROM rls_test_both; -- failure INSERT INTO rls_test_both VALUES ('r1','s1',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" -- failure INSERT INTO rls_test_both VALUES ('r4','s1',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" -- failure INSERT INTO rls_test_both VALUES ('r4','s4',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" RESET ROLE; -- Create "internal" policies, to check that the policies from -- the hooks are combined correctly. @@ -136,7 +136,7 @@ INSERT INTO rls_test_permissive VALUES ('r1','s1',7); INSERT INTO rls_test_permissive VALUES ('r3','s3',10); -- failure INSERT INTO rls_test_permissive VALUES ('r4','s4',7); -ERROR: new row violates WITH CHECK OPTION for "rls_test_permissive" +ERROR: new row violates row level security policy for "rls_test_permissive" SET ROLE s1; -- With both internal and hook policies, restrictive EXPLAIN (costs off) SELECT * FROM rls_test_restrictive; @@ -158,13 +158,13 @@ SELECT * FROM rls_test_restrictive; INSERT INTO rls_test_restrictive VALUES ('r1','s1',8); -- failure INSERT INTO rls_test_restrictive VALUES ('r3','s3',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive" +ERROR: new row violates row level security policy for "rls_test_restrictive" -- failure INSERT INTO rls_test_restrictive VALUES ('r1','s1',7); -ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive" +ERROR: new row violates row level security policy for "rls_test_restrictive" -- failure INSERT INTO rls_test_restrictive VALUES ('r4','s4',7); -ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive" +ERROR: new row violates row level security policy for "rls_test_restrictive" -- With both internal and hook policies, both permissive -- and restrictive hook policies EXPLAIN (costs off) SELECT * FROM rls_test_both; @@ -185,13 +185,13 @@ SELECT * FROM rls_test_both; INSERT INTO rls_test_both VALUES ('r1','s1',8); -- failure INSERT INTO rls_test_both VALUES ('r3','s3',10); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" -- failure INSERT INTO rls_test_both VALUES ('r1','s1',7); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" -- failure INSERT INTO rls_test_both VALUES ('r4','s4',7); -ERROR: new row violates WITH CHECK OPTION for "rls_test_both" +ERROR: new row violates row level security policy for "rls_test_both" RESET ROLE; DROP TABLE rls_test_restrictive; DROP TABLE rls_test_permissive; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 56760796ed..1ea65a7d8a 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -300,6 +300,11 @@ SELECT * FROM document WHERE did = 8; -- and confirm we can't see it -----+-----+--------+---------+-------- (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 "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 "document" -- database superuser does bypass RLS policy when enabled RESET SESSION AUTHORIZATION; SET row_security TO ON; @@ -1426,9 +1431,9 @@ NOTICE: f_leak => d3d9446802a44259755d38e6d163e820 (5 rows) INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO -ERROR: new row violates WITH CHECK OPTION for "b1" +ERROR: new row violates row level security policy for "b1" INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check -ERROR: new row violates WITH CHECK OPTION for "b1" +ERROR: new row violates row level security policy for "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 @@ -1988,7 +1993,7 @@ EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FRO (6 rows) WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail -ERROR: new row violates WITH CHECK OPTION for "t1" +ERROR: new row violates row level security policy for "t1" WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok a | b ----+---------------------------------- @@ -2006,7 +2011,7 @@ WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok (11 rows) WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail -ERROR: new row violates WITH CHECK OPTION for "t1" +ERROR: new row violates row level security policy for "t1" WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok a | b ----+--------- diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 4da5035f5b..f38b4438fd 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -146,6 +146,10 @@ 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 SELECT * FROM document WHERE did = 8; -- and confirm we can't see it +-- 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 +UPDATE document SET did = 8, dauthor = 'rls_regress_user2' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation + -- database superuser does bypass RLS policy when enabled RESET SESSION AUTHORIZATION; SET row_security TO ON;