ALTER TABLE .. FORCE ROW LEVEL SECURITY
authorStephen Frost <sfrost@snowman.net>
Mon, 5 Oct 2015 01:05:08 +0000 (21:05 -0400)
committerStephen Frost <sfrost@snowman.net>
Mon, 5 Oct 2015 01:05:08 +0000 (21:05 -0400)
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.

19 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_table.sgml
src/backend/catalog/heap.c
src/backend/commands/tablecmds.c
src/backend/parser/gram.y
src/backend/utils/adt/ri_triggers.c
src/backend/utils/init/miscinit.c
src/backend/utils/misc/rls.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/psql/describe.c
src/include/catalog/catversion.h
src/include/catalog/pg_class.h
src/include/miscadmin.h
src/include/nodes/parsenodes.h
src/test/modules/test_ddl_deparse/test_ddl_deparse.c
src/test/regress/expected/rowsecurity.out
src/test/regress/output/misc.source
src/test/regress/sql/rowsecurity.sql

index 28bb480447eb68428ac76cb2fe0aa3c4b20e276e..97ef61860623eee3b0b50430d938971f85c3071f 100644 (file)
       </entry>
      </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>
index 8e35cd937bd5c1aa0ded0ce9a2622b636c711450..aca40f596a2b617be188369f4df93dfa73e6f486 100644 (file)
@@ -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
@@ -433,6 +435,21 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </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>
index d04e94d74f3c1266c7f644dfc1ef390ce14323ce..7d7d062c068f5e425f5f4147f583ea3982c47758 100644 (file)
@@ -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);
index 126b11923f06c53f7afd0fad08c36e51c8cde7af..7668c9d771f893847826fcddbb1441b41f870acb 100644 (file)
@@ -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 <name> OPTIONS (...)
  */
index 417fb55bc4879e8681c99c2a49ae876459d88704..8bd511952c9fd9706bc2c262e7f72154c91f99c6 100644 (file)
@@ -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);
index 018cb99e8cdef488f8a903eb3f4dadf1bc7d8987..6569fdba1642e0897320394698bed5dbc7370be5 100644 (file)
@@ -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,
index f0099d31fad0b482a9df7632173fef94b928db91..e871fef7faa19c894d2caa3e8dd7f09de4602eaf 100644 (file)
@@ -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
index eaf9d6e66ffa60b8f0b43cc230580f5be7e6bc1b..6ce92af019948dfba7356654c724679af524bb21 100644 (file)
@@ -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,
index ba1683b79a087c1b4c7fabece70cc3a8c56b7ff0..36863df56881470597790a38a155143c27c643aa 100644 (file)
@@ -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);
 
index b40b816bee02fd2b8a1628add4f7a252ab4da965..3c64a82dba69c02c6ec0f148418c0a2e7906b358 100644 (file)
@@ -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 */
index 898f8b39cdcbe9b03725b5c7a352cf9410e3fab8..92ed6e297038a7a7b2a6013ed97fdc085d916bc5 100644 (file)
@@ -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):"));
 
index 9730561967bf6b96d93355ee40aca3f94907fab8..8afe5cc801a687cfe68459e9e1b1b9618f953ee9 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201509161
+#define CATALOG_VERSION_NO     201510042
 
 #endif
index 25247b54d10db9ada0372cf6714ccca9b94278d1..06d287eb54b283e1cb0cefcb78622d22965d1054 100644 (file)
@@ -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 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 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 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("");
 
 
index ff695aae276604157e72b9cfc573bee82bfb735c..77cf8d71de750175ad894d6962d76399a267486f 100644 (file)
@@ -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);
index 770866a269656466b6ba83d4a5cc1770090124b6..fdf19fac888007adb0fd0fb0a26bf4f226b543e5 100644 (file)
@@ -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;
 
index e2dc4b5c768ce4d5d47c0646c1e1c2a1fa45cfd3..4fca7373b0a9e5ab783acbef7bab77dd2ae0c289 100644 (file)
@@ -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;
index 0363dfd07ff5edde7d9f50b8cfa244effde4becb..a050444cd0c50ca5ce91b89243c24f81a3963d97 100644 (file)
@@ -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);
index 441476381886bd00e73fd6ccff831de6579d1501..5f263f9a3a1b7c7ce816d7385a764884aa442d13 100644 (file)
@@ -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 
index 7f8772fa26cbaaf070b2c77c34f060758bf12695..070c452a77ceadb5d3185305e54b5998f1b86b13 100644 (file)
@@ -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);