From 088c83363a11200f2225f279d4a5c6cc6f9db3d2 Mon Sep 17 00:00:00 2001 From: Stephen Frost Date: Sun, 4 Oct 2015 21:05:08 -0400 Subject: [PATCH] ALTER TABLE .. FORCE ROW LEVEL SECURITY To allow users to force RLS to always be applied, even for table owners, add ALTER TABLE .. FORCE ROW LEVEL SECURITY. row_security=off overrides FORCE ROW LEVEL SECURITY, to ensure pg_dump output is complete (by default). Also add SECURITY_NOFORCE_RLS context to avoid data corruption when ALTER TABLE .. FORCE ROW SECURITY is being used. The SECURITY_NOFORCE_RLS security context is used only during referential integrity checks and is only considered in check_enable_rls() after we have already checked that the current user is the owner of the relation (which should always be the case during referential integrity checks). Back-patch to 9.5 where RLS was added. --- doc/src/sgml/catalogs.sgml | 10 ++ doc/src/sgml/ref/alter_table.sgml | 17 ++ src/backend/catalog/heap.c | 1 + src/backend/commands/tablecmds.c | 40 +++++ src/backend/parser/gram.y | 14 ++ src/backend/utils/adt/ri_triggers.c | 6 +- src/backend/utils/init/miscinit.c | 18 +- src/backend/utils/misc/rls.c | 44 ++++- src/bin/pg_dump/pg_dump.c | 20 ++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 44 +++-- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_class.h | 72 ++++---- src/include/miscadmin.h | 2 + src/include/nodes/parsenodes.h | 2 + .../test_ddl_deparse/test_ddl_deparse.c | 6 + src/test/regress/expected/rowsecurity.out | 156 ++++++++++++++++++ src/test/regress/output/misc.source | 3 +- src/test/regress/sql/rowsecurity.sql | 143 ++++++++++++++++ 19 files changed, 537 insertions(+), 64 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 28bb480447e..97ef6186062 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1971,6 +1971,16 @@ + + relforcerowsecurity + bool + + + True if row level security (when enabled) will also apply to table owner; see + pg_policy catalog + + + relispopulated bool diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 8e35cd937bd..aca40f596a2 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -61,6 +61,8 @@ ALTER TABLE ALL IN TABLESPACE name ENABLE ALWAYS RULE rewrite_rule_name DISABLE ROW LEVEL SECURITY ENABLE ROW LEVEL SECURITY + FORCE ROW LEVEL SECURITY + NO FORCE ROW LEVEL SECURITY CLUSTER ON index_name SET WITHOUT CLUSTER SET WITH OIDS @@ -433,6 +435,21 @@ ALTER TABLE ALL IN TABLESPACE name + + NO FORCE/FORCE ROW LEVEL SECURITY + + + These forms control the application of row security policies belonging + to the table when the user is the table owner. If enabled, row level + security policies will be applied when the user is the table owner. If + disabled (the default) then row level security will not be applied when + the user is the table owner. + See also + . + + + + CLUSTER ON diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index d04e94d74f3..7d7d062c068 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -802,6 +802,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules); values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity); + values[Anum_pg_class_relforcerowsecurity - 1] = BoolGetDatum(rd_rel->relforcerowsecurity); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated); values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 126b11923f0..7668c9d771f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -419,6 +419,7 @@ static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKM static void ATExecGenericOptions(Relation rel, List *options); static void ATExecEnableRowSecurity(Relation rel); static void ATExecDisableRowSecurity(Relation rel); +static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls); static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, char relpersistence); @@ -2930,6 +2931,8 @@ AlterTableGetLockLevel(List *cmds) case AT_SetNotNull: case AT_EnableRowSecurity: case AT_DisableRowSecurity: + case AT_ForceRowSecurity: + case AT_NoForceRowSecurity: cmd_lockmode = AccessExclusiveLock; break; @@ -3351,6 +3354,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_DropOf: /* NOT OF */ case AT_EnableRowSecurity: case AT_DisableRowSecurity: + case AT_ForceRowSecurity: + case AT_NoForceRowSecurity: ATSimplePermissions(rel, ATT_TABLE); /* These commands never recurse */ /* No command-specific prep needed */ @@ -3667,6 +3672,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_DisableRowSecurity: ATExecDisableRowSecurity(rel); break; + case AT_ForceRowSecurity: + ATExecForceNoForceRowSecurity(rel, true); + break; + case AT_NoForceRowSecurity: + ATExecForceNoForceRowSecurity(rel, false); + break; case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; @@ -11066,6 +11077,35 @@ ATExecDisableRowSecurity(Relation rel) heap_freetuple(tuple); } +/* + * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY + */ +static void +ATExecForceNoForceRowSecurity(Relation rel, bool force_rls) +{ + Relation pg_class; + Oid relid; + HeapTuple tuple; + + relid = RelationGetRelid(rel); + + pg_class = heap_open(RelationRelationId, RowExclusiveLock); + + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + + ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls; + simple_heap_update(pg_class, &tuple->t_self, tuple); + + /* keep catalog indexes current */ + CatalogUpdateIndexes(pg_class, tuple); + + heap_close(pg_class, RowExclusiveLock); + heap_freetuple(tuple); +} + /* * ALTER FOREIGN TABLE OPTIONS (...) */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 417fb55bc48..8bd511952c9 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2353,6 +2353,20 @@ alter_table_cmd: n->subtype = AT_DisableRowSecurity; $$ = (Node *)n; } + /* ALTER TABLE FORCE ROW LEVEL SECURITY */ + | FORCE ROW LEVEL SECURITY + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ForceRowSecurity; + $$ = (Node *)n; + } + /* ALTER TABLE NO FORCE ROW LEVEL SECURITY */ + | NO FORCE ROW LEVEL SECURITY + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_NoForceRowSecurity; + $$ = (Node *)n; + } | alter_generic_options { AlterTableCmd *n = makeNode(AlterTableCmd); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 018cb99e8cd..6569fdba164 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -3014,7 +3014,8 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, /* Switch to proper UID to perform check as */ GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, - save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + save_sec_context | SECURITY_LOCAL_USERID_CHANGE | + SECURITY_NOFORCE_RLS); /* Create the plan */ qplan = SPI_prepare(querystr, nargs, argtypes); @@ -3134,7 +3135,8 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo, /* Switch to proper UID to perform check as */ GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, - save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + save_sec_context | SECURITY_LOCAL_USERID_CHANGE | + SECURITY_NOFORCE_RLS); /* Finally we can run the query. */ spi_result = SPI_execute_snapshot(qplan, diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index f0099d31fad..e871fef7faa 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -341,7 +341,7 @@ GetAuthenticatedUserId(void) * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID * and the SecurityRestrictionContext flags. * - * Currently there are two valid bits in SecurityRestrictionContext: + * Currently there are three valid bits in SecurityRestrictionContext: * * SECURITY_LOCAL_USERID_CHANGE indicates that we are inside an operation * that is temporarily changing CurrentUserId via these functions. This is @@ -359,6 +359,13 @@ GetAuthenticatedUserId(void) * where the called functions are really supposed to be side-effect-free * anyway, such as VACUUM/ANALYZE/REINDEX. * + * SECURITY_NOFORCE_RLS indicates that we are inside an operation which should + * ignore the FORCE ROW LEVEL SECURITY per-table indication. This is used to + * ensure that FORCE RLS does not mistakenly break referential integrity + * checks. Note that this is intentionally only checked when running as the + * owner of the table (which should always be the case for referential + * integrity checks). + * * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require * the new value to be valid. In fact, these routines had better not @@ -401,6 +408,15 @@ InSecurityRestrictedOperation(void) return (SecurityRestrictionContext & SECURITY_RESTRICTED_OPERATION) != 0; } +/* + * InNoForceRLSOperation - are we ignoring FORCE ROW LEVEL SECURITY ? + */ +bool +InNoForceRLSOperation(void) +{ + return (SecurityRestrictionContext & SECURITY_NOFORCE_RLS) != 0; +} + /* * These are obsolete versions of Get/SetUserIdAndSecContext that are diff --git a/src/backend/utils/misc/rls.c b/src/backend/utils/misc/rls.c index eaf9d6e66ff..6ce92af0199 100644 --- a/src/backend/utils/misc/rls.c +++ b/src/backend/utils/misc/rls.c @@ -55,6 +55,7 @@ check_enable_rls(Oid relid, Oid checkAsUser, bool noError) HeapTuple tuple; Form_pg_class classform; bool relrowsecurity; + bool relforcerowsecurity; Oid user_id = checkAsUser ? checkAsUser : GetUserId(); /* Nothing to do for built-in relations */ @@ -68,6 +69,7 @@ check_enable_rls(Oid relid, Oid checkAsUser, bool noError) classform = (Form_pg_class) GETSTRUCT(tuple); relrowsecurity = classform->relrowsecurity; + relforcerowsecurity = classform->relforcerowsecurity; ReleaseSysCache(tuple); @@ -76,14 +78,46 @@ check_enable_rls(Oid relid, Oid checkAsUser, bool noError) return RLS_NONE; /* - * Table owners and BYPASSRLS users bypass RLS. Note that a superuser - * qualifies as both. Return RLS_NONE_ENV to indicate that this decision - * depends on the environment (in this case, the user_id). + * BYPASSRLS users always bypass RLS. Note that superusers are always + * considered to have BYPASSRLS. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). */ - if (pg_class_ownercheck(relid, user_id) || - has_bypassrls_privilege(user_id)) + if (has_bypassrls_privilege(user_id)) return RLS_NONE_ENV; + /* + * Table owners generally bypass RLS, except if row_security=true and the + * table has been set (by an owner) to FORCE ROW SECURITY, and this is not + * a referential integrity check. + * + * Return RLS_NONE_ENV to indicate that this decision depends on the + * environment (in this case, the user_id). + */ + if (pg_class_ownercheck(relid, user_id)) + { + /* + * If row_security=true and FORCE ROW LEVEL SECURITY has been set on + * the relation then we return RLS_ENABLED to indicate that RLS should + * still be applied. If we are in a SECURITY_NOFORCE_RLS context or if + * row_security=false then we return RLS_NONE_ENV. + * + * The SECURITY_NOFORCE_RLS indicates that we should not apply RLS even + * if the table has FORCE RLS set- IF the current user is the owner. + * This is specifically to ensure that referential integrity checks are + * able to still run correctly. + * + * This is intentionally only done after we have checked that the user + * is the table owner, which should always be the case for referential + * integrity checks. + */ + if (row_security && relforcerowsecurity && !InNoForceRLSOperation()) + return RLS_ENABLED; + else + return RLS_NONE_ENV; + } + /* row_security GUC says to bypass RLS, but user lacks permission */ if (!row_security && !noError) ereport(ERROR, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ba1683b79a0..36863df5688 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4540,6 +4540,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) int i_relhasindex; int i_relhasrules; int i_relrowsec; + int i_relforcerowsec; int i_relhasoids; int i_relfrozenxid; int i_relminmxid; @@ -4593,7 +4594,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " - "c.relrowsecurity, " + "c.relrowsecurity, c.relforcerowsecurity, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "tc.relminmxid AS tminmxid, " @@ -4635,6 +4636,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "tc.relminmxid AS tminmxid, " @@ -4676,6 +4678,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "tc.relminmxid AS tminmxid, " @@ -4717,6 +4720,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4756,6 +4760,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4794,6 +4799,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4832,6 +4838,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4870,6 +4877,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4907,6 +4915,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4940,6 +4949,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4968,6 +4978,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "relhasindex, relhasrules, " "'t'::bool AS relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -5006,6 +5017,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) "relhasindex, relhasrules, " "'t'::bool AS relhasoids, " "'f'::bool AS relrowsecurity, " + "'f'::bool AS relforcerowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -5054,6 +5066,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasrules = PQfnumber(res, "relhasrules"); i_relrowsec = PQfnumber(res, "relrowsecurity"); + i_relforcerowsec = PQfnumber(res, "relforcerowsecurity"); i_relhasoids = PQfnumber(res, "relhasoids"); i_relfrozenxid = PQfnumber(res, "relfrozenxid"); i_relminmxid = PQfnumber(res, "relminmxid"); @@ -5106,6 +5119,7 @@ getTables(Archive *fout, DumpOptions *dopt, int *numTables) tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0); tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0); + tblinfo[i].forcerowsec = (strcmp(PQgetvalue(res, i, i_relforcerowsec), "t") == 0); tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0); tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0); tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident)); @@ -14412,6 +14426,10 @@ dumpTableSchema(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo) appendPQExpBuffer(q, "\nALTER TABLE ONLY %s SET WITH OIDS;\n", fmtId(tbinfo->dobj.name)); + if (tbinfo->forcerowsec) + appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n", + fmtId(tbinfo->dobj.name)); + if (dopt->binary_upgrade) binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index b40b816bee0..3c64a82dba6 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -211,6 +211,7 @@ typedef struct _tableInfo bool hasrules; /* does it have any rules? */ bool hastriggers; /* does it have any triggers? */ bool rowsec; /* is row security enabled? */ + bool forcerowsec; /* is row security forced? */ bool hasoids; /* does it have OIDs? */ uint32 frozenxid; /* for restore frozen xid */ uint32 minmxid; /* for restore min multi xid */ diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 898f8b39cdc..92ed6e29703 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1234,6 +1234,7 @@ describeOneTableDetails(const char *schemaname, bool hasrules; bool hastriggers; bool rowsecurity; + bool forcerowsecurity; bool hasoids; Oid tablespace; char *reloptions; @@ -1259,8 +1260,8 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relrowsecurity, c.relhasoids, " - "%s, c.reltablespace, " + "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, " + "c.relhasoids, %s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident\n" "FROM pg_catalog.pg_class c\n " @@ -1276,7 +1277,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, c.relhasoids, " + "c.relhastriggers, false, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident\n" @@ -1293,7 +1294,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, c.relhasoids, " + "c.relhastriggers, false, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence\n" @@ -1310,7 +1311,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, c.relhasoids, " + "c.relhastriggers, false, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END\n" "FROM pg_catalog.pg_class c\n " @@ -1326,7 +1327,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, c.relhasoids, " + "c.relhastriggers, false, false, c.relhasoids, " "%s, c.reltablespace\n" "FROM pg_catalog.pg_class c\n " "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" @@ -1341,7 +1342,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, false, relhasoids, " + "reltriggers <> 0, false, false, relhasoids, " "%s, reltablespace\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", (verbose ? @@ -1352,7 +1353,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, false, relhasoids, " + "reltriggers <> 0, false, false, relhasoids, " "'', reltablespace\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); @@ -1361,7 +1362,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, false, relhasoids, " + "reltriggers <> 0, false, false, relhasoids, " "'', ''\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); @@ -1385,18 +1386,19 @@ describeOneTableDetails(const char *schemaname, tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0; tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0; tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0; - tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0; + tableinfo.forcerowsecurity = strcmp(PQgetvalue(res, 0, 6), "t") == 0; + tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 7), "t") == 0; tableinfo.reloptions = (pset.sversion >= 80200) ? - pg_strdup(PQgetvalue(res, 0, 7)) : NULL; + pg_strdup(PQgetvalue(res, 0, 8)) : NULL; tableinfo.tablespace = (pset.sversion >= 80000) ? - atooid(PQgetvalue(res, 0, 8)) : 0; + atooid(PQgetvalue(res, 0, 9)) : 0; tableinfo.reloftype = (pset.sversion >= 90000 && - strcmp(PQgetvalue(res, 0, 9), "") != 0) ? - pg_strdup(PQgetvalue(res, 0, 9)) : NULL; + strcmp(PQgetvalue(res, 0, 10), "") != 0) ? + pg_strdup(PQgetvalue(res, 0, 10)) : NULL; tableinfo.relpersistence = (pset.sversion >= 90100) ? - *(PQgetvalue(res, 0, 10)) : 0; + *(PQgetvalue(res, 0, 11)) : 0; tableinfo.relreplident = (pset.sversion >= 90400) ? - *(PQgetvalue(res, 0, 11)) : 'd'; + *(PQgetvalue(res, 0, 12)) : 'd'; PQclear(res); res = NULL; @@ -2057,12 +2059,18 @@ describeOneTableDetails(const char *schemaname, * there aren't policies, or RLS isn't enabled but there are * policies */ - if (tableinfo.rowsecurity && tuples > 0) + if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples > 0) printTableAddFooter(&cont, _("Policies:")); - if (tableinfo.rowsecurity && tuples == 0) + if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples > 0) + printTableAddFooter(&cont, _("Policies (Forced Row Security Enabled):")); + + if (tableinfo.rowsecurity && !tableinfo.forcerowsecurity && tuples == 0) printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)")); + if (tableinfo.rowsecurity && tableinfo.forcerowsecurity && tuples == 0) + printTableAddFooter(&cont, _("Policies (Forced Row Security Enabled): (None)")); + if (!tableinfo.rowsecurity && tuples > 0) printTableAddFooter(&cont, _("Policies (Row Security Disabled):")); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 9730561967b..8afe5cc801a 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201509161 +#define CATALOG_VERSION_NO 201510042 #endif diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 25247b54d10..06d287eb54b 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -66,6 +66,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO bool relhastriggers; /* has (or has had) any TRIGGERs */ bool relhassubclass; /* has (or has had) derived classes */ bool relrowsecurity; /* row security is enabled or not */ + bool relforcerowsecurity; /* row security forced for owners or not */ bool relispopulated; /* matview currently holds query results */ char relreplident; /* see REPLICA_IDENTITY_xxx constants */ TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */ @@ -95,37 +96,38 @@ typedef FormData_pg_class *Form_pg_class; * ---------------- */ -#define Natts_pg_class 30 -#define Anum_pg_class_relname 1 -#define Anum_pg_class_relnamespace 2 -#define Anum_pg_class_reltype 3 -#define Anum_pg_class_reloftype 4 -#define Anum_pg_class_relowner 5 -#define Anum_pg_class_relam 6 -#define Anum_pg_class_relfilenode 7 -#define Anum_pg_class_reltablespace 8 -#define Anum_pg_class_relpages 9 -#define Anum_pg_class_reltuples 10 -#define Anum_pg_class_relallvisible 11 -#define Anum_pg_class_reltoastrelid 12 -#define Anum_pg_class_relhasindex 13 -#define Anum_pg_class_relisshared 14 -#define Anum_pg_class_relpersistence 15 -#define Anum_pg_class_relkind 16 -#define Anum_pg_class_relnatts 17 -#define Anum_pg_class_relchecks 18 -#define Anum_pg_class_relhasoids 19 -#define Anum_pg_class_relhaspkey 20 -#define Anum_pg_class_relhasrules 21 -#define Anum_pg_class_relhastriggers 22 -#define Anum_pg_class_relhassubclass 23 -#define Anum_pg_class_relrowsecurity 24 -#define Anum_pg_class_relispopulated 25 -#define Anum_pg_class_relreplident 26 -#define Anum_pg_class_relfrozenxid 27 -#define Anum_pg_class_relminmxid 28 -#define Anum_pg_class_relacl 29 -#define Anum_pg_class_reloptions 30 +#define Natts_pg_class 31 +#define Anum_pg_class_relname 1 +#define Anum_pg_class_relnamespace 2 +#define Anum_pg_class_reltype 3 +#define Anum_pg_class_reloftype 4 +#define Anum_pg_class_relowner 5 +#define Anum_pg_class_relam 6 +#define Anum_pg_class_relfilenode 7 +#define Anum_pg_class_reltablespace 8 +#define Anum_pg_class_relpages 9 +#define Anum_pg_class_reltuples 10 +#define Anum_pg_class_relallvisible 11 +#define Anum_pg_class_reltoastrelid 12 +#define Anum_pg_class_relhasindex 13 +#define Anum_pg_class_relisshared 14 +#define Anum_pg_class_relpersistence 15 +#define Anum_pg_class_relkind 16 +#define Anum_pg_class_relnatts 17 +#define Anum_pg_class_relchecks 18 +#define Anum_pg_class_relhasoids 19 +#define Anum_pg_class_relhaspkey 20 +#define Anum_pg_class_relhasrules 21 +#define Anum_pg_class_relhastriggers 22 +#define Anum_pg_class_relhassubclass 23 +#define Anum_pg_class_relrowsecurity 24 +#define Anum_pg_class_relforcerowsecurity 25 +#define Anum_pg_class_relispopulated 26 +#define Anum_pg_class_relreplident 27 +#define Anum_pg_class_relfrozenxid 28 +#define Anum_pg_class_relminmxid 29 +#define Anum_pg_class_relacl 30 +#define Anum_pg_class_reloptions 31 /* ---------------- * initial contents of pg_class @@ -140,13 +142,13 @@ typedef FormData_pg_class *Form_pg_class; * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId; * similarly, "1" in relminmxid stands for FirstMultiXactId */ -DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ )); DESCR(""); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index ff695aae276..77cf8d71de7 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -287,6 +287,7 @@ extern int trace_recovery(int trace_level); /* flags to be OR'd to form sec_context */ #define SECURITY_LOCAL_USERID_CHANGE 0x0001 #define SECURITY_RESTRICTED_OPERATION 0x0002 +#define SECURITY_NOFORCE_RLS 0x0004 extern char *DatabasePath; @@ -305,6 +306,7 @@ extern void GetUserIdAndSecContext(Oid *userid, int *sec_context); extern void SetUserIdAndSecContext(Oid userid, int sec_context); extern bool InLocalUserIdChange(void); extern bool InSecurityRestrictedOperation(void); +extern bool InNoForceRLSOperation(void); extern void GetUserIdAndContext(Oid *userid, bool *sec_def_context); extern void SetUserIdAndContext(Oid userid, bool sec_def_context); extern void InitializeSessionUserId(const char *rolename, Oid useroid); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 770866a2696..fdf19fac888 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1514,6 +1514,8 @@ typedef enum AlterTableType AT_ReplicaIdentity, /* REPLICA IDENTITY */ AT_EnableRowSecurity, /* ENABLE ROW SECURITY */ AT_DisableRowSecurity, /* DISABLE ROW SECURITY */ + AT_ForceRowSecurity, /* FORCE ROW SECURITY */ + AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */ AT_GenericOptions /* OPTIONS (...) */ } AlterTableType; diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index e2dc4b5c768..4fca7373b0a 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -275,6 +275,12 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS) case AT_DisableRowSecurity: strtype = "DISABLE ROW SECURITY"; break; + case AT_ForceRowSecurity: + strtype = "FORCE ROW SECURITY"; + break; + case AT_NoForceRowSecurity: + strtype = "NO FORCE ROW SECURITY"; + break; case AT_GenericOptions: strtype = "SET OPTIONS"; break; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 0363dfd07ff..a050444cd0c 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -3009,6 +3009,155 @@ SET SESSION AUTHORIZATION rls_regress_user0; DROP TABLE r1; DROP TABLE r2; -- +-- FORCE ROW LEVEL SECURITY applies RLS to owners but +-- only when row_security = on +-- +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 "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; +-- Shows all rows +TABLE r1; + a +---- + 10 + 20 +(2 rows) + +-- Update all rows +UPDATE r1 SET a = 1; +TABLE r1; + a +--- + 1 + 1 +(2 rows) + +-- Delete all rows +DELETE FROM r1; +TABLE r1; + a +--- +(0 rows) + +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; +-- -- Clean up objects -- RESET SESSION AUTHORIZATION; @@ -3031,3 +3180,10 @@ 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); diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source index 44147638188..5f263f9a3a1 100644 --- a/src/test/regress/output/misc.source +++ b/src/test/regress/output/misc.source @@ -672,6 +672,7 @@ SELECT user_relns() AS user_relns real_city reltime_tbl rls_tbl + rls_tbl_force road shighway slow_emp4000 @@ -709,7 +710,7 @@ SELECT user_relns() AS user_relns tvvmv varchar_tbl xacttest -(131 rows) +(132 rows) SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); name diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 7f8772fa26c..070c452a77c 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -1288,6 +1288,141 @@ SET SESSION AUTHORIZATION rls_regress_user0; DROP TABLE r1; DROP TABLE r2; +-- +-- FORCE ROW LEVEL SECURITY applies RLS to owners but +-- only when row_security = on +-- +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; + +-- RLS error +INSERT INTO r1 VALUES (1); + +-- No error (unable to see any rows to update) +UPDATE r1 SET a = 1; +TABLE r1; + +-- No error (unable to see any rows to delete) +DELETE FROM r1; +TABLE r1; + +SET row_security = off; +-- Shows all rows +TABLE r1; + +-- Update all rows +UPDATE r1 SET a = 1; +TABLE r1; + +-- Delete all rows +DELETE FROM r1; +TABLE r1; + +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; + +-- 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; + +-- 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; + +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; + +DROP TABLE r2; +DROP TABLE r1; + -- -- Clean up objects -- @@ -1315,3 +1450,11 @@ 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); -- 2.30.2