Commit 088c8336 authored by Stephen Frost's avatar Stephen Frost

ALTER TABLE .. FORCE ROW LEVEL SECURITY

To allow users to force RLS to always be applied, even for table owners,
add ALTER TABLE .. FORCE ROW LEVEL SECURITY.

row_security=off overrides FORCE ROW LEVEL SECURITY, to ensure pg_dump
output is complete (by default).

Also add SECURITY_NOFORCE_RLS context to avoid data corruption when
ALTER TABLE .. FORCE ROW SECURITY is being used. The
SECURITY_NOFORCE_RLS security context is used only during referential
integrity checks and is only considered in check_enable_rls() after we
have already checked that the current user is the owner of the relation
(which should always be the case during referential integrity checks).

Back-patch to 9.5 where RLS was added.
parent 16a70e30
......@@ -1971,6 +1971,16 @@
</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>
......
......@@ -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>
......
......@@ -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);
......
......@@ -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 (...)
*/
......
......@@ -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);
......
......@@ -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,
......
......@@ -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
......
......@@ -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,
......
......@@ -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);
......
......@@ -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 */
......
......@@ -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):"));
......
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201509161
#define CATALOG_VERSION_NO 201510042
#endif
......@@ -66,6 +66,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
bool relrowsecurity; /* row security is enabled or not */
bool relforcerowsecurity; /* row security forced for owners or not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
......@@ -95,37 +96,38 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
#define Natts_pg_class 30
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
#define Anum_pg_class_reloftype 4
#define Anum_pg_class_relowner 5
#define Anum_pg_class_relam 6
#define Anum_pg_class_relfilenode 7
#define Anum_pg_class_reltablespace 8
#define Anum_pg_class_relpages 9
#define Anum_pg_class_reltuples 10
#define Anum_pg_class_relallvisible 11
#define Anum_pg_class_reltoastrelid 12
#define Anum_pg_class_relhasindex 13
#define Anum_pg_class_relisshared 14
#define Anum_pg_class_relpersistence 15
#define Anum_pg_class_relkind 16
#define Anum_pg_class_relnatts 17
#define Anum_pg_class_relchecks 18
#define Anum_pg_class_relhasoids 19
#define Anum_pg_class_relhaspkey 20
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relrowsecurity 24
#define Anum_pg_class_relispopulated 25
#define Anum_pg_class_relreplident 26
#define Anum_pg_class_relfrozenxid 27
#define Anum_pg_class_relminmxid 28
#define Anum_pg_class_relacl 29
#define Anum_pg_class_reloptions 30
#define Natts_pg_class 31
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
#define Anum_pg_class_reloftype 4
#define Anum_pg_class_relowner 5
#define Anum_pg_class_relam 6
#define Anum_pg_class_relfilenode 7
#define Anum_pg_class_reltablespace 8
#define Anum_pg_class_relpages 9
#define Anum_pg_class_reltuples 10
#define Anum_pg_class_relallvisible 11
#define Anum_pg_class_reltoastrelid 12
#define Anum_pg_class_relhasindex 13
#define Anum_pg_class_relisshared 14
#define Anum_pg_class_relpersistence 15
#define Anum_pg_class_relkind 16
#define Anum_pg_class_relnatts 17
#define Anum_pg_class_relchecks 18
#define Anum_pg_class_relhasoids 19
#define Anum_pg_class_relhaspkey 20
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relrowsecurity 24
#define Anum_pg_class_relforcerowsecurity 25
#define Anum_pg_class_relispopulated 26
#define Anum_pg_class_relreplident 27
#define Anum_pg_class_relfrozenxid 28
#define Anum_pg_class_relminmxid 29
#define Anum_pg_class_relacl 30
#define Anum_pg_class_reloptions 31
/* ----------------
* initial contents of pg_class
......@@ -140,13 +142,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f t n 3 1 _null_ _null_ ));
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
......
......@@ -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);
......
......@@ -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;
......
......@@ -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;
......
......@@ -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);
......@@ -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
......
......@@ -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);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment