<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>
</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>
</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>
<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>
</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>
<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>
<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>
<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>
<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>
<!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">
<!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">
<!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">
--- /dev/null
+<!--
+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>
| 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>'
<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>
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
</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>
--- /dev/null
+<!--
+doc/src/sgml/ref/create_policy.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEPOLICY">
+ <indexterm zone="sql-createpolicy">
+ <primary>CREATE POLICY</primary>
+ </indexterm>
+
+ <refmeta>
+ <refentrytitle>CREATE POLICY</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE POLICY</refname>
+ <refpurpose>define a new row-security policy for a table</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
+ [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
+ [ 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>
+ The <command>CREATE POLICY</command> command defines a new row-security
+ policy for a table. Note that row-security must also be enabled on the
+ table using <command>ALTER TABLE</command> in order for created policies
+ to be applied.
+ </para>
+
+ <para>
+ A row-security policy is an expression which is added to the security-barrier
+ qualifications of queries which are run against the table the policy is on,
+ or an expression which is added to the with-check options for a table and
+ which is applied to rows which would be added to the table.
+ The security-barrier qualifications will always be evaluated prior to any
+ user-defined functions or user-provided WHERE clauses, while the with-check
+ expression will be evaluated against the rows which are going to be added to
+ the table. By adding policies to a table, a user can limit the rows which a
+ given user can select, insert, update, or delete. This capability is also
+ known as Row-Level Security or RLS.
+ </para>
+
+ <para>
+ Policy names are per-table, therefore one policy name can be used for many
+ different tables and have a definition for each table which is appropriate to
+ that table.
+ </para>
+
+ <para>
+ Policies can be applied for specific commands or for specific roles. The
+ default for newly created policies is that they apply for all commands and
+ roles, unless otherwise specified. If multiple policies apply to a given
+ query, they will be combined using OR.
+ </para>
+
+ <para>
+ Note that while row-security policies will be applied for explicit queries
+ against tables in the system, they are not applied when the system is
+ performing internal referential integrity checks or validating constraints.
+ This means there are indirect ways to determine that a given value exists.
+ An example of this is attempting to insert a duplicate value
+ into a column which is the primary key or has a unique constraint. If the
+ insert fails then the user can infer that the value already exists (this
+ example assumes that the user is permitted by policy to insert
+ records which they are not allowed to see). Another example is where a user
+ is allowed to insert into a table which references another, otherwise hidden
+ table. Existence can be determined by the user inserting values into the
+ referencing table, where success would indicate that the value exists in the
+ referenced table. These issues can be addressed by carefully crafting
+ policies which prevent users from being able to insert, delete, or update
+ records at all which might possibly indicate a value they are not otherwise
+ able to see, or by using generated values (eg: surrogate keys) instead.
+ </para>
+
+ <para>
+ Regarding how policy expressions interact with the user: as the expressions
+ are added to the user's query directly, they will be run with the rights of
+ the user running the overall query. Therefore, users who are using a given
+ policy must be able to access any tables or functions referenced in the
+ expression or they will simply receive a permission denied error when
+ attempting to query the RLS-enabled table. This does not change how views
+ work, however. As with normal queries and views, permission checks and
+ policies for the tables which are referenced by a view will use the view
+ owner's rights and any policies which apply to the view owner.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name of the policy to be created. This must be distinct from the
+ name of any other policy for the table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">table_name</replaceable></term>
+ <listitem>
+ <para>
+ The name (optionally schema-qualified) of the table the
+ policy applies to.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">command</replaceable></term>
+ <listitem>
+ <para>
+ The command to which the policy applies. Valid options are
+ <command>ALL</command>, <command>SELECT</command>,
+ <command>INSERT</command>, <command>UPDATE</command>,
+ and <command>DELETE</command>.
+ <command>ALL</command> is the default.
+ See below for specifics regarding how these are applied.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">role_name</replaceable></term>
+ <listitem>
+ <para>
+ The roles to which the policy is to be applied. The default is
+ <literal>PUBLIC</literal>, which will apply the policy to all roles.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">expression</replaceable></term>
+ <listitem>
+ <para>
+ Any <acronym>SQL</acronym> conditional expression (returning
+ <type>boolean</type>). The conditional expression cannot contain
+ any aggregate or window functions. This expression will be added
+ to queries to filter out the records which are visible to the query.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">check_expression</replaceable></term>
+ <listitem>
+ <para>
+ Any <acronym>SQL</acronym> conditional expression (returning
+ <type>boolean</type>). The condition expression cannot contain
+ any aggregate or window functions. This expression will be added
+ to queries which are attempting to add records to the table as
+ with-check options, and an error will be thrown if this condition
+ returns false for any records being added.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Per-Command policies</title>
+
+ <variablelist>
+
+ <varlistentry id="SQL-CREATEPOLICY-ALL">
+ <term><literal>ALL</></term>
+ <listitem>
+ <para>
+ Using <literal>ALL</literal> for a policy means that it will apply
+ to all commands, regardless of the type of command. If an
+ <literal>ALL</literal> policy exists and more specific policies
+ exist, then both the <literal>ALL</literal> policy and the more
+ specific policy (or policies) will be combined using
+ <literal>OR</literal>, as usual for overlapping policies.
+ Additionally, <literal>ALL</literal> policies will be applied to
+ both the selection side of a query and the modification side, using
+ the USING policy for both if only a USING policy has been defined.
+
+ As an example, if an <literal>UPDATE</literal> is issued, then the
+ <literal>ALL</literal> policy will be applicable to both what the
+ <literal>UPDATE</literal> will be able to select out as rows to be
+ updated (with the USING expression being applied), and it will be
+ applied to rows which result from the <literal>UPDATE</literal>
+ statement, to check if they are permitted to be added to the table
+ (using the WITH CHECK expression, if defined, and the USING expression
+ otherwise). If an INSERT or UPDATE command attempts to add rows to
+ the table which do not pass the <literal>ALL</literal> WITH CHECK
+ (or USING, if no WITH CHECK expression is defined) expression, the
+ command will error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="SQL-CREATEPOLICY-SELECT">
+ <term><literal>SELECT</></term>
+ <listitem>
+ <para>
+ Using <literal>SELECT</literal> for a policy means that it will apply
+ to <literal>SELECT</literal> commands. The result is that only those
+ records from the relation which pass the <literal>SELECT</literal>
+ policy will be returned, even if other records exist in the relation.
+ The <literal>SELECT</literal> policy only accepts the USING expression
+ as it only ever applies in cases where records are being retrived from
+ the relation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="SQL-CREATEPOLICY-INSERT">
+ <term><literal>INSERT</></term>
+ <listitem>
+ <para>
+ Using <literal>INSERT</literal> for a policy means that it will apply
+ to <literal>INSERT</literal> commands. Rows being inserted which do
+ not pass this policy will result in a policy violation ERROR and the
+ entire <literal>INSERT</literal> command will be aborted. The
+ <literal>INSERT</literal> policy only accepts the WITH CHECK expression
+ as it only ever applies in cases where records are being added to the
+ relation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="SQL-CREATEPOLICY-UPDATE">
+ <term><literal>DELETE</></term>
+ <listitem>
+ <para>
+ Using <literal>UPDATE</literal> for a policy means that it will apply
+ to <literal>UPDATE</literal> commands. As <literal>UPDATE</literal>
+ involves pulling an existing record and then making changes to some
+ portion (but possibly not all) of the record, the
+ <literal>UPDATE</literal> policy accepts both a USING expression and
+ a WITH CHECK expression. The USING expression will be used to
+ determine which records the <literal>UPDATE</literal> command will
+ see to operate against, while the <literal>WITH CHECK</literal>
+ expression defines what rows are allowed to be added back into the
+ relation (similar to the <literal>INSERT</literal> policy).
+ Any rows whose resulting values do not pass the
+ <literal>WITH CHECK</literal> expression will cause an ERROR and the
+ entire command will be aborted.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="SQL-CREATEPOLICY-DELETE">
+ <term><literal>DELETE</></term>
+ <listitem>
+ <para>
+ Using <literal>DELETE</literal> for a policy means that it will apply
+ to <literal>DELETE</literal> commands. Only rows which pass this
+ policy will be seen by a <literal>DELETE</literal> command. Rows may
+ be visible through a <literal>SELECT</literal> which are not seen by a
+ <literal>DELETE</literal>, as they do not pass the USING expression
+ for the <literal>DELETE</literal>, and rows which are not visible
+ through the <literal>SELECT</literal> policy may be deleted if they
+ pass the <literal>DELETE</literal> USING policy. The
+ <literal>DELETE</literal> policy only accept the USING expression as
+ it only ever applies in cases where records are being extracted from
+ the relation for deletion.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ You must be the owner of a table to create or change policies for it.
+ </para>
+
+ <para>
+ In order to maintain <firstterm>referential integrity</firstterm> between
+ two related tables, row-security policies are not applied when the system
+ performs checks on foreign key constraints.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ <command>CREATE POLICY</command> is a <productname>PostgreSQL</productname>
+ extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-alterpolicy"></member>
+ <member><xref linkend="sql-droppolicy"></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
| 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>'
</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>
--- /dev/null
+<!--
+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>
&alterOperator;
&alterOperatorClass;
&alterOperatorFamily;
+ &alterPolicy;
&alterRole;
&alterRule;
&alterSchema;
&createOperator;
&createOperatorClass;
&createOperatorFamily;
+ &createPolicy;
&createRole;
&createRule;
&createSchema;
&dropOperatorClass;
&dropOperatorFamily;
&dropOwned;
+ &dropPolicy;
&dropRole;
&dropRule;
&dropSchema;
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 \
)
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).
#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"
#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"
RemoveEventTriggerById(object->objectId);
break;
+ case OCLASS_ROWSECURITY:
+ RemovePolicyById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
+
+ case RowSecurityRelationId:
+ return OCLASS_ROWSECURITY;
}
/* shouldn't get here */
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);
#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"
#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"
-1,
false
},
+ {
+ RowSecurityRelationId,
+ RowSecurityOidIndexId,
+ -1,
+ -1,
+ Anum_pg_rowsecurity_rsecpolname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ -1,
+ false
+ },
{
EventTriggerRelationId,
EventTriggerOidIndexId,
case OBJECT_RULE:
case OBJECT_TRIGGER:
case OBJECT_CONSTRAINT:
+ case OBJECT_POLICY:
address = get_object_address_relobject(objtype, objname,
&relation, missing_ok);
break;
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 */
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,
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,
rolconnlimit,
'********'::text as rolpassword,
rolvaliduntil,
+ rolbypassrls,
setconfig as rolconfig,
pg_authid.oid
FROM pg_authid LEFT JOIN pg_db_role_setting s
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,
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';
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
#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"
case OBJECT_TRIGGER:
return renametrig(stmt);
+ case OBJECT_POLICY:
+ return rename_policy(stmt);
+
case OBJECT_DOMAIN:
case OBJECT_TYPE:
return RenameType(stmt);
#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"
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())
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;
}
}
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 */
#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"
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.
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);
{"OPERATOR", true},
{"OPERATOR CLASS", true},
{"OPERATOR FAMILY", true},
+ {"POLICY", true},
{"ROLE", false},
{"RULE", true},
{"SCHEMA", true},
case OBJECT_OPCLASS:
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
+ case OBJECT_POLICY:
case OBJECT_RULE:
case OBJECT_SCHEMA:
case OBJECT_SEQUENCE:
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
+ case OCLASS_ROWSECURITY:
return true;
case MAX_OCLASS:
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * policy.c
+ * Commands for manipulating policies.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/policy.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "commands/policy.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/pg_list.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "storage/lock.h"
+#include "utils/acl.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static void RangeVarCallbackForPolicy(const RangeVar *rv,
+ Oid relid, Oid oldrelid, void *arg);
+static const char parse_row_security_command(const char *cmd_name);
+static ArrayType* rls_role_list_to_array(List *roles);
+
+/*
+ * Callback to RangeVarGetRelidExtended().
+ *
+ * Checks the following:
+ * - the relation specified is a table.
+ * - current user owns the table.
+ * - the table is not a system table.
+ *
+ * If any of these checks fails then an error is raised.
+ */
+static void
+RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
+ void *arg)
+{
+ HeapTuple tuple;
+ Form_pg_class classform;
+ char relkind;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ relkind = classform->relkind;
+
+ /* Must own relation. */
+ if (!pg_class_ownercheck(relid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
+
+ /* No system table modifications unless explicitly allowed. */
+ if (!allowSystemTableMods && IsSystemClass(relid, classform))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ rv->relname)));
+
+ /* Relation type MUST be a table. */
+ if (relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table", rv->relname)));
+
+ ReleaseSysCache(tuple);
+}
+
+/*
+ * parse_row_security_command -
+ * helper function to convert full command strings to their char
+ * representation.
+ *
+ * cmd_name - full string command name. Valid values are 'all', 'select',
+ * 'insert', 'update' and 'delete'.
+ *
+ */
+static const char
+parse_row_security_command(const char *cmd_name)
+{
+ char cmd;
+
+ if (!cmd_name)
+ elog(ERROR, "Unregonized command.");
+
+ if (strcmp(cmd_name, "all") == 0)
+ cmd = 0;
+ else if (strcmp(cmd_name, "select") == 0)
+ cmd = ACL_SELECT_CHR;
+ else if (strcmp(cmd_name, "insert") == 0)
+ cmd = ACL_INSERT_CHR;
+ else if (strcmp(cmd_name, "update") == 0)
+ cmd = ACL_UPDATE_CHR;
+ else if (strcmp(cmd_name, "delete") == 0)
+ cmd = ACL_DELETE_CHR;
+ else
+ elog(ERROR, "Unregonized command.");
+ /* error unrecognized command */
+
+ return cmd;
+}
+
+/*
+ * rls_role_list_to_array
+ * helper function to convert a list of role names in to an array of
+ * role ids.
+ *
+ * Note: If PUBLIC is provided as a role name, then ACL_ID_PUBLIC is
+ * used as the role id.
+ *
+ * roles - the list of role names to convert.
+ */
+static ArrayType *
+rls_role_list_to_array(List *roles)
+{
+ ArrayType *role_ids;
+ Datum *temp_array;
+ ListCell *cell;
+ int num_roles;
+ int i = 0;
+
+ /* Handle no roles being passed in as being for public */
+ if (roles == NIL)
+ {
+ temp_array = (Datum *) palloc(sizeof(Datum));
+ temp_array[0] = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ role_ids = construct_array(temp_array, 1, OIDOID, sizeof(Oid), true,
+ 'i');
+ return role_ids;
+ }
+
+ num_roles = list_length(roles);
+ temp_array = (Datum *) palloc(num_roles * sizeof(Datum));
+
+ foreach(cell, roles)
+ {
+ Oid roleid = get_role_oid_or_public(strVal(lfirst(cell)));
+
+ /*
+ * PUBLIC covers all roles, so it only makes sense alone.
+ */
+ if (roleid == ACL_ID_PUBLIC)
+ {
+ if (num_roles != 1)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("ignoring roles specified other than public"),
+ errhint("All roles are members of the public role.")));
+
+ temp_array[0] = ObjectIdGetDatum(roleid);
+ num_roles = 1;
+ break;
+ }
+ else
+ temp_array[i++] = ObjectIdGetDatum(roleid);
+ }
+
+ role_ids = construct_array(temp_array, num_roles, OIDOID, sizeof(Oid), true,
+ 'i');
+
+ return role_ids;
+}
+
+/*
+ * Load row-security policy from the catalog, and keep it in
+ * the relation cache.
+ */
+void
+RelationBuildRowSecurity(Relation relation)
+{
+ Relation catalog;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ MemoryContext rscxt = NULL;
+ RowSecurityDesc *rsdesc = NULL;
+
+ catalog = heap_open(RowSecurityRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+
+ sscan = systable_beginscan(catalog, RowSecurityRelidPolnameIndexId, true,
+ NULL, 1, &skey);
+ PG_TRY();
+ {
+ /*
+ * Set up our memory context- we will always set up some kind of
+ * policy here. If no explicit policies are found then an implicit
+ * default-deny policy is created.
+ */
+ rscxt = AllocSetContextCreate(CacheMemoryContext,
+ "Row-security descriptor",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
+ rsdesc->rscxt = rscxt;
+
+ /*
+ * Loop through the row-level security entries for this relation, if
+ * any.
+ */
+ while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ {
+ Datum value_datum;
+ char cmd_value;
+ ArrayType *roles;
+ char *qual_value;
+ Expr *qual_expr;
+ char *with_check_value;
+ Expr *with_check_qual;
+ char *policy_name_value;
+ Oid policy_id;
+ bool isnull;
+ RowSecurityPolicy *policy = NULL;
+
+ oldcxt = MemoryContextSwitchTo(rscxt);
+
+ /* Get policy command */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ RelationGetDescr(catalog), &isnull);
+ if (isnull)
+ cmd_value = 0;
+ else
+ cmd_value = DatumGetChar(value_datum);
+
+ /* Get policy name */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecpolname,
+ RelationGetDescr(catalog), &isnull);
+ Assert(!isnull);
+ policy_name_value = DatumGetCString(value_datum);
+
+ /* Get policy roles */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecroles,
+ RelationGetDescr(catalog), &isnull);
+ Assert(!isnull);
+ roles = DatumGetArrayTypeP(value_datum);
+
+ /* Get policy qual */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ RelationGetDescr(catalog), &isnull);
+ if (!isnull)
+ {
+ qual_value = TextDatumGetCString(value_datum);
+ qual_expr = (Expr *) stringToNode(qual_value);
+ }
+ else
+ qual_expr = NULL;
+
+ /* Get WITH CHECK qual */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecwithcheck,
+ RelationGetDescr(catalog), &isnull);
+
+ if (!isnull)
+ {
+ with_check_value = TextDatumGetCString(value_datum);
+ with_check_qual = (Expr *) stringToNode(with_check_value);
+ }
+ else
+ with_check_qual = NULL;
+
+ policy_id = HeapTupleGetOid(tuple);
+
+ policy = palloc0(sizeof(RowSecurityPolicy));
+ policy->policy_name = policy_name_value;
+ policy->rsecid = policy_id;
+ policy->cmd = cmd_value;
+ policy->roles = roles;
+ policy->qual = copyObject(qual_expr);
+ policy->with_check_qual = copyObject(with_check_qual);
+ policy->hassublinks = contain_subplans((Node *) qual_expr) ||
+ contain_subplans((Node *) with_check_qual);
+
+ rsdesc->policies = lcons(policy, rsdesc->policies);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (qual_expr != NULL)
+ pfree(qual_expr);
+
+ if (with_check_qual != NULL)
+ pfree(with_check_qual);
+ }
+
+ /*
+ * Check if no policies were added
+ *
+ * If no policies exist in pg_rowsecurity for this relation, then we
+ * need to create a single default-deny policy. We use InvalidOid for
+ * the Oid to indicate that this is the default-deny policy (we may
+ * decide to ignore the default policy if an extension adds policies).
+ */
+ if (rsdesc->policies == NIL)
+ {
+ RowSecurityPolicy *policy = NULL;
+ Datum role;
+
+ oldcxt = MemoryContextSwitchTo(rscxt);
+
+ role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ policy = palloc0(sizeof(RowSecurityPolicy));
+ policy->policy_name = pstrdup("default-deny policy");
+ policy->rsecid = InvalidOid;
+ policy->cmd = '\0';
+ policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true,
+ 'i');
+ policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
+ sizeof(bool), BoolGetDatum(false),
+ false, true);
+ policy->with_check_qual = copyObject(policy->qual);
+ policy->hassublinks = false;
+
+ rsdesc->policies = lcons(policy, rsdesc->policies);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+ }
+ PG_CATCH();
+ {
+ if (rscxt != NULL)
+ MemoryContextDelete(rscxt);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ systable_endscan(sscan);
+ heap_close(catalog, AccessShareLock);
+
+ relation->rsdesc = rsdesc;
+}
+
+/*
+ * RemovePolicyById -
+ * remove a row-security policy by its OID. If a policy does not exist with
+ * the provided oid, then an error is raised.
+ *
+ * policy_id - the oid of the row-security policy.
+ */
+void
+RemovePolicyById(Oid policy_id)
+{
+ Relation pg_rowsecurity_rel;
+ SysScanDesc sscan;
+ ScanKeyData skey[1];
+ HeapTuple tuple;
+ Oid relid;
+ Relation rel;
+
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /*
+ * Find the policy to delete.
+ */
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(policy_id));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityOidIndexId, true,
+ NULL, 1, skey);
+
+ tuple = systable_getnext(sscan);
+
+ /* If the policy exists, then remove it, otherwise raise an error. */
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for row-security %u", policy_id);
+
+ /*
+ * Open and exclusive-lock the relation the policy belong to.
+ */
+ relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+ rel = heap_open(relid, AccessExclusiveLock);
+ if (rel->rd_rel->relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table",
+ RelationGetRelationName(rel))));
+
+ if (!allowSystemTableMods && IsSystemRelation(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ RelationGetRelationName(rel))));
+
+ simple_heap_delete(pg_rowsecurity_rel, &tuple->t_self);
+
+ systable_endscan(sscan);
+ heap_close(rel, AccessExclusiveLock);
+
+ /*
+ * Note that, unlike some of the other flags in pg_class, relhasrowsecurity
+ * is not just an indication of if policies exist. When relhasrowsecurity
+ * 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. If no policy is defined for the relation then a default-deny
+ * policy is created and all records are filtered (except for queries from
+ * the owner).
+ */
+
+ CacheInvalidateRelcache(rel);
+
+ /* Clean up */
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+}
+
+/*
+ * CreatePolicy -
+ * handles the execution of the CREATE POLICY command.
+ *
+ * stmt - the CreatePolicyStmt that describes the policy to create.
+ */
+Oid
+CreatePolicy(CreatePolicyStmt *stmt)
+{
+ Relation pg_rowsecurity_rel;
+ Oid rowsec_id;
+ Relation target_table;
+ Oid table_id;
+ char rseccmd;
+ ArrayType *role_ids;
+ ParseState *qual_pstate;
+ ParseState *with_check_pstate;
+ RangeTblEntry *rte;
+ Node *qual;
+ Node *with_check_qual;
+ ScanKeyData skey[2];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+ Datum values[Natts_pg_rowsecurity];
+ bool isnull[Natts_pg_rowsecurity];
+ ObjectAddress target;
+ ObjectAddress myself;
+
+ /* Parse command */
+ rseccmd = parse_row_security_command(stmt->cmd);
+
+ /*
+ * If the command is SELECT or DELETE then WITH CHECK should be NULL.
+ */
+ if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
+ && stmt->with_check != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("WITH CHECK cannot be applied to SELECT or DELETE")));
+
+ /*
+ * If the command is INSERT then WITH CHECK should be the only expression
+ * provided.
+ */
+ if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("Only WITH CHECK expression allowed for INSERT")));
+
+
+ /* Collect role ids */
+ role_ids = rls_role_list_to_array(stmt->roles);
+
+ /* Parse the supplied clause */
+ qual_pstate = make_parsestate(NULL);
+ with_check_pstate = make_parsestate(NULL);
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(isnull, 0, sizeof(isnull));
+
+ /* Get id of table. Also handles permissions checks. */
+ table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForPolicy,
+ (void *) stmt);
+
+ /* Open target_table to build quals. No lock is necessary.*/
+ target_table = relation_open(table_id, NoLock);
+
+ /* Add for the regular security quals */
+ rte = addRangeTableEntryForRelation(qual_pstate, target_table,
+ NULL, false, false);
+ addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+ /* Add for the with-check quals */
+ rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
+ NULL, false, false);
+ addRTEtoQuery(with_check_pstate, rte, false, true, true);
+
+ qual = transformWhereClause(qual_pstate,
+ copyObject(stmt->qual),
+ EXPR_KIND_WHERE,
+ "POLICY");
+
+ with_check_qual = transformWhereClause(with_check_pstate,
+ copyObject(stmt->with_check),
+ EXPR_KIND_WHERE,
+ "POLICY");
+
+ /* Open pg_rowsecurity catalog */
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* Set key - row security relation id. */
+ ScanKeyInit(&skey[0],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Set key - row security policy name. */
+ ScanKeyInit(&skey[1],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->policy_name));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel,
+ RowSecurityRelidPolnameIndexId, true, NULL, 2,
+ skey);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /* Complain if the policy name already exists for the table */
+ if (HeapTupleIsValid(rsec_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("policy \"%s\" for relation \"%s\" already exists",
+ stmt->policy_name, RelationGetRelationName(target_table))));
+
+ values[Anum_pg_rowsecurity_rsecrelid - 1] = ObjectIdGetDatum(table_id);
+ values[Anum_pg_rowsecurity_rsecpolname - 1]
+ = CStringGetDatum(stmt->policy_name);
+
+ if (rseccmd)
+ values[Anum_pg_rowsecurity_rseccmd - 1] = CharGetDatum(rseccmd);
+ else
+ isnull[Anum_pg_rowsecurity_rseccmd - 1] = true;
+
+ values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
+
+ /* Add qual if present. */
+ if (qual)
+ values[Anum_pg_rowsecurity_rsecqual - 1]
+ = CStringGetTextDatum(nodeToString(qual));
+ else
+ isnull[Anum_pg_rowsecurity_rsecqual - 1] = true;
+
+ /* Add WITH CHECK qual if present */
+ if (with_check_qual)
+ values[Anum_pg_rowsecurity_rsecwithcheck - 1]
+ = CStringGetTextDatum(nodeToString(with_check_qual));
+ else
+ isnull[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
+
+ rsec_tuple = heap_form_tuple(RelationGetDescr(pg_rowsecurity_rel), values,
+ isnull);
+
+ rowsec_id = simple_heap_insert(pg_rowsecurity_rel, rsec_tuple);
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
+
+ /* Record Dependencies */
+ target.classId = RelationRelationId;
+ target.objectId = table_id;
+ target.objectSubId = 0;
+
+ myself.classId = RowSecurityRelationId;
+ myself.objectId = rowsec_id;
+ myself.objectSubId = 0;
+
+ recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+ recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
+ DEPENDENCY_NORMAL);
+
+ recordDependencyOnExpr(&myself, with_check_qual,
+ with_check_pstate->p_rtable, DEPENDENCY_NORMAL);
+
+ /* Invalidate Relation Cache */
+ CacheInvalidateRelcache(target_table);
+
+ /* Clean up. */
+ heap_freetuple(rsec_tuple);
+ free_parsestate(qual_pstate);
+ free_parsestate(with_check_pstate);
+ systable_endscan(sscan);
+ relation_close(target_table, NoLock);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+ return rowsec_id;
+}
+
+/*
+ * AlterPolicy -
+ * handles the execution of the ALTER POLICY command.
+ *
+ * stmt - the AlterPolicyStmt that describes the policy and how to alter it.
+ */
+Oid
+AlterPolicy(AlterPolicyStmt *stmt)
+{
+ Relation pg_rowsecurity_rel;
+ Oid rowsec_id;
+ Relation target_table;
+ Oid table_id;
+ ArrayType *role_ids = NULL;
+ List *qual_parse_rtable = NIL;
+ List *with_check_parse_rtable = NIL;
+ Node *qual = NULL;
+ Node *with_check_qual = NULL;
+ ScanKeyData skey[2];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+ HeapTuple new_tuple;
+ Datum values[Natts_pg_rowsecurity];
+ bool isnull[Natts_pg_rowsecurity];
+ bool replaces[Natts_pg_rowsecurity];
+ ObjectAddress target;
+ ObjectAddress myself;
+ Datum cmd_datum;
+ char rseccmd;
+ bool rseccmd_isnull;
+
+ /* Parse role_ids */
+ if (stmt->roles != NULL)
+ role_ids = rls_role_list_to_array(stmt->roles);
+
+ /* Get id of table. Also handles permissions checks. */
+ table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForPolicy,
+ (void *) stmt);
+
+ target_table = relation_open(table_id, NoLock);
+
+ /* Parse the row-security clause */
+ if (stmt->qual)
+ {
+ RangeTblEntry *rte;
+ ParseState *qual_pstate = make_parsestate(NULL);
+
+ rte = addRangeTableEntryForRelation(qual_pstate, target_table,
+ NULL, false, false);
+
+ addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+ qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
+ EXPR_KIND_WHERE,
+ "ROW SECURITY");
+
+ qual_parse_rtable = qual_pstate->p_rtable;
+ free_parsestate(qual_pstate);
+ }
+
+ /* Parse the with-check row-security clause */
+ if (stmt->with_check)
+ {
+ RangeTblEntry *rte;
+ ParseState *with_check_pstate = make_parsestate(NULL);
+
+ rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
+ NULL, false, false);
+
+ addRTEtoQuery(with_check_pstate, rte, false, true, true);
+
+ with_check_qual = transformWhereClause(with_check_pstate,
+ copyObject(stmt->with_check),
+ EXPR_KIND_WHERE,
+ "ROW SECURITY");
+
+ with_check_parse_rtable = with_check_pstate->p_rtable;
+ free_parsestate(with_check_pstate);
+ }
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(replaces, 0, sizeof(replaces));
+ memset(isnull, 0, sizeof(isnull));
+
+ /* Find policy to update. */
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* Set key - row security relation id. */
+ ScanKeyInit(&skey[0],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Set key - row security policy name. */
+ ScanKeyInit(&skey[1],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->policy_name));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel,
+ RowSecurityRelidPolnameIndexId, true, NULL, 2,
+ skey);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /* Check that the policy is found, raise an error if not. */
+ if (!HeapTupleIsValid(rsec_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("policy '%s' for does not exist on table %s",
+ stmt->policy_name,
+ RelationGetRelationName(target_table))));
+
+ /* Get policy command */
+ cmd_datum = heap_getattr(rsec_tuple, Anum_pg_rowsecurity_rseccmd,
+ RelationGetDescr(pg_rowsecurity_rel),
+ &rseccmd_isnull);
+ if (rseccmd_isnull)
+ rseccmd = 0;
+ else
+ rseccmd = DatumGetChar(cmd_datum);
+
+ /*
+ * If the command is SELECT or DELETE then WITH CHECK should be NULL.
+ */
+ if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
+ && stmt->with_check != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("only USING expression allowed for SELECT, DELETE")));
+
+ /*
+ * If the command is INSERT then WITH CHECK should be the only
+ * expression provided.
+ */
+ if ((rseccmd == ACL_INSERT_CHR)
+ && stmt->qual != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("only WITH CHECK expression allowed for INSERT")));
+
+ rowsec_id = HeapTupleGetOid(rsec_tuple);
+
+ if (role_ids != NULL)
+ {
+ replaces[Anum_pg_rowsecurity_rsecroles - 1] = true;
+ values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
+ }
+
+ if (qual != NULL)
+ {
+ replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+ values[Anum_pg_rowsecurity_rsecqual - 1]
+ = CStringGetTextDatum(nodeToString(qual));
+ }
+
+ if (with_check_qual != NULL)
+ {
+ replaces[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
+ values[Anum_pg_rowsecurity_rsecwithcheck - 1]
+ = CStringGetTextDatum(nodeToString(with_check_qual));
+ }
+
+ new_tuple = heap_modify_tuple(rsec_tuple,
+ RelationGetDescr(pg_rowsecurity_rel),
+ values, isnull, replaces);
+ simple_heap_update(pg_rowsecurity_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Catalog Indexes */
+ CatalogUpdateIndexes(pg_rowsecurity_rel, new_tuple);
+
+ /* Update Dependencies. */
+ deleteDependencyRecordsFor(RowSecurityRelationId, rowsec_id, false);
+
+ /* Record Dependencies */
+ target.classId = RelationRelationId;
+ target.objectId = table_id;
+ target.objectSubId = 0;
+
+ myself.classId = RowSecurityRelationId;
+ myself.objectId = rowsec_id;
+ myself.objectSubId = 0;
+
+ recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+ recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL);
+
+ recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable,
+ DEPENDENCY_NORMAL);
+
+ heap_freetuple(new_tuple);
+
+ /* Invalidate Relation Cache */
+ CacheInvalidateRelcache(target_table);
+
+ /* Clean up. */
+ systable_endscan(sscan);
+ relation_close(target_table, NoLock);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+ return rowsec_id;
+}
+
+/*
+ * rename_policy -
+ * change the name of a policy on a relation
+ */
+Oid
+rename_policy(RenameStmt *stmt)
+{
+ Relation pg_rowsecurity_rel;
+ Relation target_table;
+ Oid table_id;
+ Oid opoloid;
+ ScanKeyData skey[2];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+
+ /* Get id of table. Also handles permissions checks. */
+ table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForPolicy,
+ (void *) stmt);
+
+ target_table = relation_open(table_id, NoLock);
+
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* First pass- check for conflict */
+
+ /* Add key - row security relation id. */
+ ScanKeyInit(&skey[0],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Add key - row security policy name. */
+ ScanKeyInit(&skey[1],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->newname));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel,
+ RowSecurityRelidPolnameIndexId, true, NULL, 2,
+ skey);
+
+ if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("row-policy \"%s\" for table \"%s\" already exists",
+ stmt->newname, RelationGetRelationName(target_table))));
+
+ systable_endscan(sscan);
+
+ /* Second pass -- find existing policy and update */
+ /* Add key - row security relation id. */
+ ScanKeyInit(&skey[0],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Add key - row security policy name. */
+ ScanKeyInit(&skey[1],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->subname));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel,
+ RowSecurityRelidPolnameIndexId, true, NULL, 2,
+ skey);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /* Complain if we did not find the policy */
+ if (!HeapTupleIsValid(rsec_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("row-policy \"%s\" for table \"%s\" does not exist",
+ stmt->subname, RelationGetRelationName(target_table))));
+
+ opoloid = HeapTupleGetOid(rsec_tuple);
+
+ rsec_tuple = heap_copytuple(rsec_tuple);
+
+ namestrcpy(&((Form_pg_rowsecurity) GETSTRUCT(rsec_tuple))->rsecpolname,
+ stmt->newname);
+
+ simple_heap_update(pg_rowsecurity_rel, &rsec_tuple->t_self, rsec_tuple);
+
+ /* keep system catalog indexes current */
+ CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
+
+ InvokeObjectPostAlterHook(RowSecurityRelationId,
+ HeapTupleGetOid(rsec_tuple), 0);
+
+ /*
+ * Invalidate relation's relcache entry so that other backends (and
+ * this one too!) are sent SI message to make them rebuild relcache
+ * entries. (Ideally this should happen automatically...)
+ */
+ CacheInvalidateRelcache(target_table);
+
+ /* Clean up. */
+ systable_endscan(sscan);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+ relation_close(target_table, NoLock);
+
+ return opoloid;
+}
+
+/*
+ * get_relation_policy_oid - Look up a policy by name to find its OID
+ *
+ * If missing_ok is false, throw an error if policy not found. If
+ * true, just return InvalidOid.
+ */
+Oid
+get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok)
+{
+ Relation pg_rowsecurity_rel;
+ ScanKeyData skey[2];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+ Oid policy_oid;
+
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, AccessShareLock);
+
+ /* Add key - row security relation id. */
+ ScanKeyInit(&skey[0],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+
+ /* Add key - row security policy name. */
+ ScanKeyInit(&skey[1],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(policy_name));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel,
+ RowSecurityRelidPolnameIndexId, true, NULL, 2,
+ skey);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ if (!HeapTupleIsValid(rsec_tuple))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("policy \"%s\" for table \"%s\" does not exist",
+ policy_name, get_rel_name(relid))));
+
+ policy_oid = InvalidOid;
+ }
+ else
+ policy_oid = HeapTupleGetOid(rsec_tuple);
+
+ /* Clean up. */
+ systable_endscan(sscan);
+ heap_close(pg_rowsecurity_rel, AccessShareLock);
+
+ return policy_oid;
+}
#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"
#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"
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);
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_EnableRowSecurity:
+ case AT_DisableRowSecurity:
cmd_lockmode = AccessExclusiveLock;
break;
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 */
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;
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 (...)
*/
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 */
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)
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);
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dbypassRLS)
+ bypassrls = intVal(dbypassRLS->arg) != 0;
/* Check some permissions first */
if (issuper)
(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())
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);
/*
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;
DefElem *dconnlimit = NULL;
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
+ DefElem *dbypassRLS = NULL;
Oid roleid;
/* Extract options from the statement node tree */
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);
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.
(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 &&
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);
*
* 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)
/*
* 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,
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);
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
* ****************************************************************
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;
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);
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)
{
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;
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);
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);
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)
result->relationOids = glob->relationOids;
result->invalItems = glob->invalItems;
result->nParamExec = glob->nParamExec;
+ result->has_rls = glob->has_rls;
return result;
}
* 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
void
extract_query_dependencies(Node *query,
List **relationOids,
- List **invalItems)
+ List **invalItems,
+ bool *hasRowSecurity)
{
PlannerGlobal glob;
PlannerInfo root;
glob.type = T_PlannerGlobal;
glob.relationOids = NIL;
glob.invalItems = NIL;
+ glob.has_rls = false;
MemSet(&root, 0, sizeof(root));
root.type = T_PlannerInfo;
*relationOids = glob.relationOids;
*invalItems = glob.invalItems;
+ *hasRowSecurity = glob.has_rls;
}
static bool
Query *query = (Query *) node;
ListCell *lc;
+ /* Collect row-security information */
+ context->glob->has_rls = query->hasRowSecurity;
+
if (query->commandType == CMD_UTILITY)
{
/*
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
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
%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
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
| AlterGroupStmt
| AlterObjectSchemaStmt
| AlterOwnerStmt
+ | AlterPolicyStmt
| AlterSeqStmt
| AlterSystemStmt
| AlterTableStmt
| CreateOpClassStmt
| CreateOpFamilyStmt
| AlterOpFamilyStmt
+ | CreatePolicyStmt
| CreatePLangStmt
| CreateSchemaStmt
| CreateSeqStmt
| DropOpClassStmt
| DropOpFamilyStmt
| DropOwnedStmt
+ | DropPolicyStmt
| DropPLangStmt
| DropRuleStmt
| DropStmt
$$ = 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)
{
/*
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);
}
;
+/*****************************************************************************
+ *
+ * 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 :
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);
| PASSING
| PASSWORD
| PLANS
+ | POLICY
| PRECEDING
| PREPARE
| PREPARED
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
#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"
* 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++)
- {
- rule = rules->rules[i];
- if (rule->event != CMD_SELECT)
- continue;
+ locks = NIL;
+ for (i = 0; i < rules->numLocks; i++)
+ {
+ rule = rules->rules[i];
+ if (rule->event != CMD_SELECT)
+ continue;
- locks = lappend(locks, rule);
- }
+ locks = lappend(locks, rule);
+ }
+
+ /*
+ * If we found any, apply them --- but first check for recursion!
+ */
+ if (locks != NIL)
+ {
+ ListCell *l;
+
+ if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("infinite recursion detected in rules for relation \"%s\"",
+ RelationGetRelationName(rel))));
+ activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+
+ foreach(l, locks)
+ {
+ rule = lfirst(l);
+
+ parsetree = ApplyRetrieveRule(parsetree,
+ rule,
+ rt_index,
+ rel,
+ activeRIRs,
+ forUpdatePushedDown);
+ }
+ activeRIRs = list_delete_first(acti