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);