Code review for row security.

Buildfarm member tick identified an issue where the policies in the
relcache for a relation were were being replaced underneath a running
query, leading to segfaults while processing the policies to be added
to a query.  Similar to how TupleDesc RuleLocks are handled, add in a
equalRSDesc() function to check if the policies have actually changed
and, if not, swap back the rsdesc field (using the original instead of
the temporairly built one; the whole structure is swapped and then
specific fields swapped back).  This now passes a CLOBBER_CACHE_ALWAYS
for me and should resolve the buildfarm error.

In addition to addressing this, add a new chapter in Data Definition
under Privileges which explains row security and provides examples of
its usage, change \d to always list policies (even if row security is
disabled- but note that it is disabled, or enabled with no policies),
rework check_role_for_policy (it really didn't need the entire policy,
but it did need to be using has_privs_of_role()), and change the field
in pg_class to relrowsecurity from relhasrowsecurity, based on
Heikki's suggestion.  Also from Heikki, only issue SET ROW_SECURITY in
pg_restore when talking to a 9.5+ server, list Bypass RLS in \du, and
document --enable-row-security options for pg_dump and pg_restore.

Lastly, fix a number of minor whitespace and typo issues from Heikki,
Dimitri, add a missing #include, per Peter E, fix a few minor
variable-assigned-but-not-used and resource leak issues from Coverity
and add tab completion for role attribute bypassrls as well.
This commit is contained in:
Stephen Frost 2014-09-24 16:32:22 -04:00
parent 3f6f9260e3
commit 6550b901fe
24 changed files with 442 additions and 125 deletions

View File

@ -1941,8 +1941,9 @@
</row> </row>
<row> <row>
<entry><structfield>relhasrowsecurity</structfield></entry> <entry><structfield>relrowsecurity</structfield></entry>
<entry><type>bool</type></entry> <entry><type>bool</type></entry>
<entry></entry>
<entry> <entry>
True if table has row-security enabled; see True if table has row-security enabled; see
<link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
@ -5415,7 +5416,7 @@
<note> <note>
<para> <para>
<literal>pg_class.relhasrowsecurity</literal> <literal>pg_class.relrowsecurity</literal>
True if the table has row-security enabled. True if the table has row-security enabled.
Must be true if the table has a row-security policy in this catalog. Must be true if the table has a row-security policy in this catalog.
</para> </para>
@ -9228,10 +9229,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry>True if table has (or once had) triggers</entry> <entry>True if table has (or once had) triggers</entry>
</row> </row>
<row> <row>
<entry><structfield>hasrowsecurity</structfield></entry> <entry><structfield>rowsecurity</structfield></entry>
<entry><type>boolean</type></entry> <entry><type>boolean</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry> <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relrowsecurity</literal></entry>
<entry>True if table has row security enabled</entry> <entry>True if row security is enabled on the table</entry>
</row> </row>
</tbody> </tbody>
</tgroup> </tgroup>

View File

@ -5457,9 +5457,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
<para> <para>
The allowed values of <varname>row_security</> are The allowed values of <varname>row_security</> are
<literal>on</> (apply normally- not to superuser or table owner), <literal>on</> (apply normally - not to superuser or table owner),
<literal>off</> (fail if row security would be applied), and <literal>off</> (fail if row security would be applied), and
<literal>force</> (apply always- even to superuser and table owner). <literal>force</> (apply always - even to superuser and table owner).
</para> </para>
<para> <para>

View File

@ -1508,6 +1508,174 @@ REVOKE ALL ON accounts FROM PUBLIC;
</para> </para>
</sect1> </sect1>
<sect1 id="ddl-rowsecurity">
<title>Row Security Policies</title>
<indexterm zone="ddl-rowsecurity">
<primary>rowsecurity</primary>
</indexterm>
<indexterm zone="ddl-rowsecurity">
<primary>rls</primary>
</indexterm>
<indexterm>
<primary>policies</primary>
<see>policy</see>
</indexterm>
<indexterm zone="ddl-rowsecurity">
<primary>POLICY</primary>
</indexterm>
<para>
In addition to the <xref linkend="ddl-priv"> system available through
<xref linkend="sql-grant">, tables can have row security policies
which limit the rows returned for normal queries and rows which can
be added through data modification commands. By default, tables do
not have any policies and all rows are visible and able to be added,
subject to the regular <xref linkend="ddl-priv"> system. This is
also known to as Row Level Security.
</para>
<para>
When row security is enabled on a table with
<xref linkend="sql-altertable">, all normal access to the table
(excluding the owner) for selecting rows or adding rows must be through
a policy. If no policy exists for the table, a default-deny policy is
used and no rows are visible or can be added. Privileges which operate
at the whole table level such as <literal>TRUNCATE</>, and
<literal>REFERENCES</> are not subject to row security.
</para>
<para>
Row security policies can be specific to commands, or to roles, or to
both. The commands available are <literal>SELECT</>, <literal>INSERT</>,
<literal>UPDATE</>, and <literal>DELETE</>. Multiple roles can be
assigned to a given policy and normal role membership and inheiritance
rules apply.
</para>
<para>
To specify which rows are visible and what rows can be added to the
table with row security, an expression is required which returns a
boolean result. This expression will be evaluated for each row prior
to other conditionals or functions which are part of the query. The
one exception to this rule are <literal>leakproof</literal> functions,
which are guaranteed to not leak information. Two expressions may be
specified to provide independent control over the rows which are
visible and the rows which are allowed to be added. The expression
is run as part of the query and with the privileges of the user
running the query, however, security definer functions can be used in
the expression.
</para>
<para>
Enabling and disabling row security, as well as adding policies to a
table, is always the privilege of the owner only.
</para>
<para>
Policies are created using the <xref linkend="sql-createpolicy">
command, altered using the <xref linkend="sql-alterpolicy"> command,
and dropped using the <xref linkend="sql-droppolicy"> command. To
enable and disable row security for a given table, use the
<xref linkend="sql-altertable"> command.
</para>
<para>
The table owners and superusers bypass the row security system when
querying a table, by default. Row security can be enabled for
superusers and table owners by setting
<xref linkend="guc-row-security"> to <literal>force</literal>. Any
user can request that row security be bypassed by setting
<xref linkend="guc-row-security"> to <literal>off</literal>. If
the user does not have privileges to bypass row security when
querying a given table then an error will be returned instead. Other
users can be granted the ability to bypass the row security system
with the <literal>BYPASSRLS</literal> role attribute. This
attribute can only be set by a superuser.
</para>
<para>
Each policy has a name and multiple policies can be defined for a
table. As policies are table-specific, each policy for a table must
have a unique name. Different tables may have policies with the
same name.
</para>
<para>
When multiple policies apply to a given query, they are combined using
<literal>OR</literal>, similar to how a given role has the privileges
of all roles which they are a member of.
</para>
<para>
Referential integrity checks, such as unique or primary key constraints
and foreign key references, will bypass row security to ensure that
data integrity is maintained. Care must be taken when developing
schemas and row level policies to avoid a "covert channel" leak of
information through these referntial integrity checks.
</para>
<para>
To enable row security for a table,
the <command>ALTER TABLE</command> is used. For example, to enable
row level security for the table accounts, use:
</para>
<programlisting>
-- Create the table first
CREATE TABLE accounts (manager text, company text, contact_email text);
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
</programlisting>
<para>
To create a policy on the account relation to allow the managers role
to view the rows of their accounts, the <command>CREATE POLICY</command>
command can be used:
</para>
<programlisting>
CREATE POLICY account_managers ON accounts TO managers
USING (manager = current_user);
</programlisting>
<para>
If no role is specified, or the special <quote>user</quote> name
<literal>PUBLIC</literal> is used, then the policy applies to all
users on the system. To allow all users to view their own row in
a user table, a simple policy can be used:
</para>
<programlisting>
CREATE POLICY user_policy ON users
USING (user = current_user);
</programlisting>
<para>
To use a different policy for rows which are being added to the
table from those rows which are visible, the WITH CHECK clause
can be used. This would allow all users to view all rows in the
users table, but only modify their own:
</para>
<programlisting>
CREATE POLICY user_policy ON users
USING (true)
WITH CHECK (user = current_user);
</programlisting>
<para>
Row security can be disabled with the <command>ALTER TABLE</command>
also. Note that disabling row security does not remove the
policies which are defined on the table, they are simply ignored
and all rows are visible and able to be added, subject to the
normal privileges system.
</para>
</sect1>
<sect1 id="ddl-schemas"> <sect1 id="ddl-schemas">
<title>Schemas</title> <title>Schemas</title>

View File

@ -429,7 +429,7 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
These forms control the application of row security policies belonging These forms control the application of row security policies belonging
to the table. If enabled and no policies exist for the table, then a to the table. If enabled and no policies exist for the table, then a
default-deny policy is applied. Note that policies can exist for a table default-deny policy is applied. Note that policies can exist for a table
even if row level security is disabled- in this case, the policies will even if row level security is disabled - in this case, the policies will
NOT be applied and the policies will be ignored. NOT be applied and the policies will be ignored.
See also See also
<xref linkend="SQL-CREATEPOLICY">. <xref linkend="SQL-CREATEPOLICY">.

View File

@ -240,7 +240,7 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
</varlistentry> </varlistentry>
<varlistentry id="SQL-CREATEPOLICY-UPDATE"> <varlistentry id="SQL-CREATEPOLICY-UPDATE">
<term><literal>DELETE</></term> <term><literal>UPDATE</></term>
<listitem> <listitem>
<para> <para>
Using <literal>UPDATE</literal> for a policy means that it will apply Using <literal>UPDATE</literal> for a policy means that it will apply

View File

@ -687,6 +687,23 @@ PostgreSQL documentation
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--enable-row-security</></term>
<listitem>
<para>
This option is relevant only when dumping the contents of a table
which has row security. By default, pg_dump will set
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
that all data is dumped from the table. If the user does not have
sufficient privileges to bypass row security, then an error is thrown.
This parameter instructs <application>pg_dump</application> to set
row_security to 'ON' instead, allowing the user to dump the contents
of the table which they have access to.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term> <term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term>
<listitem> <listitem>

View File

@ -490,6 +490,29 @@
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--enable-row-security</></term> <listitem>
<para>
This option is relevant only when restoring the contents of a table
which has row security. By default, pg_restore will set
<literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
that all data is restored in to the table. If the user does not have
sufficient privileges to bypass row security, then an error is thrown.
This parameter instructs <application>pg_restore</application> to set
row_security to 'ON' instead, allowing the user to attempt to restore
the contents of the table with row security enabled. This may still
fail if the user does not have the right to insert the rows from the
dump into the table.
</para>
<para>
Note that this option currently also requires the dump be in INSERT
format as COPY TO does not support row security.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--if-exists</option></term> <term><option>--if-exists</option></term>
<listitem> <listitem>

View File

@ -799,7 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey); values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules); values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity); values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated); values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident); values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);

View File

@ -119,7 +119,7 @@ CREATE VIEW pg_tables AS
C.relhasindex AS hasindexes, C.relhasindex AS hasindexes,
C.relhasrules AS hasrules, C.relhasrules AS hasrules,
C.relhastriggers AS hastriggers, C.relhastriggers AS hastriggers,
C.relhasrowsecurity AS hasrowsecurity C.relrowsecurity AS rowsecurity
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace) LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
WHERE C.relkind = 'r'; WHERE C.relkind = 'r';

View File

@ -108,7 +108,7 @@ parse_row_security_command(const char *cmd_name)
char cmd; char cmd;
if (!cmd_name) if (!cmd_name)
elog(ERROR, "Unregonized command."); elog(ERROR, "unregonized command");
if (strcmp(cmd_name, "all") == 0) if (strcmp(cmd_name, "all") == 0)
cmd = 0; cmd = 0;
@ -121,8 +121,7 @@ parse_row_security_command(const char *cmd_name)
else if (strcmp(cmd_name, "delete") == 0) else if (strcmp(cmd_name, "delete") == 0)
cmd = ACL_DELETE_CHR; cmd = ACL_DELETE_CHR;
else else
elog(ERROR, "Unregonized command."); elog(ERROR, "unregonized command");
/* error unrecognized command */
return cmd; return cmd;
} }
@ -422,8 +421,8 @@ RemovePolicyById(Oid policy_id)
heap_close(rel, AccessExclusiveLock); heap_close(rel, AccessExclusiveLock);
/* /*
* Note that, unlike some of the other flags in pg_class, relhasrowsecurity * Note that, unlike some of the other flags in pg_class, relrowsecurity
* is not just an indication of if policies exist. When relhasrowsecurity * is not just an indication of if policies exist. When relrowsecurity
* is set (which can be done directly by the user or indirectly by creating * is set (which can be done directly by the user or indirectly by creating
* a policy on the table), then all access to the relation must be through * a policy on the table), then all access to the relation must be through
* a policy. If no policy is defined for the relation then a default-deny * a policy. If no policy is defined for the relation then a default-deny
@ -484,7 +483,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL) if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("Only WITH CHECK expression allowed for INSERT"))); errmsg("only WITH CHECK expression allowed for INSERT")));
/* Collect role ids */ /* Collect role ids */
@ -731,7 +730,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
if (!HeapTupleIsValid(rsec_tuple)) if (!HeapTupleIsValid(rsec_tuple))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT), (errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("policy '%s' for does not exist on table %s", errmsg("policy \"%s\" on table \"%s\" does not exist",
stmt->policy_name, stmt->policy_name,
RelationGetRelationName(target_table)))); RelationGetRelationName(target_table))));
@ -850,7 +849,7 @@ rename_policy(RenameStmt *stmt)
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock); pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
/* First pass- check for conflict */ /* First pass -- check for conflict */
/* Add key - row security relation id. */ /* Add key - row security relation id. */
ScanKeyInit(&skey[0], ScanKeyInit(&skey[0],
@ -868,7 +867,7 @@ rename_policy(RenameStmt *stmt)
RowSecurityRelidPolnameIndexId, true, NULL, 2, RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey); skey);
if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan))) if (HeapTupleIsValid(systable_getnext(sscan)))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT), (errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("row-policy \"%s\" for table \"%s\" already exists", errmsg("row-policy \"%s\" for table \"%s\" already exists",

View File

@ -10647,7 +10647,7 @@ ATExecEnableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid); elog(ERROR, "cache lookup failed for relation %u", relid);
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true; ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
simple_heap_update(pg_class, &tuple->t_self, tuple); simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */ /* keep catalog indexes current */
@ -10674,7 +10674,7 @@ ATExecDisableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple)) if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid); elog(ERROR, "cache lookup failed for relation %u", relid);
((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false; ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
simple_heap_update(pg_class, &tuple->t_self, tuple); simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */ /* keep catalog indexes current */

View File

@ -61,7 +61,7 @@ static void process_policies(List *policies, int rt_index,
Expr **final_qual, Expr **final_qual,
Expr **final_with_check_qual, Expr **final_with_check_qual,
bool *hassublinks); bool *hassublinks);
static bool check_role_for_policy(RowSecurityPolicy *policy, Oid user_id); static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
/* /*
* hook to allow extensions to apply their own security policy * hook to allow extensions to apply their own security policy
@ -177,7 +177,7 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
* all of them OR'd together. However, to avoid the situation of an * all of them OR'd together. However, to avoid the situation of an
* extension granting more access to a table than the internal policies * extension granting more access to a table than the internal policies
* would allow, the extension's policies are AND'd with the internal * would allow, the extension's policies are AND'd with the internal
* policies. In other words- extensions can only provide further * policies. In other words - extensions can only provide further
* filtering of the result set (or further reduce the set of records * filtering of the result set (or further reduce the set of records
* allowed to be added). * allowed to be added).
* *
@ -305,7 +305,8 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
policy = (RowSecurityPolicy *) lfirst(item); policy = (RowSecurityPolicy *) lfirst(item);
/* Always add ALL policies, if they exist. */ /* Always add ALL policies, if they exist. */
if (policy->cmd == '\0' && check_role_for_policy(policy, user_id)) if (policy->cmd == '\0' &&
check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies); policies = lcons(policy, policies);
/* Build the list of policies to return. */ /* Build the list of policies to return. */
@ -313,23 +314,23 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
{ {
case CMD_SELECT: case CMD_SELECT:
if (policy->cmd == ACL_SELECT_CHR if (policy->cmd == ACL_SELECT_CHR
&& check_role_for_policy(policy, user_id)) && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies); policies = lcons(policy, policies);
break; break;
case CMD_INSERT: case CMD_INSERT:
/* If INSERT then only need to add the WITH CHECK qual */ /* If INSERT then only need to add the WITH CHECK qual */
if (policy->cmd == ACL_INSERT_CHR if (policy->cmd == ACL_INSERT_CHR
&& check_role_for_policy(policy, user_id)) && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies); policies = lcons(policy, policies);
break; break;
case CMD_UPDATE: case CMD_UPDATE:
if (policy->cmd == ACL_UPDATE_CHR if (policy->cmd == ACL_UPDATE_CHR
&& check_role_for_policy(policy, user_id)) && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies); policies = lcons(policy, policies);
break; break;
case CMD_DELETE: case CMD_DELETE:
if (policy->cmd == ACL_DELETE_CHR if (policy->cmd == ACL_DELETE_CHR
&& check_role_for_policy(policy, user_id)) && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies); policies = lcons(policy, policies);
break; break;
default: default:
@ -473,7 +474,7 @@ check_enable_rls(Oid relid, Oid checkAsUser)
{ {
HeapTuple tuple; HeapTuple tuple;
Form_pg_class classform; Form_pg_class classform;
bool relhasrowsecurity; bool relrowsecurity;
Oid user_id = checkAsUser ? checkAsUser : GetUserId(); Oid user_id = checkAsUser ? checkAsUser : GetUserId();
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
@ -482,12 +483,12 @@ check_enable_rls(Oid relid, Oid checkAsUser)
classform = (Form_pg_class) GETSTRUCT(tuple); classform = (Form_pg_class) GETSTRUCT(tuple);
relhasrowsecurity = classform->relhasrowsecurity; relrowsecurity = classform->relrowsecurity;
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
/* Nothing to do if the relation does not have RLS */ /* Nothing to do if the relation does not have RLS */
if (!relhasrowsecurity) if (!relrowsecurity)
return RLS_NONE; return RLS_NONE;
/* /*
@ -537,19 +538,19 @@ check_enable_rls(Oid relid, Oid checkAsUser)
* check_role_for_policy - * check_role_for_policy -
* determines if the policy should be applied for the current role * determines if the policy should be applied for the current role
*/ */
bool static bool
check_role_for_policy(RowSecurityPolicy *policy, Oid user_id) check_role_for_policy(ArrayType *policy_roles, Oid user_id)
{ {
int i; int i;
Oid *roles = (Oid *) ARR_DATA_PTR(policy->roles); Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
/* Quick fall-thru for policies applied to all roles */ /* Quick fall-thru for policies applied to all roles */
if (roles[0] == ACL_ID_PUBLIC) if (roles[0] == ACL_ID_PUBLIC)
return true; return true;
for (i = 0; i < ARR_DIMS(policy->roles)[0]; i++) for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
{ {
if (is_member_of_role(user_id, roles[i])) if (has_privs_of_role(user_id, roles[i]))
return true; return true;
} }

View File

@ -2309,9 +2309,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* have RLS enabled. * have RLS enabled.
*/ */
if (!has_bypassrls_privilege(GetUserId()) && if (!has_bypassrls_privilege(GetUserId()) &&
((pk_rel->rd_rel->relhasrowsecurity && ((pk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(pkrte->relid, GetUserId())) || !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
(fk_rel->rd_rel->relhasrowsecurity && (fk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(fkrte->relid, GetUserId())))) !pg_class_ownercheck(fkrte->relid, GetUserId()))))
return false; return false;

View File

@ -847,6 +847,87 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
return true; return true;
} }
/*
* equalPolicy
*
* Determine whether two policies are equivalent
*/
static bool
equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2)
{
int i;
Oid *r1,
*r2;
if (policy1 != NULL)
{
if (policy2 == NULL)
return false;
if (policy1->rsecid != policy2->rsecid)
return false;
if (policy1->cmd != policy2->cmd)
return false;
if (policy1->hassublinks != policy2->hassublinks);
return false;
if (strcmp(policy1->policy_name,policy2->policy_name) != 0)
return false;
if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0])
return false;
r1 = (Oid *) ARR_DATA_PTR(policy1->roles);
r2 = (Oid *) ARR_DATA_PTR(policy2->roles);
for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++)
{
if (r1[i] != r2[i])
return false;
}
if (!equal(policy1->qual, policy1->qual))
return false;
if (!equal(policy1->with_check_qual, policy2->with_check_qual))
return false;
}
else if (policy2 != NULL)
return false;
return true;
}
/*
* equalRSDesc
*
* Determine whether two RowSecurityDesc's are equivalent
*/
static bool
equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
{
ListCell *lc,
*rc;
if (rsdesc1 == NULL && rsdesc2 == NULL)
return true;
if ((rsdesc1 != NULL && rsdesc2 == NULL) ||
(rsdesc1 == NULL && rsdesc2 != NULL))
return false;
if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies))
return false;
/* RelationBuildRowSecurity should build policies in order */
forboth(lc, rsdesc1->policies, rc, rsdesc2->policies)
{
RowSecurityPolicy *l = (RowSecurityPolicy *) lfirst(lc);
RowSecurityPolicy *r = (RowSecurityPolicy *) lfirst(rc);
if (!equalPolicy(l,r))
return false;
}
return false;
}
/* /*
* RelationBuildDesc * RelationBuildDesc
@ -967,7 +1048,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else else
relation->trigdesc = NULL; relation->trigdesc = NULL;
if (relation->rd_rel->relhasrowsecurity) if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation); RelationBuildRowSecurity(relation);
else else
relation->rsdesc = NULL; relation->rsdesc = NULL;
@ -2104,6 +2185,7 @@ RelationClearRelation(Relation relation, bool rebuild)
Oid save_relid = RelationGetRelid(relation); Oid save_relid = RelationGetRelid(relation);
bool keep_tupdesc; bool keep_tupdesc;
bool keep_rules; bool keep_rules;
bool keep_policies;
/* Build temporary entry, but don't link it into hashtable */ /* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false); newrel = RelationBuildDesc(save_relid, false);
@ -2117,6 +2199,7 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rsdesc, newrel->rsdesc);
/* /*
* Perform swapping of the relcache entry contents. Within this * Perform swapping of the relcache entry contents. Within this
@ -2165,6 +2248,8 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(RuleLock *, rd_rules); SWAPFIELD(RuleLock *, rd_rules);
SWAPFIELD(MemoryContext, rd_rulescxt); SWAPFIELD(MemoryContext, rd_rulescxt);
} }
if (keep_policies)
SWAPFIELD(RowSecurityDesc *, rsdesc);
/* toast OID override must be preserved */ /* toast OID override must be preserved */
SWAPFIELD(Oid, rd_toastoid); SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */ /* pgstat_info must be preserved */
@ -3345,11 +3430,11 @@ RelationCacheInitializePhase3(void)
/* /*
* Re-load the row security policies if the relation has them, since * Re-load the row security policies if the relation has them, since
* they are not preserved in the cache. Note that we can never NOT * they are not preserved in the cache. Note that we can never NOT
* have a policy while relhasrowsecurity is true- * have a policy while relrowsecurity is true,
* RelationBuildRowSecurity will create a single default-deny policy * RelationBuildRowSecurity will create a single default-deny policy
* if there is no policy defined in pg_rowsecurity. * if there is no policy defined in pg_rowsecurity.
*/ */
if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL) if (relation->rd_rel->relrowsecurity && relation->rsdesc == NULL)
{ {
RelationBuildRowSecurity(relation); RelationBuildRowSecurity(relation);

View File

@ -376,10 +376,13 @@ RestoreArchive(Archive *AHX)
/* /*
* Enable row-security if necessary. * Enable row-security if necessary.
*/ */
if (!ropt->enable_row_security) if (PQserverVersion(AH->connection) >= 90500)
ahprintf(AH, "SET row_security = off;\n"); {
else if (!ropt->enable_row_security)
ahprintf(AH, "SET row_security = on;\n"); ahprintf(AH, "SET row_security = off;\n");
else
ahprintf(AH, "SET row_security = on;\n");
}
/* /*
* Establish important parameter values right away. * Establish important parameter values right away.

View File

@ -2777,7 +2777,7 @@ dumpBlobs(Archive *fout, void *arg)
void void
getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables) getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
{ {
PQExpBuffer query = createPQExpBuffer(); PQExpBuffer query;
PGresult *res; PGresult *res;
RowSecurityInfo *rsinfo; RowSecurityInfo *rsinfo;
int i_oid; int i_oid;
@ -2792,6 +2792,8 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
if (fout->remoteVersion < 90500) if (fout->remoteVersion < 90500)
return; return;
query = createPQExpBuffer();
for (i = 0; i < numTables; i++) for (i = 0; i < numTables; i++)
{ {
TableInfo *tbinfo = &tblinfo[i]; TableInfo *tbinfo = &tblinfo[i];
@ -2809,7 +2811,7 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
* We represent RLS enabled on a table by creating RowSecurityInfo * We represent RLS enabled on a table by creating RowSecurityInfo
* object with an empty policy. * object with an empty policy.
*/ */
if (tbinfo->hasrowsec) if (tbinfo->rowsec)
{ {
/* /*
* Note: use tableoid 0 so that this object won't be mistaken for * Note: use tableoid 0 so that this object won't be mistaken for
@ -4534,7 +4536,7 @@ getTables(Archive *fout, int *numTables)
int i_relhastriggers; int i_relhastriggers;
int i_relhasindex; int i_relhasindex;
int i_relhasrules; int i_relhasrules;
int i_relhasrowsec; int i_relrowsec;
int i_relhasoids; int i_relhasoids;
int i_relfrozenxid; int i_relfrozenxid;
int i_relminmxid; int i_relminmxid;
@ -4588,7 +4590,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relhasrowsecurity, " "c.relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, " "tc.relminmxid AS tminmxid, "
@ -4629,7 +4631,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, " "tc.relminmxid AS tminmxid, "
@ -4670,7 +4672,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, " "tc.relminmxid AS tminmxid, "
@ -4711,7 +4713,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, " "0 AS tminmxid, "
@ -4750,7 +4752,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, " "0 AS tminmxid, "
@ -4788,7 +4790,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, " "c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, " "0 AS tminmxid, "
@ -4826,7 +4828,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, " "(%s c.relowner) AS rolname, "
"c.relchecks, (c.reltriggers <> 0) AS relhastriggers, " "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, " "0 AS tminmxid, "
@ -4864,7 +4866,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, " "(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, " "relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, " "relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid," "0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid," "0 AS tfrozenxid, 0 AS tminmxid,"
@ -4901,7 +4903,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, " "(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, " "relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, " "relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid," "0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid," "0 AS tfrozenxid, 0 AS tminmxid,"
@ -4934,7 +4936,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, " "(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, " "relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, " "relhasindex, relhasrules, relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid," "0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid," "0 AS tfrozenxid, 0 AS tminmxid,"
@ -4962,7 +4964,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, " "relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, " "relhasindex, relhasrules, "
"'t'::bool AS relhasoids, " "'t'::bool AS relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid," "0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid," "0 AS tfrozenxid, 0 AS tminmxid,"
@ -5000,7 +5002,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, " "relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, " "relhasindex, relhasrules, "
"'t'::bool AS relhasoids, " "'t'::bool AS relhasoids, "
"'f'::bool AS relhasrowsecurity, " "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid," "0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, " "0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid," "0 AS tfrozenxid, 0 AS tminmxid,"
@ -5048,7 +5050,7 @@ getTables(Archive *fout, int *numTables)
i_relhastriggers = PQfnumber(res, "relhastriggers"); i_relhastriggers = PQfnumber(res, "relhastriggers");
i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules"); i_relhasrules = PQfnumber(res, "relhasrules");
i_relhasrowsec = PQfnumber(res, "relhasrowsecurity"); i_relrowsec = PQfnumber(res, "relrowsecurity");
i_relhasoids = PQfnumber(res, "relhasoids"); i_relhasoids = PQfnumber(res, "relhasoids");
i_relfrozenxid = PQfnumber(res, "relfrozenxid"); i_relfrozenxid = PQfnumber(res, "relfrozenxid");
i_relminmxid = PQfnumber(res, "relminmxid"); i_relminmxid = PQfnumber(res, "relminmxid");
@ -5100,7 +5102,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0); tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0); tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0); tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0);
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "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].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident)); tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));

View File

@ -246,7 +246,7 @@ typedef struct _tableInfo
bool hasindex; /* does it have any indexes? */ bool hasindex; /* does it have any indexes? */
bool hasrules; /* does it have any rules? */ bool hasrules; /* does it have any rules? */
bool hastriggers; /* does it have any triggers? */ bool hastriggers; /* does it have any triggers? */
bool hasrowsec; /* does it have any row-security policy? */ bool rowsec; /* does it have any row-security policy? */
bool hasoids; /* does it have OIDs? */ bool hasoids; /* does it have OIDs? */
uint32 frozenxid; /* for restore frozen xid */ uint32 frozenxid; /* for restore frozen xid */
uint32 minmxid; /* for restore min multi xid */ uint32 minmxid; /* for restore min multi xid */

View File

@ -463,7 +463,7 @@ usage(const char *progname)
printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n")); printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n"));
printf(_(" -1, --single-transaction restore as a single transaction\n")); printf(_(" -1, --single-transaction restore as a single transaction\n"));
printf(_(" --disable-triggers disable triggers during data-only restore\n")); printf(_(" --disable-triggers disable triggers during data-only restore\n"));
printf(_(" --enable-row-security enable row level security\n")); printf(_(" --enable-row-security enable row level security\n"));
printf(_(" --if-exists use IF EXISTS when dropping objects\n")); printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n" printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n"
" created\n")); " created\n"));

View File

@ -1204,7 +1204,7 @@ describeOneTableDetails(const char *schemaname,
bool hasindex; bool hasindex;
bool hasrules; bool hasrules;
bool hastriggers; bool hastriggers;
bool hasrowsecurity; bool rowsecurity;
bool hasoids; bool hasoids;
Oid tablespace; Oid tablespace;
char *reloptions; char *reloptions;
@ -1230,7 +1230,7 @@ describeOneTableDetails(const char *schemaname,
{ {
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasrowsecurity, c.relhasoids, " "c.relhastriggers, c.relrowsecurity, c.relhasoids, "
"%s, c.reltablespace, " "%s, c.reltablespace, "
"CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
"c.relpersistence, c.relreplident\n" "c.relpersistence, c.relreplident\n"
@ -1355,7 +1355,7 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0; tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0; tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0; tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0; tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0; tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
tableinfo.reloptions = (pset.sversion >= 80200) ? tableinfo.reloptions = (pset.sversion >= 80200) ?
pg_strdup(PQgetvalue(res, 0, 7)) : NULL; pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
@ -1998,18 +1998,17 @@ describeOneTableDetails(const char *schemaname,
PQclear(result); PQclear(result);
} }
/* print any row-level policies */
if (pset.sversion >= 90500) if (pset.sversion >= 90500)
{
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"", ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
gettext_noop("Row-security")); gettext_noop("Row-security"));
if (verbose && pset.sversion >= 90500)
appendPQExpBuffer(&buf,
"\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
/* print any row-level policies */ if (verbose)
if (tableinfo.hasrowsecurity) appendPQExpBuffer(&buf,
{ "\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
printfPQExpBuffer(&buf, printfPQExpBuffer(&buf,
"SELECT rs.rsecpolname,\n" "SELECT rs.rsecpolname,\n"
"CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n" "CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n"
@ -2019,41 +2018,53 @@ describeOneTableDetails(const char *schemaname,
"FROM pg_catalog.pg_rowsecurity rs\n" "FROM pg_catalog.pg_rowsecurity rs\n"
"WHERE rs.rsecrelid = '%s' ORDER BY 1;", "WHERE rs.rsecrelid = '%s' ORDER BY 1;",
oid); oid);
result = PSQLexec(buf.data, false); result = PSQLexec(buf.data, false);
if (!result) if (!result)
goto error_return; goto error_return;
else else
tuples = PQntuples(result); tuples = PQntuples(result);
if (tuples > 0) /*
{ * Handle cases where RLS is enabled and there are policies,
* or there aren't policies, or RLS isn't enabled but there
* are policies
*/
if (tableinfo.rowsecurity && tuples > 0)
printTableAddFooter(&cont, _("Policies:")); printTableAddFooter(&cont, _("Policies:"));
for (i = 0; i < tuples; i++)
if (tableinfo.rowsecurity && tuples == 0)
printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)"));
if (!tableinfo.rowsecurity && tuples > 0)
printTableAddFooter(&cont, _("Policies (Row Security Disabled):"));
/* Might be an empty set - that's ok */
for (i = 0; i < tuples; i++)
{
printfPQExpBuffer(&buf, " POLICY \"%s\"",
PQgetvalue(result, i, 0));
if (!PQgetisnull(result, i, 4))
appendPQExpBuffer(&buf, " (%s)",
PQgetvalue(result, i, 4));
if (!PQgetisnull(result, i, 2))
appendPQExpBuffer(&buf, " EXPRESSION %s",
PQgetvalue(result, i, 2));
if (!PQgetisnull(result, i, 3))
appendPQExpBuffer(&buf, " WITH CHECK %s",
PQgetvalue(result, i, 3));
printTableAddFooter(&cont, buf.data);
if (!PQgetisnull(result, i, 1))
{ {
printfPQExpBuffer(&buf, " POLICY \"%s\"", printfPQExpBuffer(&buf, " APPLIED TO %s",
PQgetvalue(result, i, 0)); PQgetvalue(result, i, 1));
if (!PQgetisnull(result, i, 4))
appendPQExpBuffer(&buf, " (%s)",
PQgetvalue(result, i, 4));
if (!PQgetisnull(result, i, 2))
appendPQExpBuffer(&buf, " EXPRESSION %s",
PQgetvalue(result, i, 2));
if (!PQgetisnull(result, i, 3))
appendPQExpBuffer(&buf, " WITH CHECK %s",
PQgetvalue(result, i, 3));
printTableAddFooter(&cont, buf.data); printTableAddFooter(&cont, buf.data);
if (!PQgetisnull(result, i, 1))
{
printfPQExpBuffer(&buf, " APPLIED TO %s",
PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
}
} }
} }
PQclear(result); PQclear(result);
@ -2708,6 +2719,10 @@ describeRoles(const char *pattern, bool verbose)
if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0) if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
add_role_attribute(&buf, _("Replication")); add_role_attribute(&buf, _("Replication"));
if (pset.sversion >= 90500)
if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
add_role_attribute(&buf, _("Bypass RLS"));
conns = atoi(PQgetvalue(res, i, 6)); conns = atoi(PQgetvalue(res, i, 6));
if (conns >= 0) if (conns >= 0)
{ {

View File

@ -1214,11 +1214,12 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "ROLE") == 0)) pg_strcasecmp(prev2_wd, "ROLE") == 0))
{ {
static const char *const list_ALTERUSER[] = static const char *const list_ALTERUSER[] =
{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER", {"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE", "CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION", "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET", "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL}; "REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
"VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER); COMPLETE_WITH_LIST(list_ALTERUSER);
} }
@ -1231,11 +1232,12 @@ psql_completion(const char *text, int start, int end)
{ {
/* Similar to the above, but don't complete "WITH" again. */ /* Similar to the above, but don't complete "WITH" again. */
static const char *const list_ALTERUSER_WITH[] = static const char *const list_ALTERUSER_WITH[] =
{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER", {"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE", "CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION", "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET", "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL}; "REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
"VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER_WITH); COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
} }
@ -2565,10 +2567,10 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0)) pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
{ {
static const char *const list_CREATEROLE[] = static const char *const list_CREATEROLE[] =
{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER", {"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB", "CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE", "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL}; "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE); COMPLETE_WITH_LIST(list_CREATEROLE);
@ -2583,10 +2585,10 @@ psql_completion(const char *text, int start, int end)
{ {
/* Similar to the above, but don't complete "WITH" again. */ /* Similar to the above, but don't complete "WITH" again. */
static const char *const list_CREATEROLE_WITH[] = static const char *const list_CREATEROLE_WITH[] =
{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER", {"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB", "CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE", "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL}; "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE_WITH); COMPLETE_WITH_LIST(list_CREATEROLE_WITH);

View File

@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201409191 #define CATALOG_VERSION_NO 201409241
#endif #endif

View File

@ -65,7 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhasrules; /* has (or has had) any rules */ bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */ bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */ bool relhassubclass; /* has (or has had) derived classes */
bool relhasrowsecurity; /* has (or has had) row-security policy */ bool relrowsecurity; /* row-security is enabled or not */
bool relispopulated; /* matview currently holds query results */ bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */ char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */ TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@ -119,7 +119,7 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 21 #define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22 #define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23 #define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relhasrowsecurity 24 #define Anum_pg_class_relrowsecurity 24
#define Anum_pg_class_relispopulated 25 #define Anum_pg_class_relispopulated 25
#define Anum_pg_class_relreplident 26 #define Anum_pg_class_relreplident 26
#define Anum_pg_class_relfrozenxid 27 #define Anum_pg_class_relfrozenxid 27

View File

@ -16,6 +16,7 @@
#define POLICY_H #define POLICY_H
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "utils/relcache.h"
extern void RelationBuildRowSecurity(Relation relation); extern void RelationBuildRowSecurity(Relation relation);
@ -24,10 +25,10 @@ extern void RemovePolicyById(Oid policy_id);
extern Oid CreatePolicy(CreatePolicyStmt *stmt); extern Oid CreatePolicy(CreatePolicyStmt *stmt);
extern Oid AlterPolicy(AlterPolicyStmt *stmt); extern Oid AlterPolicy(AlterPolicyStmt *stmt);
Oid get_relation_policy_oid(Oid relid, extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
const char *policy_name, bool missing_ok); bool missing_ok);
Oid rename_policy(RenameStmt *stmt); extern Oid rename_policy(RenameStmt *stmt);
#endif /* POLICY_H */ #endif /* POLICY_H */

View File

@ -2046,7 +2046,7 @@ pg_tables| SELECT n.nspname AS schemaname,
c.relhasindex AS hasindexes, c.relhasindex AS hasindexes,
c.relhasrules AS hasrules, c.relhasrules AS hasrules,
c.relhastriggers AS hastriggers, c.relhastriggers AS hastriggers,
c.relhasrowsecurity AS hasrowsecurity c.relrowsecurity AS rowsecurity
FROM ((pg_class c FROM ((pg_class c
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace))) LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))