* Noah Misch (n...@leadboat.com) wrote: > On Mon, Sep 28, 2015 at 05:13:56PM -0400, Stephen Frost wrote: > > * Noah Misch (n...@leadboat.com) wrote: > > > In schema reviews, I will raise a red flag for use of this feature; the > > > best > > > designs will instead use additional roles. I forecast that PostgreSQL > > > would > > > fare better with no owner-constrained-by-RLS capability. Even so, others > > > want > > > it, and FORCE ROW SECURITY would deliver it with an acceptable risk > > > profile. > > > > I've attached a patch to implement it. It's not fully polished but it's > > sufficient for comment, I believe. Additional comments, documentation > > and regression tests are to be added, if we have agreement on the > > grammer and implementation approach. > > This patch has FORCE ROW LEVEL SECURITY take precedence over row_security=off, > which thwarts pg_dump use of row_security=off to ensure dump completeness.
Fixed. > Should this be a table-level flag, or should it be a policy-level flag? A > policy-level flag is more powerful. If nobody really anticipates using that > power, this table-level flag works for me. table-level seems the right level to me and no one is calling for policy-level. Further, policy-level could be added later if there ends up being significant interest later. > > > SECURITY_ROW_LEVEL_DISABLED could have been okay. I removed an incomplete > > > implementation (e.g. didn't affect CASCADE constraints). Writing a full > > > one > > > would be a mammoth job, and for what? Setting the wrong SELECT policies > > > can > > > disrupt _any_ application logic; no foreign keys or FORCE ROW SECURITY > > > need be > > > involved. Protecting just foreign keys brings some value, but it will not > > > materially reduce the vigilance demanded of RLS policy authors and > > > reviewers. > > > > I have a hard time with this. We're not talking about the application > > logic in this case, we're talking about the guarantees which the > > database is making to the user, be it an application or an individual. > > If disabling policies has an effect, table owners must be feeding conflicting > requirements into the system. Violating policies during referential integrity > queries is not, in general, less serious than violating referential integrity > itself. Rules and triggers pose the same threat, and we let those break > referential integrity. I think the ideal in all of these cases is rather to > detect the conflict and raise an error. SECURITY_ROW_LEVEL_DISABLED and > SECURITY_NOFORCE_RLS send policies in a third direction, neither the beaten > path of rules/triggers nor the ideal. The agreement between the user and the system with regard to permissions and referential integrity is that referential integrity takes priority over permissions. Prior to FORCE and SECURITY_NOFORCE_RLS that is true for RLS. I don't believe it makes sense that adding FORCE would change that agreement, nor do I agree that the ideal would be for the system to throw errors when the permissions system would deny access during RI checks. While I appreciate that rules and triggers can break RI, the way RLS works is consistent with the ACL system and FORCE should be consistent with the ACL system and normal/non-FORCE RLS with regard to referential integrity. > > I've included a patch (the second in the set attached) which adds a > > SECURITY_NOFORCE_RLS bit which has a much tighter scope- it only applies > > after the regular owner check is done. This reduces the risk of it > > being set mistakenly dramatically, I believe. > > Yes, that's far safer than SECURITY_ROW_LEVEL_DISABLED. I assume the final > design will let table owners completely bypass FORCE ROW LEVEL SECURITY under > "SET row_security = off". If so, SECURITY_NOFORCE_RLS poses negligible risk. I've made that change. > Functions differing only in s/ = true/ = false/? ATExecEnableDisableTrigger() > is a better model for this. I've changed that to be one function. As an independent patch, I'll do the same for ATExecEnable/DisableRowSecurity. Apologies about the timing, I had intended to get this done yesterday. Barring further concerns, I'll push the attached later today with the necessary catversion bump. Thanks! Stephen
From 810c8f6ea717303a537b1c80337f98d3ad282645 Mon Sep 17 00:00:00 2001 From: Stephen Frost <sfr...@snowman.net> Date: Mon, 28 Sep 2015 11:28:15 -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/pg_class.h | 72 +++++----- src/include/miscadmin.h | 2 + src/include/nodes/parsenodes.h | 2 + .../modules/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 +++++++++++++++++++ 18 files changed, 536 insertions(+), 63 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 28bb480..97ef618 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1972,6 +1972,16 @@ </row> <row> + <entry><structfield>relforcerowsecurity</structfield></entry> + <entry><type>bool</type></entry> + <entry></entry> + <entry> + True if row level security (when enabled) will also apply to table owner; see + <link linkend="catalog-pg-policy"><structname>pg_policy</structname></link> catalog + </entry> + </row> + + <row> <entry><structfield>relispopulated</structfield></entry> <entry><type>bool</type></entry> <entry></entry> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 8e35cd9..aca40f5 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 <replaceable class="PARAMETER">name</replaceable> ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable> DISABLE ROW LEVEL SECURITY ENABLE ROW LEVEL SECURITY + FORCE ROW LEVEL SECURITY + NO FORCE ROW LEVEL SECURITY CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable> SET WITHOUT CLUSTER SET WITH OIDS @@ -434,6 +436,21 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> </varlistentry> <varlistentry> + <term><literal>NO FORCE</literal>/<literal>FORCE ROW LEVEL SECURITY</literal></term> + <listitem> + <para> + 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 + <xref linkend="SQL-CREATEPOLICY">. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>CLUSTER ON</literal></term> <listitem> <para> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index d04e94d..7d7d062 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 126b119..7668c9d 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; @@ -11067,6 +11078,35 @@ ATExecDisableRowSecurity(Relation rel) } /* + * 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 <name> OPTIONS (...) */ static void diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 417fb55..8bd5119 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 <name> FORCE ROW LEVEL SECURITY */ + | FORCE ROW LEVEL SECURITY + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_ForceRowSecurity; + $$ = (Node *)n; + } + /* ALTER TABLE <name> 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 018cb99..6569fdb 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 f0099d3..e871fef 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 eaf9d6e..6ce92af 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 ba1683b..36863df 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 b40b816..3c64a82 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 898f8b3..92ed6e2 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/pg_class.h b/src/include/catalog/pg_class.h index 25247b5..06d287e 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 ff695aa..77cf8d7 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 770866a..fdf19fa 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 e2dc4b5..4fca737 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 0363dfd..a050444 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 4414763..5f263f9 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 7f8772f..070c452 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -1289,6 +1289,141 @@ 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 -- RESET SESSION AUTHORIZATION; @@ -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); -- 1.9.1
signature.asc
Description: Digital signature