Commit 491c029d authored by Stephen Frost's avatar Stephen Frost

Row-Level Security Policies (RLS)

Building on the updatable security-barrier views work, add the
ability to define policies on tables to limit the set of rows
which are returned from a query and which are allowed to be added
to a table.  Expressions defined by the policy for filtering are
added to the security barrier quals of the query, while expressions
defined to check records being added to a table are added to the
with-check options of the query.

New top-level commands are CREATE/ALTER/DROP POLICY and are
controlled by the table owner.  Row Security is able to be enabled
and disabled by the owner on a per-table basis using
ALTER TABLE .. ENABLE/DISABLE ROW SECURITY.

Per discussion, ROW SECURITY is disabled on tables by default and
must be enabled for policies on the table to be used.  If no
policies exist on a table with ROW SECURITY enabled, a default-deny
policy is used and no records will be visible.

By default, row security is applied at all times except for the
table owner and the superuser.  A new GUC, row_security, is added
which can be set to ON, OFF, or FORCE.  When set to FORCE, row
security will be applied even for the table owner and superusers.
When set to OFF, row security will be disabled when allowed and an
error will be thrown if the user does not have rights to bypass row
security.

Per discussion, pg_dump sets row_security = OFF by default to ensure
that exports and backups will have all data in the table or will
error if there are insufficient privileges to bypass row security.
A new option has been added to pg_dump, --enable-row-security, to
ask pg_dump to export with row security enabled.

A new role capability, BYPASSRLS, which can only be set by the
superuser, is added to allow other users to be able to bypass row
security using row_security = OFF.

Many thanks to the various individuals who have helped with the
design, particularly Robert Haas for his feedback.

Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean
Rasheed, with additional changes and rework by me.

Reviewers have included all of the above, Greg Smith,
Jeff McCormick, and Robert Haas.
parent e5603a2f
......@@ -238,6 +238,11 @@
<entry>replication slot information</entry>
</row>
<row>
<entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link></entry>
<entry>table row-level security policies</entry>
</row>
<row>
<entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
<entry>security labels on database objects</entry>
......@@ -1935,6 +1940,15 @@
</entry>
</row>
<row>
<entry><structfield>relhasrowsecurity</structfield></entry>
<entry><type>bool</type></entry>
<entry>
True if table has row-security enabled; see
<link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
</entry>
</row>
<row>
<entry><structfield>relhassubclass</structfield></entry>
<entry><type>bool</type></entry>
......@@ -5328,6 +5342,86 @@
</table>
</sect1>
<sect1 id="catalog-pg-rowsecurity">
<title><structname>pg_rowsecurity</structname></title>
<indexterm zone="catalog-pg-rowsecurity">
<primary>pg_rowsecurity</primary>
</indexterm>
<para>
The catalog <structname>pg_rowsecurity</structname> stores row-level
security policies for each table. A policy includes the kind of
command which it applies to (or all commands), the roles which it
applies to, the expression to be added as a security-barrier
qualification to queries which include the table and the expression
to be added as a with-check option for queries which attempt to add
new records to the table.
</para>
<table>
<title><structname>pg_rowsecurity</structname> Columns</title>
<tgroup cols="4">
<thead>
<row>
<entry>Name</entry>
<entry>Type</entry>
<entry>References</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><structfield>rsecpolname</structfield></entry>
<entry><type>name</type></entry>
<entry></entry>
<entry>The name of the row-security policy</entry>
</row>
<row>
<entry><structfield>rsecrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
<entry>The table to which the row-security policy belongs</entry>
</row>
<row>
<entry><structfield>rseccmd</structfield></entry>
<entry><type>char</type></entry>
<entry></entry>
<entry>The command type to which the row-security policy is applied.</entry>
</row>
<row>
<entry><structfield>rsecqual</structfield></entry>
<entry><type>pg_node_tree</type></entry>
<entry></entry>
<entry>The expression tree to be added to the security barrier qualifications for queries which use the table.</entry>
</row>
<row>
<entry><structfield>rsecwithcheck</structfield></entry>
<entry><type>pg_node_tree</type></entry>
<entry></entry>
<entry>The expression tree to be added to the with check qualifications for queries which attempt to add rows to the table.</entry>
</row>
</tbody>
</tgroup>
</table>
<note>
<para>
<literal>pg_class.relhasrowsecurity</literal>
True if the table has row-security enabled.
Must be true if the table has a row-security policy in this catalog.
</para>
</note>
</sect1>
<sect1 id="catalog-pg-seclabel">
<title><structname>pg_seclabel</structname></title>
......@@ -9133,6 +9227,12 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhastriggers</literal></entry>
<entry>True if table has (or once had) triggers</entry>
</row>
<row>
<entry><structfield>hasrowsecurity</structfield></entry>
<entry><type>boolean</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry>
<entry>True if table has row security enabled</entry>
</row>
</tbody>
</tgroup>
</table>
......
......@@ -5429,6 +5429,46 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
<varlistentry id="guc-row-security" xreflabel="row_security">
<term><varname>row_security</varname> (<type>enum</type>)
<indexterm>
<primary><varname>row_security</> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
This variable controls if row security policies are to be applied
to queries which are run against tables that have row security enabled.
The default is 'on'. When set to 'on', all users, except superusers
and the owner of the table, will have the row policies for the table
applied to their queries. The table owner and superuser can request
that row policies be applied to their queries by setting this to
'force'. Lastly, this can also be set to 'off' which will bypass row
policies for the table, if possible, and error if not.
</para>
<para>
For a user who is not a superuser and not the table owner to bypass
row policies for the table, they must have the BYPASSRLS role attribute.
If this is set to 'off' and the user queries a table which has row
policies enabled and the user does not have the right to bypass
row policies then a permission denied error will be returned.
</para>
<para>
The allowed values of <varname>row_security</> are
<literal>on</> (apply normally- not to superuser or table owner),
<literal>off</> (fail if row security would be applied), and
<literal>force</> (apply always- even to superuser and table owner).
</para>
<para>
For more information on row security policies,
see <xref linkend="SQL-CREATEPOLICY">.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-default-tablespace" xreflabel="default_tablespace">
<term><varname>default_tablespace</varname> (<type>string</type>)
<indexterm>
......
......@@ -195,6 +195,12 @@
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
......@@ -351,6 +357,12 @@
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE RULE</literal></entry>
<entry align="center"><literal>X</literal></entry>
......@@ -525,6 +537,12 @@
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP RULE</literal></entry>
<entry align="center"><literal>X</literal></entry>
......
......@@ -3422,6 +3422,13 @@
<entry>non-reserved</entry>
<entry>non-reserved</entry>
</row>
<row>
<entry><token>POLICY</token></entry>
<entry>non-reserved</entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry><token>PORTION</token></entry>
<entry></entry>
......
......@@ -25,6 +25,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY alterOperator SYSTEM "alter_operator.sgml">
<!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
<!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
<!ENTITY alterPolicy SYSTEM "alter_policy.sgml">
<!ENTITY alterRole SYSTEM "alter_role.sgml">
<!ENTITY alterRule SYSTEM "alter_rule.sgml">
<!ENTITY alterSchema SYSTEM "alter_schema.sgml">
......@@ -69,6 +70,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY createOperator SYSTEM "create_operator.sgml">
<!ENTITY createOperatorClass SYSTEM "create_opclass.sgml">
<!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
<!ENTITY createPolicy SYSTEM "create_policy.sgml">
<!ENTITY createRole SYSTEM "create_role.sgml">
<!ENTITY createRule SYSTEM "create_rule.sgml">
<!ENTITY createSchema SYSTEM "create_schema.sgml">
......@@ -110,6 +112,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY dropOperatorClass SYSTEM "drop_opclass.sgml">
<!ENTITY dropOperatorFamily SYSTEM "drop_opfamily.sgml">
<!ENTITY dropOwned SYSTEM "drop_owned.sgml">
<!ENTITY dropPolicy SYSTEM "drop_policy.sgml">
<!ENTITY dropRole SYSTEM "drop_role.sgml">
<!ENTITY dropRule SYSTEM "drop_rule.sgml">
<!ENTITY dropSchema SYSTEM "drop_schema.sgml">
......
<!--
doc/src/sgml/ref/alter_policy.sgml
PostgreSQL documentation
-->
<refentry id="SQL-ALTERPOLICY">
<indexterm zone="sql-alterpolicy">
<primary>ALTER POLICY</primary>
</indexterm>
<refmeta>
<refentrytitle>ALTER POLICY</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>ALTER POLICY</refname>
<refpurpose>change the definition of a row-security policy</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
ALTER POLICY <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
[ RENAME TO <replaceable class="PARAMETER">new_name</replaceable> ]
[ TO { <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] ]
[ USING ( <replaceable class="parameter">expression</replaceable> ) ]
[ WITH CHECK ( <replaceable class="parameter">check_expression</replaceable> ) ]
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>ALTER POLICY</command> changes the <replaceable class="parameter">
definition</replaceable> of an existing row-security policy.
</para>
<para>
To use <command>ALTER POLICY</command>, you must own the table that
the policy applies to.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of an existing policy to alter.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of the table that the
policy is on.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">new_name</replaceable></term>
<listitem>
<para>
The new name for the policy.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">role_name</replaceable></term>
<listitem>
<para>
The role to which the policy applies. Multiple roles can be specified at one time.
To apply the policy to all roles, use <literal>PUBLIC</literal>, which is also
the default.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">expression</replaceable></term>
<listitem>
<para>
The USING expression for the policy. This expression will be added as a
security-barrier qualification to queries which use the table
automatically. If multiple policies are being applied for a given
table then they are all combined and added using OR. The USING
expression applies to records which are being retrived from the table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">check_expression</replaceable></term>
<listitem>
<para>
The with-check expression for the policy. This expression will be
added as a WITH CHECK OPTION qualification to queries which use the
table automatically. If multiple policies are being applied for a
given table then they are all combined and added using OR. The WITH
CHECK expression applies to records which are being added to the table.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Compatibility</title>
<para>
<command>ALTER POLICY</command> is a <productname>PostgreSQL</productname> extension.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createpolicy"></member>
<member><xref linkend="sql-droppolicy"></member>
</simplelist>
</refsect1>
</refentry>
......@@ -32,6 +32,7 @@ ALTER ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replace
| INHERIT | NOINHERIT
| LOGIN | NOLOGIN
| REPLICATION | NOREPLICATION
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
......@@ -142,6 +143,8 @@ ALTER ROLE { <replaceable class="PARAMETER">name</replaceable> | ALL } [ IN DATA
<term><literal>NOLOGIN</literal></term>
<term><literal>REPLICATION</literal></term>
<term><literal>NOREPLICATION</literal></term>
<term><literal>BYPASSRLS</literal></term>
<term><literal>NOBYPASSRLS</literal></term>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
<term><literal>ENCRYPTED</></term>
......
......@@ -59,6 +59,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
ENABLE RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE REPLICA RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
DISABLE ROW LEVEL SECURITY
ENABLE ROW LEVEL SECURITY
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
SET WITH OIDS
......@@ -420,6 +422,21 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DISABLE</literal>/<literal>ENABLE ROW LEVEL SECURITY</literal></term>
<listitem>
<para>
These forms control the application of row security policies belonging
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
even if row level security is disabled- in this case, the policies will
NOT be applied and the policies will be ignored.
See also
<xref linkend="SQL-CREATEPOLICY">.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CLUSTER ON</literal></term>
<listitem>
......
This diff is collapsed.
......@@ -32,6 +32,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
| INHERIT | NOINHERIT
| LOGIN | NOLOGIN
| REPLICATION | NOREPLICATION
| BYPASSRLS | NOBYPASSRLS
| CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
| VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
......@@ -190,6 +191,25 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
</listitem>
</varlistentry>
<varlistentry>
<term><literal>BYPASSRLS</literal></term>
<term><literal>NOBYPASSRLS</literal></term>
<listitem>
<para>
These clauses determine whether a role is allowed to bypass row-security
policies. A role having the <literal>BYPASSRLS</literal> attribute will
be allowed to bypass row-security policies by setting
<literal>row_security</literal> to
<literal>OFF</literal>. <literal>NOBYPASSRLS</literal> is the default.
Note that pg_dump will set <literal>row_security</literal> to
<literal>OFF</literal> by default, to ensure all contents of a table are
dumped out. If the user running pg_dump does not have appropriate
permissions, an error will be returned. The superuser and owner of the
table being dumped are considered to always have the right to bypass RLS.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
<listitem>
......
<!--
doc/src/sgml/ref/drop_policy.sgml
PostgreSQL documentation
-->
<refentry id="SQL-DROPPOLICY">
<indexterm zone="sql-droppolicy">
<primary>DROP POLICY</primary>
</indexterm>
<refmeta>
<refentrytitle>DROP POLICY</refentrytitle>
<manvolnum>7</manvolnum>
<refmiscinfo>SQL - Language Statements</refmiscinfo>
</refmeta>
<refnamediv>
<refname>DROP POLICY</refname>
<refpurpose>remove a row-security policy from a table</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
DROP POLICY [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>DROP POLICY</command> removes the specified row-security policy
from the table. Note that if the last policy is removed for a table and
the table still has ROW POLICY enabled via <command>ALTER TABLE</command>,
then the default-deny policy will be used. <command>ALTER TABLE</command>
can be used to disable row security for a table using
<literal>DISABLE ROW SECURITY</literal>, whether policies for the table
exist or not.
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><literal>IF EXISTS</literal></term>
<listitem>
<para>
Do not throw an error if the policy does not exist. A notice is issued
in this case.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of the policy to drop.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of the table that
the policy is on.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
To drop the row-security policy called <literal>p1</literal> on the
table named <literal>my_table</literal>:
<programlisting>
DROP POLICY p1 ON my_table;
</programlisting>
</para>
</refsect1>
<refsect1>
<title>Compatibility</title>
<para>
<command>DROP POLICY</command> is a <productname>PostgreSQL</productname> extension.
</para>
</refsect1>
<refsect1>
<title>See Also</title>
<simplelist type="inline">
<member><xref linkend="sql-createpolicy"></member>
<member><xref linkend="sql-alterpolicy"></member>
</simplelist>
</refsect1>
</refentry>
......@@ -53,6 +53,7 @@
&alterOperator;
&alterOperatorClass;
&alterOperatorFamily;
&alterPolicy;
&alterRole;
&alterRule;
&alterSchema;
......@@ -97,6 +98,7 @@
&createOperator;
&createOperatorClass;
&createOperatorFamily;
&createPolicy;
&createRole;
&createRule;
&createSchema;
......@@ -138,6 +140,7 @@
&dropOperatorClass;
&dropOperatorFamily;
&dropOwned;
&dropPolicy;
&dropRole;
&dropRule;
&dropSchema;
......
......@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h \
pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
toasting.h indexing.h \
)
......
......@@ -5080,6 +5080,25 @@ has_createrole_privilege(Oid roleid)
return result;
}
bool
has_bypassrls_privilege(Oid roleid)
{
bool result = false;
HeapTuple utup;
/* Superusers bypass all permission checking. */
if (superuser_arg(roleid))
return true;
utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (HeapTupleIsValid(utup))
{
result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
ReleaseSysCache(utup);
}
return result;
}
/*
* Fetch pg_default_acl entry for given role, namespace and object type
* (object type must be given in pg_default_acl's encoding).
......
......@@ -45,6 +45,7 @@
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_rowsecurity.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
......@@ -57,6 +58,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
......@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemoveEventTriggerById(object->objectId);
break;
case OCLASS_ROWSECURITY:
RemovePolicyById(object->objectId);
break;
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
......@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
case RowSecurityRelationId:
return OCLASS_ROWSECURITY;
}
/* shouldn't get here */
......
......@@ -799,6 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
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);
......
......@@ -42,6 +42,7 @@
#include "catalog/pg_opfamily.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rowsecurity.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
......@@ -55,6 +56,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
......@@ -343,6 +345,18 @@ static const ObjectPropertyType ObjectProperty[] =
-1,
false
},
{
RowSecurityRelationId,
RowSecurityOidIndexId,
-1,
-1,
Anum_pg_rowsecurity_rsecpolname,
InvalidAttrNumber,
InvalidAttrNumber,
InvalidAttrNumber,
-1,
false
},
{
EventTriggerRelationId,
EventTriggerOidIndexId,
......@@ -517,6 +531,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
case OBJECT_RULE:
case OBJECT_TRIGGER:
case OBJECT_CONSTRAINT:
case OBJECT_POLICY:
address = get_object_address_relobject(objtype, objname,
&relation, missing_ok);
break;
......@@ -982,6 +997,13 @@ get_object_address_relobject(ObjectType objtype, List *objname,
InvalidOid;
address.objectSubId = 0;
break;
case OBJECT_POLICY:
address.classId = RowSecurityRelationId;
address.objectId = relation ?
get_relation_policy_oid(reloid, depname, missing_ok) :
InvalidOid;
address.objectSubId = 0;
break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
/* placate compiler, which doesn't know elog won't return */
......@@ -1155,6 +1177,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
case OBJECT_COLUMN:
case OBJECT_RULE:
case OBJECT_TRIGGER:
case OBJECT_POLICY:
case OBJECT_CONSTRAINT:
if (!pg_class_ownercheck(RelationGetRelid(relation), roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
......@@ -2166,6 +2189,41 @@ getObjectDescription(const ObjectAddress *object)
break;
}
case OCLASS_ROWSECURITY:
{
Relation rsec_rel;
ScanKeyData skey[1];
SysScanDesc sscan;
HeapTuple tuple;
Form_pg_rowsecurity form_rsec;
rsec_rel = heap_open(RowSecurityRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(object->objectId));
sscan = systable_beginscan(rsec_rel, RowSecurityOidIndexId,
true, NULL, 1, skey);
tuple = systable_getnext(sscan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for row-security relation %u",
object->objectId);
form_rsec = (Form_pg_rowsecurity) GETSTRUCT(tuple);
appendStringInfo(&buffer, _("policy %s on "),
NameStr(form_rsec->rsecpolname));
getRelationDescription(&buffer, form_rsec->rsecrelid);
systable_endscan(sscan);
heap_close(rsec_rel, AccessShareLock);
break;
}
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
......
......@@ -19,6 +19,7 @@ CREATE VIEW pg_roles AS
rolconnlimit,
'********'::text as rolpassword,
rolvaliduntil,
rolbypassrls,
setconfig as rolconfig,
pg_authid.oid
FROM pg_authid LEFT JOIN pg_db_role_setting s
......@@ -62,6 +63,34 @@ CREATE VIEW pg_user AS
useconfig
FROM pg_shadow;
CREATE VIEW pg_policies AS
SELECT
rs.rsecpolname AS policyname,
(SELECT relname FROM pg_catalog.pg_class WHERE oid = rs.rsecrelid) AS tablename,
CASE
WHEN rs.rsecroles = '{0}' THEN
string_to_array('public', '')
ELSE
ARRAY
(
SELECT rolname
FROM pg_catalog.pg_authid
WHERE oid = ANY (rs.rsecroles) ORDER BY 1
)
END AS roles,
CASE WHEN rs.rseccmd IS NULL THEN 'ALL' ELSE
CASE rs.rseccmd
WHEN 'r' THEN 'SELECT'
WHEN 'a' THEN 'INSERT'
WHEN 'u' THEN 'UPDATE'
WHEN 'd' THEN 'DELETE'
END
END AS cmd,
pg_catalog.pg_get_expr(rs.rsecqual, rs.rsecrelid) AS qual,
pg_catalog.pg_get_expr(rs.rsecwithcheck, rs.rsecrelid) AS with_check
FROM pg_catalog.pg_rowsecurity rs
ORDER BY 1;
CREATE VIEW pg_rules AS
SELECT
N.nspname AS schemaname,
......@@ -89,7 +118,8 @@ CREATE VIEW pg_tables AS
T.spcname AS tablespace,
C.relhasindex AS hasindexes,
C.relhasrules AS hasrules,
C.relhastriggers AS hastriggers
C.relhastriggers AS hastriggers,
C.relhasrowsecurity AS hasrowsecurity
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
WHERE C.relkind = 'r';
......
......@@ -17,7 +17,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
policy.o portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
......
......@@ -43,6 +43,7 @@
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/tablecmds.h"
......@@ -338,6 +339,9 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_TRIGGER:
return renametrig(stmt);
case OBJECT_POLICY:
return rename_policy(stmt);
case OBJECT_DOMAIN:
case OBJECT_TYPE:
return RenameType(stmt);
......
......@@ -37,7 +37,9 @@
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "parser/parse_relation.h"
#include "nodes/makefuncs.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rowsecurity.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
......@@ -784,6 +786,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
bool pipe = (stmt->filename == NULL);
Relation rel;
Oid relid;
Node *query = NULL;
/* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
......@@ -837,11 +840,72 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
rte->selectedCols = bms_add_member(rte->selectedCols, attno);
}
ExecCheckRTPerms(list_make1(rte), true);
/*
* Permission check for row security.
*
* check_enable_rls will ereport(ERROR) if the user has requested
* something invalid and will otherwise indicate if we should enable
* RLS (returns RLS_ENABLED) or not for this COPY statement.
*
* If the relation has a row security policy and we are to apply it
* then perform a "query" copy and allow the normal query processing to
* handle the policies.
*
* If RLS is not enabled for this, then just fall through to the
* normal non-filtering relation handling.
*/
if (check_enable_rls(rte->relid, InvalidOid) == RLS_ENABLED)
{
SelectStmt *select;
ColumnRef *cr;
ResTarget *target;
RangeVar *from;
if (is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY FROM not supported with row security."),
errhint("Use direct INSERT statements instead.")));
/* Build target list */
cr = makeNode(ColumnRef);
if (!stmt->attlist)
cr->fields = list_make1(makeNode(A_Star));
else
cr->fields = stmt->attlist;
cr->location = 1;
target = makeNode(ResTarget);
target->name = NULL;
target->indirection = NIL;
target->val = (Node *) cr;
target->location = 1;
/* Build FROM clause */
from = makeRangeVar(NULL, RelationGetRelationName(rel), 1);
/* Build query */
select = makeNode(SelectStmt);
select->targetList = list_make1(target);
select->fromClause = list_make1(from);
query = (Node*) select;
relid = InvalidOid;
/* Close the handle to the relation as it is no longer needed. */
heap_close(rel, (is_from ? RowExclusiveLock : AccessShareLock));
rel = NULL;
}
}
else
{
Assert(stmt->query);
query = stmt->query;
relid = InvalidOid;
rel = NULL;
}
......@@ -861,7 +925,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
}
else
{
cstate = BeginCopyTo(rel, stmt->query, queryString,
cstate = BeginCopyTo(rel, query, queryString,
stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
*processed = DoCopyTo(cstate); /* copy from database to file */
......
......@@ -36,6 +36,7 @@
#include "miscadmin.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rowsecurity.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
......@@ -419,6 +420,19 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
ExecCheckRTPerms(list_make1(rte), true);
/*
* Make sure the constructed table does not have RLS enabled.
*
* check_enable_rls() will ereport(ERROR) itself if the user has requested
* something invalid, and otherwise will return RLS_ENABLED if RLS should
* be enabled here. We don't actually support that currently, so throw
* our own ereport(ERROR) if that happens.
*/
if (check_enable_rls(intoRelationId, InvalidOid) == RLS_ENABLED)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("policies not yet implemented for this command"))));
/*
* Tentatively mark the target as populated, if it's a matview and we're
* going to fill it; otherwise, no change needed.
......
......@@ -371,6 +371,15 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
list_length(objname) - 1));
}
break;
case OBJECT_POLICY:
if (!owningrel_does_not_exist_skipping(objname, &msg, &name))
{
msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, skipping");
name = strVal(llast(objname));
args = NameListToString(list_truncate(list_copy(objname),
list_length(objname) - 1));
}
break;
case OBJECT_EVENT_TRIGGER:
msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
name = NameListToString(objname);
......
......@@ -85,6 +85,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"OPERATOR", true},
{"OPERATOR CLASS", true},
{"OPERATOR FAMILY", true},
{"POLICY", true},
{"ROLE", false},
{"RULE", true},
{"SCHEMA", true},
......@@ -936,6 +937,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_OPCLASS:
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_RULE:
case OBJECT_SCHEMA:
case OBJECT_SEQUENCE:
......@@ -995,6 +997,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
case OCLASS_ROWSECURITY:
return true;
case MAX_OCLASS:
......
This diff is collapsed.
......@@ -36,6 +36,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_rowsecurity.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
......@@ -45,6 +46,7 @@
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/policy.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
......@@ -408,6 +410,8 @@ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockm
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
static void ATExecEnableRowSecurity(Relation rel);
static void ATExecDisableRowSecurity(Relation rel);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
......@@ -2872,6 +2876,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
case AT_EnableRowSecurity:
case AT_DisableRowSecurity:
cmd_lockmode = AccessExclusiveLock;
break;
......@@ -3280,6 +3286,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_DropInherit: /* NO INHERIT */
case AT_AddOf: /* OF */
case AT_DropOf: /* NOT OF */
case AT_EnableRowSecurity:
case AT_DisableRowSecurity:
ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
/* No command-specific prep needed */
......@@ -3571,6 +3579,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_ReplicaIdentity:
ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
break;
case AT_EnableRowSecurity:
ATExecEnableRowSecurity(rel);
break;
case AT_DisableRowSecurity:
ATExecDisableRowSecurity(rel);
break;
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
......@@ -10614,6 +10628,62 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
index_close(indexRel, NoLock);
}
/*
* ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
*/
static void
ATExecEnableRowSecurity(Relation rel)
{
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))->relhasrowsecurity = true;
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);
}
static void
ATExecDisableRowSecurity(Relation rel)
{
Relation pg_class;
Oid relid;
HeapTuple tuple;
relid = RelationGetRelid(rel);
/* Pull the record for this relation and update it */
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))->relhasrowsecurity = false;
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 (...)
*/
......
......@@ -87,6 +87,7 @@ CreateRole(CreateRoleStmt *stmt)
bool createdb = false; /* Can the user create databases? */
bool canlogin = false; /* Can this user login? */
bool isreplication = false; /* Is this a replication role? */
bool bypassrls = false; /* Is this a row security enabled role? */
int connlimit = -1; /* maximum connections allowed */
List *addroleto = NIL; /* roles to make this a member of */
List *rolemembers = NIL; /* roles to be members of this role */
......@@ -106,6 +107,7 @@ CreateRole(CreateRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
......@@ -232,6 +234,14 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
......@@ -267,6 +277,8 @@ CreateRole(CreateRoleStmt *stmt)
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = intVal(dbypassRLS->arg) != 0;
/* Check some permissions first */
if (issuper)
......@@ -283,6 +295,13 @@ CreateRole(CreateRoleStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create replication users")));
}
else if (bypassrls)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to change bypassrls attribute.")));
}
else
{
if (!have_createrole_privilege())
......@@ -375,6 +394,8 @@ CreateRole(CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
......@@ -474,6 +495,7 @@ AlterRole(AlterRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
bool bypassrls = -1;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
......@@ -484,6 +506,7 @@ AlterRole(AlterRoleStmt *stmt)
DefElem *dconnlimit = NULL;
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
/* Extract options from the statement node tree */
......@@ -578,6 +601,14 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
......@@ -609,6 +640,8 @@ AlterRole(AlterRoleStmt *stmt)
rolemembers = (List *) drolemembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = intVal(dbypassRLS->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
......@@ -642,6 +675,13 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter replication users")));
}
else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to change bypassrls attribute")));
}
else if (!have_createrole_privilege())
{
if (!(inherit < 0 &&
......@@ -775,6 +815,12 @@ AlterRole(AlterRoleStmt *stmt)
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
if (bypassrls >= 0)
{
new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
}
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
......
......@@ -501,6 +501,12 @@ ExecutorRewind(QueryDesc *queryDesc)
*
* Returns true if permissions are adequate. Otherwise, throws an appropriate
* error if ereport_on_violation is true, or simply returns false otherwise.
*
* Note that this does NOT address row-level security policies (aka: RLS). If
* rows will be returned to the user as a result of this permission check
* passing, then RLS also needs to be consulted (and check_enable_rls()).
*
* See rewrite/rowsecurity.c.
*/
bool
ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
......@@ -1660,15 +1666,17 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
/*
* WITH CHECK OPTION checks are intended to ensure that the new tuple
* is visible in the view. If the view's qual evaluates to NULL, then
* the new tuple won't be included in the view. Therefore we need to
* tell ExecQual to return FALSE for NULL (the opposite of what we do
* above for CHECK constraints).
* is visible (in the case of a view) or that it passes the
* 'with-check' policy (in the case of row security).
* If the qual evaluates to NULL or FALSE, then the new tuple won't be
* included in the view or doesn't pass the 'with-check' policy for the
* table. We need ExecQual to return FALSE for NULL to handle the view
* case (the opposite of what we do above for CHECK constraints).
*/
if (!ExecQual((List *) wcoExpr, econtext, false))
ereport(ERROR,
(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
errmsg("new row violates WITH CHECK OPTION for \"%s\"",
wco->viewname),
errdetail("Failing row contains %s.",
ExecBuildSlotValueDescription(slot,
......
......@@ -2488,6 +2488,7 @@ _copyQuery(const Query *from)
COPY_SCALAR_FIELD(hasRecursive);
COPY_SCALAR_FIELD(hasModifyingCTE);
COPY_SCALAR_FIELD(hasForUpdate);
COPY_SCALAR_FIELD(hasRowSecurity);
COPY_NODE_FIELD(cteList);
COPY_NODE_FIELD(rtable);
COPY_NODE_FIELD(jointree);
......@@ -3849,6 +3850,35 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
return newnode;
}
static CreatePolicyStmt *
_copyCreatePolicyStmt(const CreatePolicyStmt *from)
{
CreatePolicyStmt *newnode = makeNode(CreatePolicyStmt);
COPY_STRING_FIELD(policy_name);
COPY_NODE_FIELD(table);
COPY_SCALAR_FIELD(cmd);
COPY_NODE_FIELD(roles);
COPY_NODE_FIELD(qual);
COPY_NODE_FIELD(with_check);
return newnode;
}
static AlterPolicyStmt *
_copyAlterPolicyStmt(const AlterPolicyStmt *from)
{
AlterPolicyStmt *newnode = makeNode(AlterPolicyStmt);
COPY_STRING_FIELD(policy_name);
COPY_NODE_FIELD(table);
COPY_NODE_FIELD(roles);
COPY_NODE_FIELD(qual);
COPY_NODE_FIELD(with_check);
return newnode;
}
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
......@@ -4561,7 +4591,12 @@ copyObject(const void *from)
case T_AlterTSConfigurationStmt:
retval = _copyAlterTSConfigurationStmt(from);
break;
case T_CreatePolicyStmt:
retval = _copyCreatePolicyStmt(from);
break;
case T_AlterPolicyStmt:
retval = _copyAlterPolicyStmt(from);
break;
case T_A_Expr:
retval = _copyAExpr(from);
break;
......
......@@ -857,6 +857,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_SCALAR_FIELD(hasRecursive);
COMPARE_SCALAR_FIELD(hasModifyingCTE);
COMPARE_SCALAR_FIELD(hasForUpdate);
COMPARE_SCALAR_FIELD(hasRowSecurity);
COMPARE_NODE_FIELD(cteList);
COMPARE_NODE_FIELD(rtable);
COMPARE_NODE_FIELD(jointree);
......@@ -2007,6 +2008,31 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
return true;
}
static bool
_equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
{
COMPARE_STRING_FIELD(policy_name);
COMPARE_NODE_FIELD(table);
COMPARE_SCALAR_FIELD(cmd);
COMPARE_NODE_FIELD(roles);
COMPARE_NODE_FIELD(qual);
COMPARE_NODE_FIELD(with_check);
return true;
}
static bool
_equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
{
COMPARE_STRING_FIELD(policy_name);
COMPARE_NODE_FIELD(table);
COMPARE_NODE_FIELD(roles);
COMPARE_NODE_FIELD(qual);
COMPARE_NODE_FIELD(with_check);
return true;
}
static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
......@@ -3025,7 +3051,12 @@ equal(const void *a, const void *b)
case T_AlterTSConfigurationStmt:
retval = _equalAlterTSConfigurationStmt(a, b);
break;
case T_CreatePolicyStmt:
retval = _equalCreatePolicyStmt(a, b);
break;
case T_AlterPolicyStmt:
retval = _equalAlterPolicyStmt(a, b);
break;
case T_A_Expr:
retval = _equalAExpr(a, b);
break;
......
......@@ -2263,6 +2263,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_BOOL_FIELD(hasRecursive);
WRITE_BOOL_FIELD(hasModifyingCTE);
WRITE_BOOL_FIELD(hasForUpdate);
WRITE_BOOL_FIELD(hasRowSecurity);
WRITE_NODE_FIELD(cteList);
WRITE_NODE_FIELD(rtable);
WRITE_NODE_FIELD(jointree);
......
......@@ -208,6 +208,7 @@ _readQuery(void)
READ_BOOL_FIELD(hasRecursive);
READ_BOOL_FIELD(hasModifyingCTE);
READ_BOOL_FIELD(hasForUpdate);
READ_BOOL_FIELD(hasRowSecurity);
READ_NODE_FIELD(cteList);
READ_NODE_FIELD(rtable);
READ_NODE_FIELD(jointree);
......
......@@ -177,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->lastPHId = 0;
glob->lastRowMarkId = 0;
glob->transientPlan = false;
glob->has_rls = false;
/* Determine what fraction of the plan is likely to be scanned */
if (cursorOptions & CURSOR_OPT_FAST_PLAN)
......@@ -254,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
result->nParamExec = glob->nParamExec;
result->has_rls = glob->has_rls;
return result;
}
......@@ -1206,6 +1208,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* This may add new security barrier subquery RTEs to the rangetable.
*/
expand_security_quals(root, tlist);
root->glob->has_rls = parse->hasRowSecurity;
/*
* Locate any window functions in the tlist. (We don't need to look
......
......@@ -2081,7 +2081,8 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
void
extract_query_dependencies(Node *query,
List **relationOids,
List **invalItems)
List **invalItems,
bool *hasRowSecurity)
{
PlannerGlobal glob;
PlannerInfo root;
......@@ -2091,6 +2092,7 @@ extract_query_dependencies(Node *query,
glob.type = T_PlannerGlobal;
glob.relationOids = NIL;
glob.invalItems = NIL;
glob.has_rls = false;
MemSet(&root, 0, sizeof(root));
root.type = T_PlannerInfo;
......@@ -2100,6 +2102,7 @@ extract_query_dependencies(Node *query,
*relationOids = glob.relationOids;
*invalItems = glob.invalItems;
*hasRowSecurity = glob.has_rls;
}
static bool
......@@ -2115,6 +2118,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
Query *query = (Query *) node;
ListCell *lc;
/* Collect row-security information */
context->glob->has_rls = query->hasRowSecurity;
if (query->commandType == CMD_UTILITY)
{
/*
......
......@@ -231,7 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt
AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
......@@ -240,11 +240,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropPolicyStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
......@@ -319,6 +319,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> all_Op MathOp
%type <str> row_security_cmd RowSecurityDefaultForCmd
%type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole
%type <str> iso_level opt_encoding
%type <node> grantee
%type <list> grantee_list
......@@ -589,7 +593,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
......@@ -740,6 +744,7 @@ stmt :
| AlterGroupStmt
| AlterObjectSchemaStmt
| AlterOwnerStmt
| AlterPolicyStmt
| AlterSeqStmt
| AlterSystemStmt
| AlterTableStmt
......@@ -774,6 +779,7 @@ stmt :
| CreateOpClassStmt
| CreateOpFamilyStmt
| AlterOpFamilyStmt
| CreatePolicyStmt
| CreatePLangStmt
| CreateSchemaStmt
| CreateSeqStmt
......@@ -799,6 +805,7 @@ stmt :
| DropOpClassStmt
| DropOpFamilyStmt
| DropOwnedStmt
| DropPolicyStmt
| DropPLangStmt
| DropRuleStmt
| DropStmt
......@@ -957,6 +964,10 @@ AlterOptRoleElem:
$$ = makeDefElem("canlogin", (Node *)makeInteger(TRUE));
else if (strcmp($1, "nologin") == 0)
$$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
else if (strcmp($1, "bypassrls") == 0)
$$ = makeDefElem("bypassrls", (Node *)makeInteger(TRUE));
else if (strcmp($1, "nobypassrls") == 0)
$$ = makeDefElem("bypassrls", (Node *)makeInteger(FALSE));
else if (strcmp($1, "noinherit") == 0)
{
/*
......@@ -2302,6 +2313,20 @@ alter_table_cmd:
n->def = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ENABLE ROW LEVEL SECURITY */
| ENABLE_P ROW LEVEL SECURITY
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_EnableRowSecurity;
$$ = (Node *)n;
}
/* ALTER TABLE <name> DISABLE ROW LEVEL SECURITY */
| DISABLE_P ROW LEVEL SECURITY
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_DisableRowSecurity;
$$ = (Node *)n;
}
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
......@@ -4495,6 +4520,105 @@ AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generi
}
;
/*****************************************************************************
*
* QUERIES:
* CREATE POLICY name ON table FOR cmd TO role USING (qual)
* WITH CHECK (with_check)
* ALTER POLICY name ON table FOR cmd TO role USING (qual)
* WITH CHECK (with_check)
* DROP POLICY name ON table
*
*****************************************************************************/
CreatePolicyStmt:
CREATE POLICY name ON qualified_name RowSecurityDefaultForCmd
RowSecurityDefaultToRole RowSecurityOptionalExpr
RowSecurityOptionalWithCheck
{
CreatePolicyStmt *n = makeNode(CreatePolicyStmt);
n->policy_name = $3;
n->table = $5;
n->cmd = $6;
n->roles = $7;
n->qual = $8;
n->with_check = $9;
$$ = (Node *) n;
}
;
AlterPolicyStmt:
ALTER POLICY name ON qualified_name RowSecurityOptionalToRole
RowSecurityOptionalExpr RowSecurityOptionalWithCheck
{
AlterPolicyStmt *n = makeNode(AlterPolicyStmt);
n->policy_name = $3;
n->table = $5;
n->roles = $6;
n->qual = $7;
n->with_check = $8;
$$ = (Node *) n;
}
;
DropPolicyStmt:
DROP POLICY name ON any_name opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_POLICY;
n->objects = list_make1(lappend($5, makeString($3)));
n->arguments = NIL;
n->behavior = $6;
n->missing_ok = false;
n->concurrent = false;
$$ = (Node *) n;
}
| DROP POLICY IF_P EXISTS name ON any_name opt_drop_behavior
{
DropStmt *n = makeNode(DropStmt);
n->removeType = OBJECT_POLICY;
n->objects = list_make1(lappend($7, makeString($5)));
n->arguments = NIL;
n->behavior = $8;
n->missing_ok = true;
n->concurrent = false;
$$ = (Node *) n;
}
;
RowSecurityOptionalExpr:
USING '(' a_expr ')' { $$ = $3; }
| /* EMPTY */ { $$ = NULL; }
;
RowSecurityOptionalWithCheck:
WITH CHECK '(' a_expr ')' { $$ = $4; }
| /* EMPTY */ { $$ = NULL; }
;
RowSecurityDefaultToRole:
TO role_list { $$ = $2; }
| /* EMPTY */ { $$ = list_make1(makeString("public")); }
;
RowSecurityOptionalToRole:
TO role_list { $$ = $2; }
| /* EMPTY */ { $$ = NULL; }
;
RowSecurityDefaultForCmd:
FOR row_security_cmd { $$ = $2; }
| /* EMPTY */ { $$ = "all"; }
;
row_security_cmd:
ALL { $$ = "all"; }
| SELECT { $$ = "select"; }
| INSERT { $$ = "insert"; }
| UPDATE { $$ = "update"; }
| DELETE_P { $$ = "delete"; }
;
/*****************************************************************************
*
* QUERIES :
......@@ -7240,6 +7364,26 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER POLICY name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_POLICY;
n->relation = $5;
n->subname = $3;
n->newname = $8;
n->missing_ok = false;
$$ = (Node *)n;
}
| ALTER POLICY IF_P EXISTS name ON qualified_name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
n->renameType = OBJECT_POLICY;
n->relation = $7;
n->subname = $5;
n->newname = $10;
n->missing_ok = true;
$$ = (Node *)n;
}
| ALTER SCHEMA name RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
......@@ -13036,6 +13180,7 @@ unreserved_keyword:
| PASSING
| PASSWORD
| PLANS
| POLICY
| PRECEDING
| PREPARE
| PREPARED
......
......@@ -13,6 +13,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = rewriteRemove.o rewriteDefine.o \
rewriteHandler.o rewriteManip.o rewriteSupport.o
rewriteHandler.o rewriteManip.o rewriteSupport.o \
rowsecurity.o
include $(top_srcdir)/src/backend/common.mk
......@@ -25,6 +25,7 @@
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rowsecurity.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
......@@ -1670,11 +1671,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
* Collect the RIR rules that we must apply
*/
rules = rel->rd_rules;
if (rules == NULL)
if (rules != NULL)
{
heap_close(rel, NoLock);
continue;
}
locks = NIL;
for (i = 0; i < rules->numLocks; i++)
{
......@@ -1713,6 +1711,52 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
activeRIRs = list_delete_first(activeRIRs);
}
}
/*
* If the RTE has row-security quals, apply them and recurse into the
* securityQuals.
*/
if (prepend_row_security_policies(parsetree, rte, rt_index))
{
/*
* We applied security quals, check for infinite recursion and
* then expand any nested queries.
*/
if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("infinite recursion detected in row-security policy for relation \"%s\"",
RelationGetRelationName(rel))));
/*
* Make sure we check for recursion in either securityQuals or
* WITH CHECK quals.
*/
if (rte->securityQuals != NIL)
{
activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
expression_tree_walker( (Node*) rte->securityQuals,
fireRIRonSubLink, (void*)activeRIRs );
activeRIRs = list_delete_first(activeRIRs);
}
if (parsetree->withCheckOptions != NIL)
{
WithCheckOption *wco;
List *quals = NIL;
wco = (WithCheckOption *) makeNode(WithCheckOption);
quals = lcons(wco->qual, quals);
activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
expression_tree_walker( (Node*) quals, fireRIRonSubLink,
(void*)activeRIRs);
}
}
heap_close(rel, NoLock);
}
......
This diff is collapsed.
......@@ -39,6 +39,7 @@
#include "commands/extension.h"
#include "commands/matview.h"
#include "commands/lockcmds.h"
#include "commands/policy.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
#include "commands/proclang.h"
......@@ -1320,6 +1321,14 @@ ProcessUtilitySlow(Node *parsetree,
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
break;
case T_CreatePolicyStmt: /* CREATE POLICY */
CreatePolicy((CreatePolicyStmt *) parsetree);
break;
case T_AlterPolicyStmt: /* ALTER POLICY */
AlterPolicy((AlterPolicyStmt *) parsetree);
break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(parsetree));
......@@ -1623,6 +1632,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_OPFAMILY:
tag = "ALTER OPERATOR FAMILY";
break;
case OBJECT_POLICY:
tag = "ALTER POLICY";
break;
case OBJECT_ROLE:
tag = "ALTER ROLE";
break;
......@@ -1944,6 +1956,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
break;
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
default:
tag = "???";
}
......@@ -2287,6 +2302,14 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER TEXT SEARCH CONFIGURATION";
break;
case T_CreatePolicyStmt:
tag = "CREATE POLICY";
break;
case T_AlterPolicyStmt:
tag = "ALTER POLICY";
break;
case T_PrepareStmt:
tag = "PREPARE";
break;
......@@ -2831,6 +2854,14 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
case T_CreatePolicyStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterPolicyStmt:
lev = LOGSTMT_DDL;
break;
case T_AlterTSDictionaryStmt:
lev = LOGSTMT_DDL;
break;
......
......@@ -117,7 +117,6 @@ static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
static Oid get_role_oid_or_public(const char *rolname);
/*
......@@ -5126,7 +5125,7 @@ get_role_oid(const char *rolname, bool missing_ok)
* get_role_oid_or_public - As above, but return ACL_ID_PUBLIC if the
* role name is "public".
*/
static Oid
Oid
get_role_oid_or_public(const char *rolname)
{
if (strcmp(rolname, "public") == 0)
......
......@@ -2303,6 +2303,18 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
return false;
/*
* Also punt if RLS is enabled on either table unless this role has the
* bypassrls right or is the table owner of the table(s) involved which
* have RLS enabled.
*/
if (!has_bypassrls_privilege(GetUserId()) &&
((pk_rel->rd_rel->relhasrowsecurity &&
!pg_class_ownercheck(pkrte->relid, GetUserId())) ||
(fk_rel->rd_rel->relhasrowsecurity &&
!pg_class_ownercheck(fkrte->relid, GetUserId()))))
return false;
/*----------
* The query string built is:
* SELECT fk.keycols FROM ONLY relname fk
......@@ -2956,6 +2968,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
Relation query_rel;
Oid save_userid;
int save_sec_context;
int temp_sec_context;
/*
* Use the query type code to determine whether the query is run against
......@@ -2968,8 +2981,22 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
/* Switch to proper UID to perform check as */
GetUserIdAndSecContext(&save_userid, &save_sec_context);
/*
* Row-level security should be disabled in the case where a foreign-key
* relation is queried to check existence of tuples that references the
* primary-key being modified.
*/
temp_sec_context = save_sec_context | SECURITY_LOCAL_USERID_CHANGE;
if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK
|| qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK_FROM_PK
|| qkey->constr_queryno == RI_PLAN_RESTRICT_DEL_CHECKREF
|| qkey->constr_queryno == RI_PLAN_RESTRICT_UPD_CHECKREF)
temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
temp_sec_context);
/* Create the plan */
qplan = SPI_prepare(querystr, nargs, argtypes);
......
......@@ -53,12 +53,14 @@
#include "catalog/namespace.h"
#include "executor/executor.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/cost.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "rewrite/rowsecurity.h"
#include "storage/lmgr.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
......@@ -151,6 +153,8 @@ CreateCachedPlan(Node *raw_parse_tree,
CachedPlanSource *plansource;
MemoryContext source_context;
MemoryContext oldcxt;
Oid user_id;
int security_context;
Assert(query_string != NULL); /* required as of 8.4 */
......@@ -173,6 +177,8 @@ CreateCachedPlan(Node *raw_parse_tree,
*/
oldcxt = MemoryContextSwitchTo(source_context);
GetUserIdAndSecContext(&user_id, &security_context);
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
plansource->magic = CACHEDPLANSOURCE_MAGIC;
plansource->raw_parse_tree = copyObject(raw_parse_tree);
......@@ -201,6 +207,11 @@ CreateCachedPlan(Node *raw_parse_tree,
plansource->generic_cost = -1;
plansource->total_custom_cost = 0;
plansource->num_custom_plans = 0;
plansource->has_rls = false;
plansource->rowSecurityDisabled
= (security_context & SECURITY_ROW_LEVEL_DISABLED) != 0;
plansource->row_security_env = row_security;
plansource->planUserId = InvalidOid;
MemoryContextSwitchTo(oldcxt);
......@@ -371,7 +382,8 @@ CompleteCachedPlan(CachedPlanSource *plansource,
*/
extract_query_dependencies((Node *) querytree_list,
&plansource->relationOids,
&plansource->invalItems);
&plansource->invalItems,
&plansource->has_rls);
/*
* Also save the current search_path in the query_context. (This
......@@ -565,6 +577,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
return NIL;
}
/*
* If this is a new cached plan, then set the user id it was planned by
* and under what row security settings; these are needed to determine
* plan invalidation when RLS is involved.
*/
if (!OidIsValid(plansource->planUserId))
{
plansource->planUserId = GetUserId();
plansource->row_security_env = row_security;
}
/*
* If the query is currently valid, we should have a saved search_path ---
* check to see if that matches the current environment. If not, we want
......@@ -582,6 +605,23 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
}
}
/*
* Check if row security is enabled for this query and things have changed
* such that we need to invalidate this plan and rebuild it. Note that if
* row security was explicitly disabled (eg: this is a FK check plan) then
* we don't invalidate due to RLS.
*
* Otherwise, if the plan has a possible RLS dependency, force a replan if
* either the role under which the plan was planned or the row_security
* setting has been changed.
*/
if (plansource->is_valid
&& !plansource->rowSecurityDisabled
&& plansource->has_rls
&& (plansource->planUserId != GetUserId()
|| plansource->row_security_env != row_security))
plansource->is_valid = false;
/*
* If the query is currently valid, acquire locks on the referenced
* objects; then check again. We need to do it this way to cover the race
......@@ -723,7 +763,8 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
*/
extract_query_dependencies((Node *) qlist,
&plansource->relationOids,
&plansource->invalItems);
&plansource->invalItems,
&plansource->has_rls);
/*
* Also save the current search_path in the query_context. (This should
......
......@@ -55,6 +55,7 @@
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
......@@ -966,6 +967,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->trigdesc = NULL;
if (relation->rd_rel->relhasrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rsdesc = NULL;
/*
* if it's an index, initialize index-related information
*/
......@@ -1936,6 +1942,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_indexcxt);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rsdesc)
MemoryContextDelete(relation->rsdesc->rscxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
......@@ -3242,8 +3250,8 @@ RelationCacheInitializePhase3(void)
* wrong in the results from formrdesc or the relcache cache file. If we
* faked up relcache entries using formrdesc, then read the real pg_class
* rows and replace the fake entries with them. Also, if any of the
* relcache entries have rules or triggers, load that info the hard way
* since it isn't recorded in the cache file.
* relcache entries have rules, triggers, or security policies, load that
* info the hard way since it isn't recorded in the cache file.
*
* Whenever we access the catalogs to read data, there is a possibility of
* a shared-inval cache flush causing relcache entries to be removed.
......@@ -3334,6 +3342,21 @@ RelationCacheInitializePhase3(void)
restart = true;
}
/*
* 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
* have a policy while relhasrowsecurity is true-
* RelationBuildRowSecurity will create a single default-deny policy
* if there is no policy defined in pg_rowsecurity.
*/
if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL)
{
RelationBuildRowSecurity(relation);
Assert (relation->rsdesc != NULL);
restart = true;
}
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
......@@ -4706,6 +4729,7 @@ load_relcache_init_file(bool shared)
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rsdesc = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
......
......@@ -61,6 +61,7 @@
#include "replication/syncrep.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
#include "rewrite/rowsecurity.h"
#include "storage/bufmgr.h"
#include "storage/dsm_impl.h"
#include "storage/standby.h"
......@@ -400,6 +401,23 @@ static const struct config_enum_entry huge_pages_options[] = {
{NULL, 0, false}
};
/*
* Although only "on", "off", and "force" are documented, we
* accept all the likely variants of "on" and "off".
*/
static const struct config_enum_entry row_security_options[] = {
{"on", ROW_SECURITY_ON, false},
{"off", ROW_SECURITY_OFF, false},
{"force", ROW_SECURITY_FORCE, false},
{"true", ROW_SECURITY_ON, true},
{"false", ROW_SECURITY_OFF, true},
{"yes", ROW_SECURITY_ON, true},
{"no", ROW_SECURITY_OFF, true},
{"1", ROW_SECURITY_ON, true},
{"0", ROW_SECURITY_OFF, true},
{NULL, 0, false}
};
/*
* Options for enum values stored in other modules
*/
......@@ -456,6 +474,8 @@ int tcp_keepalives_idle;
int tcp_keepalives_interval;
int tcp_keepalives_count;
int row_security = true;
/*
* This really belongs in pg_shmem.c, but is defined here so that it doesn't
* need to be duplicated in all the different implementations of pg_shmem.c.
......@@ -3517,6 +3537,16 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
{
{"row_security", PGC_USERSET, CONN_AUTH_SECURITY,
gettext_noop("Enable row security."),
gettext_noop("When enabled, row security will be applied to all users.")
},
&row_security,
ROW_SECURITY_ON, row_security_options,
NULL, NULL, NULL
},
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
......
......@@ -90,6 +90,7 @@
#ssl_crl_file = '' # (change requires restart)
#password_encryption = on
#db_user_namespace = off
#row_security = on
# GSSAPI using Kerberos
#krb_server_keyfile = ''
......
......@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "reading rewrite rules\n");
getRules(fout, &numRules);
if (g_verbose)
write_msg(NULL, "reading row-security policies\n");
getRowSecurity(fout, tblinfo, numTables);
*numTablesPtr = numTables;
return tblinfo;
}
......
......@@ -150,6 +150,7 @@ typedef struct _restoreOptions
bool single_txn;
bool *idWanted; /* array showing which dump IDs to emit */
int enable_row_security;
} RestoreOptions;
typedef void (*SetupWorkerPtr) (Archive *AH, RestoreOptions *ropt);
......
......@@ -373,6 +373,14 @@ RestoreArchive(Archive *AHX)
ahprintf(AH, "BEGIN;\n\n");
}
/*
* Enable row-security if necessary.
*/
if (!ropt->enable_row_security)
ahprintf(AH, "SET row_security = off;\n");
else
ahprintf(AH, "SET row_security = on;\n");
/*
* Establish important parameter values right away.
*/
......@@ -3242,6 +3250,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
strcmp(te->desc, "INDEX") == 0 ||
strcmp(te->desc, "RULE") == 0 ||
strcmp(te->desc, "TRIGGER") == 0 ||
strcmp(te->desc, "ROW SECURITY") == 0 ||
strcmp(te->desc, "USER MAPPING") == 0)
{
/* these object types don't have separate owners */
......
This diff is collapsed.
......@@ -111,7 +111,8 @@ typedef enum
DO_PRE_DATA_BOUNDARY,
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
DO_REFRESH_MATVIEW
DO_REFRESH_MATVIEW,
DO_ROW_SECURITY
} DumpableObjectType;
typedef struct _dumpableObject
......@@ -245,6 +246,7 @@ typedef struct _tableInfo
bool hasindex; /* does it have any indexes? */
bool hasrules; /* does it have any rules? */
bool hastriggers; /* does it have any triggers? */
bool hasrowsec; /* does it have any row-security policy? */
bool hasoids; /* does it have OIDs? */
uint32 frozenxid; /* for restore frozen xid */
uint32 minmxid; /* for restore min multi xid */
......@@ -486,6 +488,23 @@ typedef struct _blobInfo
char *blobacl;
} BlobInfo;
/*
* The RowSecurityInfo struct is used to represent row policies on a table and
* to indicate if a table has RLS enabled (ENABLE ROW SECURITY). If
* rsecpolname is NULL, then the record indicates ENABLE ROW SECURITY, while if
* it's non-NULL then this is a regular policy definition.
*/
typedef struct _rowSecurityInfo
{
DumpableObject dobj;
TableInfo *rstable;
char *rsecpolname; /* null indicates RLS is enabled on rel */
char *rseccmd;
char *rsecroles;
char *rsecqual;
char *rsecwithcheck;
} RowSecurityInfo;
/* global decls */
extern bool force_quotes; /* double-quotes for identifiers flag */
extern bool g_verbose; /* verbose flag */
......@@ -577,5 +596,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
#endif /* PG_DUMP_H */
......@@ -70,7 +70,8 @@ static const int oldObjectTypePriority[] =
10, /* DO_PRE_DATA_BOUNDARY */
13, /* DO_POST_DATA_BOUNDARY */
20, /* DO_EVENT_TRIGGER */
15 /* DO_REFRESH_MATVIEW */
15, /* DO_REFRESH_MATVIEW */
21 /* DO_ROW_SECURITY */
};
/*
......@@ -118,7 +119,8 @@ static const int newObjectTypePriority[] =
22, /* DO_PRE_DATA_BOUNDARY */
25, /* DO_POST_DATA_BOUNDARY */
32, /* DO_EVENT_TRIGGER */
33 /* DO_REFRESH_MATVIEW */
33, /* DO_REFRESH_MATVIEW */
34 /* DO_ROW_SECURITY */
};
static DumpId preDataBoundId;
......@@ -1434,6 +1436,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"BLOB DATA (ID %d)",
obj->dumpId);
return;
case DO_ROW_SECURITY:
snprintf(buf, bufsize,
"ROW-SECURITY POLICY (ID %d OID %u)",
obj->dumpId, obj->catId.oid);
return;
case DO_PRE_DATA_BOUNDARY:
snprintf(buf, bufsize,
"PRE-DATA BOUNDARY (ID %d)",
......
......@@ -663,17 +663,29 @@ dumpRoles(PGconn *conn)
i_rolpassword,
i_rolvaliduntil,
i_rolreplication,
i_rolbypassrls,
i_rolcomment,
i_is_current_user;
int i;
/* note: rolconfig is dumped later */
if (server_version >= 90100)
if (server_version >= 90500)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, rolbypassrls, "
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
"ORDER BY 2");
else if (server_version >= 90100)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, "
"false as rolbypassrls, "
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
......@@ -684,6 +696,7 @@ dumpRoles(PGconn *conn)
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, false as rolreplication, "
"false as rolbypassrls, "
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
......@@ -694,6 +707,7 @@ dumpRoles(PGconn *conn)
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, false as rolreplication, "
"false as rolbypassrls, "
"null as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
......@@ -724,6 +738,7 @@ dumpRoles(PGconn *conn)
"null::text as rolpassword, "
"null::abstime as rolvaliduntil, "
"false as rolreplication, "
"false as rolbypassrls, "
"null as rolcomment, false "
"FROM pg_group "
"WHERE NOT EXISTS (SELECT 1 FROM pg_shadow "
......@@ -743,6 +758,7 @@ dumpRoles(PGconn *conn)
i_rolpassword = PQfnumber(res, "rolpassword");
i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
i_rolreplication = PQfnumber(res, "rolreplication");
i_rolbypassrls = PQfnumber(res, "rolbypassrls");
i_rolcomment = PQfnumber(res, "rolcomment");
i_is_current_user = PQfnumber(res, "is_current_user");
......@@ -810,6 +826,11 @@ dumpRoles(PGconn *conn)
else
appendPQExpBufferStr(buf, " NOREPLICATION");
if (strcmp(PQgetvalue(res, i, i_rolbypassrls), "t") == 0)
appendPQExpBufferStr(buf, " BYPASSRLS");
else
appendPQExpBufferStr(buf, " NOBYPASSRLS");
if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0)
appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
PQgetvalue(res, i, i_rolconnlimit));
......
......@@ -70,6 +70,7 @@ main(int argc, char **argv)
Archive *AH;
char *inputFileSpec;
static int disable_triggers = 0;
static int enable_row_security = 0;
static int if_exists = 0;
static int no_data_for_failed_tables = 0;
static int outputNoTablespaces = 0;
......@@ -111,6 +112,7 @@ main(int argc, char **argv)
* the following options don't have an equivalent short option letter
*/
{"disable-triggers", no_argument, &disable_triggers, 1},
{"enable-row-security", no_argument, &enable_row_security, 1},
{"if-exists", no_argument, &if_exists, 1},
{"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1},
{"no-tablespaces", no_argument, &outputNoTablespaces, 1},
......@@ -333,6 +335,7 @@ main(int argc, char **argv)
}
opts->disable_triggers = disable_triggers;
opts->enable_row_security = enable_row_security;
opts->noDataForFailedTables = no_data_for_failed_tables;
opts->noTablespace = outputNoTablespaces;
opts->use_setsessauth = use_setsessauth;
......@@ -460,6 +463,7 @@ usage(const char *progname)
printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n"));
printf(_(" -1, --single-transaction restore as a single transaction\n"));
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
printf(_(" --enable-row-security enable row level security\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"
" created\n"));
......
......@@ -742,7 +742,7 @@ permissionsList(const char *pattern)
PQExpBufferData buf;
PGresult *res;
printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, true, false, false};
static const bool translate_columns[] = {false, false, true, false, false, false};
initPQExpBuffer(&buf);
......@@ -778,7 +778,38 @@ permissionsList(const char *pattern)
" FROM pg_catalog.pg_attribute a\n"
" WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n"
" ), E'\\n') AS \"%s\"",
gettext_noop("Column access privileges"));
gettext_noop("Column privileges"));
if (pset.sversion >= 90500)
appendPQExpBuffer(&buf,
",\n pg_catalog.array_to_string(ARRAY(\n"
" SELECT rsecpolname\n"
" || CASE WHEN rseccmd IS NOT NULL THEN\n"
" E' (' || rseccmd || E')'\n"
" ELSE E':' \n"
" END\n"
" || CASE WHEN rs.rsecqual IS NOT NULL THEN\n"
" E'\\n (u): ' || pg_catalog.pg_get_expr(rsecqual, rsecrelid)\n"
" ELSE E''\n"
" END\n"
" || CASE WHEN rsecwithcheck IS NOT NULL THEN\n"
" E'\\n (c): ' || pg_catalog.pg_get_expr(rsecwithcheck, rsecrelid)\n"
" ELSE E''\n"
" END"
" || CASE WHEN rs.rsecroles <> '{0}' THEN\n"
" E'\\n to: ' || pg_catalog.array_to_string(\n"
" ARRAY(\n"
" SELECT rolname\n"
" FROM pg_catalog.pg_roles\n"
" WHERE oid = ANY (rs.rsecroles)\n"
" ORDER BY 1\n"
" ), E', ')\n"
" ELSE E''\n"
" END\n"
" FROM pg_catalog.pg_rowsecurity rs\n"
" WHERE rsecrelid = c.oid), E'\\n')\n"
" AS \"%s\"",
gettext_noop("Policies"));
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
......@@ -1173,6 +1204,7 @@ describeOneTableDetails(const char *schemaname,
bool hasindex;
bool hasrules;
bool hastriggers;
bool hasrowsecurity;
bool hasoids;
Oid tablespace;
char *reloptions;
......@@ -1194,11 +1226,28 @@ describeOneTableDetails(const char *schemaname,
initPQExpBuffer(&tmpbuf);
/* Get general table info */
if (pset.sversion >= 90400)
if (pset.sversion >= 90500)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasoids, "
"c.relhastriggers, c.relhasrowsecurity, 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 "
"LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
"WHERE c.oid = '%s';",
(verbose ?
"pg_catalog.array_to_string(c.reloptions || "
"array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
: "''"),
oid);
}
else if (pset.sversion >= 90400)
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, 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"
......@@ -1215,7 +1264,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasoids, "
"c.relhastriggers, 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"
......@@ -1232,7 +1281,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasoids, "
"c.relhastriggers, 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 "
......@@ -1248,7 +1297,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
"c.relhastriggers, c.relhasoids, "
"c.relhastriggers, 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"
......@@ -1263,7 +1312,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT relchecks, relkind, relhasindex, relhasrules, "
"reltriggers <> 0, relhasoids, "
"reltriggers <> 0, false, relhasoids, "
"%s, reltablespace\n"
"FROM pg_catalog.pg_class WHERE oid = '%s';",
(verbose ?
......@@ -1274,7 +1323,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT relchecks, relkind, relhasindex, relhasrules, "
"reltriggers <> 0, relhasoids, "
"reltriggers <> 0, false, relhasoids, "
"'', reltablespace\n"
"FROM pg_catalog.pg_class WHERE oid = '%s';",
oid);
......@@ -1283,7 +1332,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT relchecks, relkind, relhasindex, relhasrules, "
"reltriggers <> 0, relhasoids, "
"reltriggers <> 0, false, relhasoids, "
"'', ''\n"
"FROM pg_catalog.pg_class WHERE oid = '%s';",
oid);
......@@ -1306,18 +1355,19 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
tableinfo.reloptions = (pset.sversion >= 80200) ?
pg_strdup(PQgetvalue(res, 0, 6)) : NULL;
pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
tableinfo.tablespace = (pset.sversion >= 80000) ?
atooid(PQgetvalue(res, 0, 7)) : 0;
atooid(PQgetvalue(res, 0, 8)) : 0;
tableinfo.reloftype = (pset.sversion >= 90000 &&
strcmp(PQgetvalue(res, 0, 8), "") != 0) ?
pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
strcmp(PQgetvalue(res, 0, 9), "") != 0) ?
pg_strdup(PQgetvalue(res, 0, 9)) : NULL;
tableinfo.relpersistence = (pset.sversion >= 90100) ?
*(PQgetvalue(res, 0, 9)) : 0;
*(PQgetvalue(res, 0, 10)) : 0;
tableinfo.relreplident = (pset.sversion >= 90400) ?
*(PQgetvalue(res, 0, 10)) : 'd';
*(PQgetvalue(res, 0, 11)) : 'd';
PQclear(res);
res = NULL;
......@@ -1948,6 +1998,67 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
if (pset.sversion >= 90500)
appendPQExpBuffer(&buf,
",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
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 (tableinfo.hasrowsecurity)
{
printfPQExpBuffer(&buf,
"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"
"pg_catalog.pg_get_expr(rs.rsecqual, rs.rsecrelid),\n"
"pg_catalog.pg_get_expr(rs.rsecwithcheck, rs.rsecrelid),\n"
"rs.rseccmd AS cmd\n"
"FROM pg_catalog.pg_rowsecurity rs\n"
"WHERE rs.rsecrelid = '%s' ORDER BY 1;",
oid);
result = PSQLexec(buf.data, false);
if (!result)
goto error_return;
else
tuples = PQntuples(result);
if (tuples > 0)
{
printTableAddFooter(&cont, _("Policies:"));
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, " APPLIED TO %s",
PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
}
}
}
PQclear(result);
}
/* print rules */
if (tableinfo.hasrules && tableinfo.relkind != 'm')
{
......@@ -2529,6 +2640,11 @@ describeRoles(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\n, r.rolreplication");
}
if (pset.sversion >= 90500)
{
appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
}
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
processSQLNamePattern(pset.db, &buf, pattern, false, false,
......
This diff is collapsed.
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201409162
#define CATALOG_VERSION_NO 201409191
#endif
......@@ -147,6 +147,7 @@ typedef enum ObjectClass
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
OCLASS_ROWSECURITY, /* pg_rowsecurity */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
......
......@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops));
#define RowSecurityOidIndexId 3257
DECLARE_UNIQUE_INDEX(pg_rowsecurity_polname_relid_index, 3258, on pg_rowsecurity using btree(rsecrelid oid_ops, rsecpolname name_ops));
#define RowSecurityRelidPolnameIndexId 3258
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
......
......@@ -52,6 +52,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
bool rolcatupdate; /* allowed to alter catalogs manually? */
bool rolcanlogin; /* allowed to log in as session user? */
bool rolreplication; /* role used for streaming replication */
bool rolbypassrls; /* allowed to bypass row level security? */
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
......@@ -73,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* compiler constants for pg_authid
* ----------------
*/
#define Natts_pg_authid 11
#define Natts_pg_authid 12
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
......@@ -82,9 +83,10 @@ typedef FormData_pg_authid *Form_pg_authid;
#define Anum_pg_authid_rolcatupdate 6
#define Anum_pg_authid_rolcanlogin 7
#define Anum_pg_authid_rolreplication 8
#define Anum_pg_authid_rolconnlimit 9
#define Anum_pg_authid_rolpassword 10
#define Anum_pg_authid_rolvaliduntil 11
#define Anum_pg_authid_rolbypassrls 9
#define Anum_pg_authid_rolconnlimit 10
#define Anum_pg_authid_rolpassword 11
#define Anum_pg_authid_rolvaliduntil 12
/* ----------------
* initial contents of pg_authid
......@@ -93,7 +95,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* user choices.
* ----------------
*/
DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_ ));
DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
#define BOOTSTRAP_SUPERUSERID 10
......
......@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
bool relhasrowsecurity; /* has (or has had) row-security policy */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
......@@ -94,7 +95,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
#define Natts_pg_class 29
#define Natts_pg_class 30
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
......@@ -118,12 +119,13 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
#define Anum_pg_class_relispopulated 24
#define Anum_pg_class_relreplident 25
#define Anum_pg_class_relfrozenxid 26
#define Anum_pg_class_relminmxid 27
#define Anum_pg_class_relacl 28
#define Anum_pg_class_reloptions 29
#define Anum_pg_class_relhasrowsecurity 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
/* ----------------
* initial contents of pg_class
......@@ -138,13 +140,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 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 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 27 0 t 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 27 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 29 0 t 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 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
......
This diff is collapsed.
/*-------------------------------------------------------------------------
*
* policy.h
* prototypes for policy.c.
*
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/commands/policy.h
*
*-------------------------------------------------------------------------
*/
#ifndef POLICY_H
#define POLICY_H
#include "nodes/parsenodes.h"
extern void RelationBuildRowSecurity(Relation relation);
extern void RemovePolicyById(Oid policy_id);
extern Oid CreatePolicy(CreatePolicyStmt *stmt);
extern Oid AlterPolicy(AlterPolicyStmt *stmt);
Oid get_relation_policy_oid(Oid relid,
const char *policy_name, bool missing_ok);
Oid rename_policy(RenameStmt *stmt);
#endif /* POLICY_H */
......@@ -272,6 +272,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_ROW_LEVEL_DISABLED 0x0004
extern char *DatabasePath;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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