Add support for security invoker views.

A security invoker view checks permissions for accessing its
underlying base relations using the privileges of the user of the
view, rather than the privileges of the view owner. Additionally, if
any of the base relations are tables with RLS enabled, the policies of
the user of the view are applied, rather than those of the view owner.

This allows views to be defined without giving away additional
privileges on the underlying base relations, and matches a similar
feature available in other database systems.

It also allows views to operate more naturally with RLS, without
affecting the assignments of policies to users.

Christoph Heiss, with some additional hacking by me. Reviewed by
Laurenz Albe and Wolfgang Walther.

Discussion: https://postgr.es/m/b66dd6d6-ad3e-c6f2-8b90-47be773da240%40cybertec.at
This commit is contained in:
Dean Rasheed 2022-03-22 10:28:10 +00:00
parent f5576a21b0
commit 7faa5fc84b
20 changed files with 1240 additions and 76 deletions

View File

@ -156,7 +156,17 @@ ALTER VIEW [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET
<listitem>
<para>
Changes the security-barrier property of the view. The value must
be Boolean value, such as <literal>true</literal>
be a Boolean value, such as <literal>true</literal>
or <literal>false</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>security_invoker</literal> (<type>boolean</type>)</term>
<listitem>
<para>
Changes the security-invoker property of the view. The value must
be a Boolean value, such as <literal>true</literal>
or <literal>false</literal>.
</para>
</listitem>

View File

@ -608,7 +608,9 @@ AND
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.
owner's rights and any policies which apply to the view owner, except if
the view is defined using the <literal>security_invoker</literal> option
(see <link linkend="sql-createview"><command>CREATE VIEW</command></link>).
</para>
<para>

View File

@ -137,8 +137,6 @@ CREATE VIEW [ <replaceable>schema</replaceable> . ] <replaceable>view_name</repl
This parameter may be either <literal>local</literal> or
<literal>cascaded</literal>, and is equivalent to specifying
<literal>WITH [ CASCADED | LOCAL ] CHECK OPTION</literal> (see below).
This option can be changed on existing views using <link
linkend="sql-alterview"><command>ALTER VIEW</command></link>.
</para>
</listitem>
</varlistentry>
@ -152,7 +150,22 @@ CREATE VIEW [ <replaceable>schema</replaceable> . ] <replaceable>view_name</repl
</para>
</listitem>
</varlistentry>
</variablelist></para>
<varlistentry>
<term><literal>security_invoker</literal> (<type>boolean</type>)</term>
<listitem>
<para>
This option causes the underlying base relations to be checked
against the privileges of the user of the view rather than the view
owner. See the notes below for full details.
</para>
</listitem>
</varlistentry>
</variablelist>
All of the above options can be changed on existing views using <link
linkend="sql-alterview"><command>ALTER VIEW</command></link>.
</para>
</listitem>
</varlistentry>
@ -265,18 +278,74 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
</para>
<para>
Access to tables referenced in the view is determined by permissions of
the view owner. In some cases, this can be used to provide secure but
restricted access to the underlying tables. However, not all views are
secure against tampering; see <xref linkend="rules-privileges"/> for
details. Functions called in the view are treated the same as if they had
been called directly from the query using the view. Therefore the user of
a view must have permissions to call all functions used by the view.
By default, access to the underlying base relations referenced in the view
is determined by the permissions of the view owner. In some cases, this
can be used to provide secure but restricted access to the underlying
tables. However, not all views are secure against tampering; see <xref
linkend="rules-privileges"/> for details.
</para>
<para>
When <command>CREATE OR REPLACE VIEW</command> is used on an
existing view, only the view's defining SELECT rule is changed.
If the view has the <literal>security_invoker</literal> property set to
<literal>true</literal>, access to the underlying base relations is
determined by the permissions of the user executing the query, rather than
the view owner. Thus, the user of a security invoker view must have the
relevant permissions on the view and its underlying base relations.
</para>
<para>
If any of the underlying base relations is a security invoker view, it
will be treated as if it had been accessed directly from the original
query. Thus, a security invoker view will always check its underlying
base relations using the permissions of the current user, even if it is
accessed from a view without the <literal>security_invoker</literal>
property.
</para>
<para>
If any of the underlying base relations has
<link linkend="ddl-rowsecurity">row-level security</link> enabled, then
by default, the row-level security policies of the view owner are applied,
and access to any additional relations referred to by those policies is
determined by the permissions of the view owner. However, if the view has
<literal>security_invoker</literal> set to <literal>true</literal>, then
the policies and permissions of the invoking user are used instead, as if
the base relations had been referenced directly from the query using the
view.
</para>
<para>
Functions called in the view are treated the same as if they had been
called directly from the query using the view. Therefore, the user of
a view must have permissions to call all functions used by the view.
Functions in the view are executed with the privileges of the user
executing the query or the function owner, depending on whether the
functions are defined as <literal>SECURITY INVOKER</literal> or
<literal>SECURITY DEFINER</literal>. Thus, for example, calling
<literal>CURRENT_USER</literal> directly in a view will always return the
invoking user, not the view owner. This is not affected by the view's
<literal>security_invoker</literal> setting, and so a view with
<literal>security_invoker</literal> set to <literal>false</literal> is
<emphasis>not</emphasis> equivalent to a
<literal>SECURITY DEFINER</literal> function and those concepts should not
be confused.
</para>
<para>
The user creating or replacing a view must have <literal>USAGE</literal>
privileges on any schemas referred to in the view query, in order to look
up the referenced objects in those schemas. Note, however, that this
lookup only happens when the view is created or replaced. Therefore, the
user of the view only requires the <literal>USAGE</literal> privilege on
the schema containing the view, not on the schemas referred to in the view
query, even for a security invoker view.
</para>
<para>
When <command>CREATE OR REPLACE VIEW</command> is used on an existing
view, only the view's defining SELECT rule, plus any
<literal>WITH ( ... )</literal> parameters and its
<literal>CHECK OPTION</literal> are changed.
Other view properties, including ownership, permissions, and non-SELECT
rules, remain unchanged. You must own the view
to replace it (this includes being a member of the owning role).
@ -387,10 +456,13 @@ CREATE VIEW vista AS SELECT text 'Hello World' AS hello;
<para>
Note that the user performing the insert, update or delete on the view
must have the corresponding insert, update or delete privilege on the
view. In addition the view's owner must have the relevant privileges on
the underlying base relations, but the user performing the update does
not need any permissions on the underlying base relations (see
<xref linkend="rules-privileges"/>).
view. In addition, by default, the view's owner must have the relevant
privileges on the underlying base relations, whereas the user performing
the update does not need any permissions on the underlying base relations
(see <xref linkend="rules-privileges"/>). However, if the view has
<literal>security_invoker</literal> set to <literal>true</literal>, the
user performing the update, rather than the view owner, must have the
relevant privileges on the underlying base relations.
</para>
</refsect2>
</refsect1>
@ -486,7 +558,8 @@ UNION ALL
<command>CREATE OR REPLACE VIEW</command> is a
<productname>PostgreSQL</productname> language extension.
So is the concept of a temporary view.
The <literal>WITH ( ... )</literal> clause is an extension as well.
The <literal>WITH ( ... )</literal> clause is an extension as well, as are
security barrier views and security invoker views.
</para>
</refsect1>

View File

@ -174,10 +174,15 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
</para>
<para>
The user performing the lock on the view must have the corresponding privilege
on the view. In addition the view's owner must have the relevant privileges on
the underlying base relations, but the user performing the lock does
not need any permissions on the underlying base relations.
The user performing the lock on the view must have the corresponding
privilege on the view. In addition, by default, the view's owner must
have the relevant privileges on the underlying base relations, whereas the
user performing the lock does not need any permissions on the underlying
base relations. However, if the view has
<literal>security_invoker</literal> set to <literal>true</literal>
(see <link linkend="sql-createview"><command>CREATE VIEW</command></link>),
the user performing the lock, rather than the view owner, must have the
relevant privileges on the underlying base relations.
</para>
<para>

View File

@ -2007,11 +2007,14 @@ SELECT * FROM shoelace;
a relation (table or view) is automatically the owner of the
rewrite rules that are defined for it.
The <productname>PostgreSQL</productname> rule system changes the
behavior of the default access control system. Relations that
are used due to rules get checked against the
behavior of the default access control system. With the exception of
<literal>SELECT</literal> rules associated with security invoker views
(see <link linkend="sql-createview"><command>CREATE VIEW</command></link>),
all relations that are used due to rules get checked against the
privileges of the rule owner, not the user invoking the rule.
This means that users only need the required privileges
for the tables/views that are explicitly named in their queries.
This means that, except for security invoker views, users only need the
required privileges for the tables/views that are explicitly named in
their queries.
</para>
<para>

View File

@ -140,6 +140,15 @@ static relopt_bool boolRelOpts[] =
},
false
},
{
{
"security_invoker",
"Privileges on underlying relations are checked as the invoking user, not the view owner",
RELOPT_KIND_VIEW,
AccessExclusiveLock
},
false
},
{
{
"vacuum_truncate",
@ -1996,6 +2005,8 @@ view_reloptions(Datum reloptions, bool validate)
static const relopt_parse_elt tab[] = {
{"security_barrier", RELOPT_TYPE_BOOL,
offsetof(ViewOptions, security_barrier)},
{"security_invoker", RELOPT_TYPE_BOOL,
offsetof(ViewOptions, security_invoker)},
{"check_option", RELOPT_TYPE_ENUM,
offsetof(ViewOptions, check_option)}
};

View File

@ -169,7 +169,7 @@ typedef struct
{
LOCKMODE lockmode; /* lock mode to use */
bool nowait; /* no wait mode */
Oid viewowner; /* view owner for checking the privilege */
Oid check_as_user; /* user for checking the privilege */
Oid viewoid; /* OID of the view to be locked */
List *ancestor_views; /* OIDs of ancestor views */
} LockViewRecurse_context;
@ -215,8 +215,12 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
if (list_member_oid(context->ancestor_views, relid))
continue;
/* Check permissions with the view owner's privilege. */
aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
/*
* Check permissions as the specified user. This will either be
* the view owner or the current user.
*/
aclresult = LockTableAclCheck(relid, context->lockmode,
context->check_as_user);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
@ -259,9 +263,16 @@ LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
view = table_open(reloid, NoLock);
viewquery = get_view_query(view);
/*
* If the view has the security_invoker property set, check permissions as
* the current user. Otherwise, check permissions as the view owner.
*/
context.lockmode = lockmode;
context.nowait = nowait;
context.viewowner = view->rd_rel->relowner;
if (RelationHasSecurityInvoker(view))
context.check_as_user = GetUserId();
else
context.check_as_user = view->rd_rel->relowner;
context.viewoid = reloid;
context.ancestor_views = lappend_oid(ancestor_views, reloid);

View File

@ -3242,18 +3242,24 @@ rewriteTargetView(Query *parsetree, Relation view)
0);
/*
* Mark the new target RTE for the permissions checks that we want to
* enforce against the view owner, as distinct from the query caller. At
* the relation level, require the same INSERT/UPDATE/DELETE permissions
* that the query caller needs against the view. We drop the ACL_SELECT
* bit that is presumably in new_rte->requiredPerms initially.
* If the view has "security_invoker" set, mark the new target RTE for the
* permissions checks that we want to enforce against the query caller.
* Otherwise we want to enforce them against the view owner.
*
* At the relation level, require the same INSERT/UPDATE/DELETE
* permissions that the query caller needs against the view. We drop the
* ACL_SELECT bit that is presumably in new_rte->requiredPerms initially.
*
* Note: the original view RTE remains in the query's rangetable list.
* Although it will be unused in the query plan, we need it there so that
* the executor still performs appropriate permissions checks for the
* query caller's use of the view.
*/
new_rte->checkAsUser = view->rd_rel->relowner;
if (RelationHasSecurityInvoker(view))
new_rte->checkAsUser = InvalidOid;
else
new_rte->checkAsUser = view->rd_rel->relowner;
new_rte->requiredPerms = view_rte->requiredPerms;
/*

View File

@ -722,6 +722,8 @@ RelationBuildTupleDesc(Relation relation)
* entry, because that keeps the update logic in RelationClearRelation()
* manageable. The other subsidiary data structures are simple enough
* to be easy to free explicitly, anyway.
*
* Note: The relation's reloptions must have been extracted first.
*/
static void
RelationBuildRuleLock(Relation relation)
@ -787,6 +789,7 @@ RelationBuildRuleLock(Relation relation)
Datum rule_datum;
char *rule_str;
RewriteRule *rule;
Oid check_as_user;
rule = (RewriteRule *) MemoryContextAlloc(rulescxt,
sizeof(RewriteRule));
@ -826,10 +829,23 @@ RelationBuildRuleLock(Relation relation)
pfree(rule_str);
/*
* We want the rule's table references to be checked as though by the
* table owner, not the user referencing the rule. Therefore, scan
* through the rule's actions and set the checkAsUser field on all
* rtable entries. We have to look at the qual as well, in case it
* If this is a SELECT rule defining a view, and the view has
* "security_invoker" set, we must perform all permissions checks on
* relations referred to by the rule as the invoking user.
*
* In all other cases (including non-SELECT rules on security invoker
* views), perform the permissions checks as the relation owner.
*/
if (rule->event == CMD_SELECT &&
relation->rd_rel->relkind == RELKIND_VIEW &&
RelationHasSecurityInvoker(relation))
check_as_user = InvalidOid;
else
check_as_user = relation->rd_rel->relowner;
/*
* Scan through the rule's actions and set the checkAsUser field on
* all rtable entries. We have to look at the qual as well, in case it
* contains sublinks.
*
* The reason for doing this when the rule is loaded, rather than when
@ -838,8 +854,8 @@ RelationBuildRuleLock(Relation relation)
* the rule tree during load is relatively cheap (compared to
* constructing it in the first place), so we do it here.
*/
setRuleCheckAsUser((Node *) rule->actions, relation->rd_rel->relowner);
setRuleCheckAsUser(rule->qual, relation->rd_rel->relowner);
setRuleCheckAsUser((Node *) rule->actions, check_as_user);
setRuleCheckAsUser(rule->qual, check_as_user);
if (numlocks >= maxlocks)
{
@ -1164,27 +1180,6 @@ retry:
*/
RelationBuildTupleDesc(relation);
/*
* Fetch rules and triggers that affect this relation
*/
if (relation->rd_rel->relhasrules)
RelationBuildRuleLock(relation);
else
{
relation->rd_rules = NULL;
relation->rd_rulescxt = NULL;
}
if (relation->rd_rel->relhastriggers)
RelationBuildTriggers(relation);
else
relation->trigdesc = NULL;
if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rd_rsdesc = NULL;
/* foreign key data is not loaded till asked for */
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
@ -1216,6 +1211,30 @@ retry:
/* extract reloptions if any */
RelationParseRelOptions(relation, pg_class_tuple);
/*
* Fetch rules and triggers that affect this relation.
*
* Note that RelationBuildRuleLock() relies on this being done after
* extracting the relation's reloptions.
*/
if (relation->rd_rel->relhasrules)
RelationBuildRuleLock(relation);
else
{
relation->rd_rules = NULL;
relation->rd_rulescxt = NULL;
}
if (relation->rd_rel->relhastriggers)
RelationBuildTriggers(relation);
else
relation->trigdesc = NULL;
if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rd_rsdesc = NULL;
/*
* initialize the relation lock manager information
*/

View File

@ -398,6 +398,7 @@ typedef struct ViewOptions
{
int32 vl_len_; /* varlena header (do not touch directly!) */
bool security_barrier;
bool security_invoker;
ViewOptCheckOption check_option;
} ViewOptions;
@ -411,6 +412,16 @@ typedef struct ViewOptions
(relation)->rd_options ? \
((ViewOptions *) (relation)->rd_options)->security_barrier : false)
/*
* RelationHasSecurityInvoker
* Returns true if the relation has the security_invoker property set.
* Note multiple eval of argument!
*/
#define RelationHasSecurityInvoker(relation) \
(AssertMacro(relation->rd_rel->relkind == RELKIND_VIEW), \
(relation)->rd_options ? \
((ViewOptions *) (relation)->rd_options)->security_invoker : false)
/*
* RelationHasCheckOption
* Returns true if the relation is a view defined with either the local

View File

@ -296,17 +296,31 @@ ERROR: invalid value for boolean option "security_barrier": 100
CREATE VIEW mysecview6 WITH (invalid_option) -- Error
AS SELECT * FROM tbl1 WHERE a < 100;
ERROR: unrecognized parameter "invalid_option"
CREATE VIEW mysecview7 WITH (security_invoker=true)
AS SELECT * FROM tbl1 WHERE a = 100;
CREATE VIEW mysecview8 WITH (security_invoker=false, security_barrier=true)
AS SELECT * FROM tbl1 WHERE a > 100;
CREATE VIEW mysecview9 WITH (security_invoker)
AS SELECT * FROM tbl1 WHERE a < 100;
CREATE VIEW mysecview10 WITH (security_invoker=100) -- Error
AS SELECT * FROM tbl1 WHERE a <> 100;
ERROR: invalid value for boolean option "security_invoker": 100
SELECT relname, relkind, reloptions FROM pg_class
WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
'mysecview3'::regclass, 'mysecview4'::regclass)
'mysecview3'::regclass, 'mysecview4'::regclass,
'mysecview7'::regclass, 'mysecview8'::regclass,
'mysecview9'::regclass)
ORDER BY relname;
relname | relkind | reloptions
------------+---------+--------------------------
relname | relkind | reloptions
------------+---------+------------------------------------------------
mysecview1 | v |
mysecview2 | v | {security_barrier=true}
mysecview3 | v | {security_barrier=false}
mysecview4 | v | {security_barrier=true}
(4 rows)
mysecview7 | v | {security_invoker=true}
mysecview8 | v | {security_invoker=false,security_barrier=true}
mysecview9 | v | {security_invoker=true}
(7 rows)
CREATE OR REPLACE VIEW mysecview1
AS SELECT * FROM tbl1 WHERE a = 256;
@ -316,17 +330,28 @@ CREATE OR REPLACE VIEW mysecview3 WITH (security_barrier=true)
AS SELECT * FROM tbl1 WHERE a < 256;
CREATE OR REPLACE VIEW mysecview4 WITH (security_barrier=false)
AS SELECT * FROM tbl1 WHERE a <> 256;
CREATE OR REPLACE VIEW mysecview7
AS SELECT * FROM tbl1 WHERE a > 256;
CREATE OR REPLACE VIEW mysecview8 WITH (security_invoker=true)
AS SELECT * FROM tbl1 WHERE a < 256;
CREATE OR REPLACE VIEW mysecview9 WITH (security_invoker=false, security_barrier=true)
AS SELECT * FROM tbl1 WHERE a <> 256;
SELECT relname, relkind, reloptions FROM pg_class
WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
'mysecview3'::regclass, 'mysecview4'::regclass)
'mysecview3'::regclass, 'mysecview4'::regclass,
'mysecview7'::regclass, 'mysecview8'::regclass,
'mysecview9'::regclass)
ORDER BY relname;
relname | relkind | reloptions
------------+---------+--------------------------
relname | relkind | reloptions
------------+---------+------------------------------------------------
mysecview1 | v |
mysecview2 | v |
mysecview3 | v | {security_barrier=true}
mysecview4 | v | {security_barrier=false}
(4 rows)
mysecview7 | v |
mysecview8 | v | {security_invoker=true}
mysecview9 | v | {security_invoker=false,security_barrier=true}
(7 rows)
-- Check that unknown literals are converted to "text" in CREATE VIEW,
-- so that we don't end up with unknown-type columns.
@ -2039,7 +2064,7 @@ drop cascades to view aliased_view_2
drop cascades to view aliased_view_3
drop cascades to view aliased_view_4
DROP SCHEMA testviewschm2 CASCADE;
NOTICE: drop cascades to 74 other objects
NOTICE: drop cascades to 77 other objects
DETAIL: drop cascades to table t1
drop cascades to view temporal1
drop cascades to view temporal2
@ -2060,6 +2085,9 @@ drop cascades to view mysecview1
drop cascades to view mysecview2
drop cascades to view mysecview3
drop cascades to view mysecview4
drop cascades to view mysecview7
drop cascades to view mysecview8
drop cascades to view mysecview9
drop cascades to view unspecified_types
drop cascades to table tt1
drop cascades to table tx1

View File

@ -156,9 +156,75 @@ BEGIN;
LOCK TABLE ONLY lock_tbl1;
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_tbl1 FROM regress_rol_lock1;
-- Tables referred to by views are locked without explicit permission to do so
-- as long as we have permission to lock the view itself.
SET ROLE regress_rol_lock1;
-- fail without permissions on the view
BEGIN;
LOCK TABLE lock_view1;
ERROR: permission denied for view lock_view1
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_view1 TO regress_rol_lock1;
SET ROLE regress_rol_lock1;
BEGIN;
LOCK TABLE lock_view1 IN ACCESS EXCLUSIVE MODE;
-- lock_view1 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked.
select relname from pg_locks l, pg_class c
where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock'
order by relname;
relname
------------
lock_tbl1
lock_tbl2
lock_tbl3
lock_view1
(4 rows)
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_view1 FROM regress_rol_lock1;
-- Tables referred to by security invoker views require explicit permission to
-- be locked.
CREATE VIEW lock_view8 WITH (security_invoker) AS SELECT * FROM lock_tbl1;
SET ROLE regress_rol_lock1;
-- fail without permissions on the view
BEGIN;
LOCK TABLE lock_view8;
ERROR: permission denied for view lock_view8
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_view8 TO regress_rol_lock1;
SET ROLE regress_rol_lock1;
-- fail without permissions on the table referenced by the view
BEGIN;
LOCK TABLE lock_view8;
ERROR: permission denied for table lock_tbl1
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1;
BEGIN;
LOCK TABLE lock_view8 IN ACCESS EXCLUSIVE MODE;
-- lock_view8 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked.
select relname from pg_locks l, pg_class c
where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock'
order by relname;
relname
------------
lock_tbl1
lock_tbl2
lock_tbl3
lock_view8
(4 rows)
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_view8 FROM regress_rol_lock1;
--
-- Clean up
--
DROP VIEW lock_view8;
DROP VIEW lock_view7;
DROP VIEW lock_view6;
DROP VIEW lock_view5;

View File

@ -2431,6 +2431,7 @@ ERROR: permission denied for view rls_view
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
GRANT SELECT ON rls_view TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
NOTICE: f_leak => dad
@ -2447,6 +2448,259 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
Filter: (((a % 2) = 0) AND f_leak(b))
(2 rows)
-- Policy requiring access to another table.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE TABLE z1_blacklist (a int);
INSERT INTO z1_blacklist VALUES (3), (4);
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
-- Query as role that is not owner of table but is owner of view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
a | b
---+-----
2 | bbb
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
----------------------------------------------------------------------
Seq Scan on z1
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
SubPlan 1
-> Seq Scan on z1_blacklist
(4 rows)
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
a | b
---+-----
2 | bbb
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
----------------------------------------------------------------------
Seq Scan on z1
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
SubPlan 1
-> Seq Scan on z1_blacklist
(4 rows)
SET SESSION AUTHORIZATION regress_rls_alice;
REVOKE SELECT ON z1_blacklist FROM regress_rls_bob;
DROP POLICY p3 ON z1;
SET SESSION AUTHORIZATION regress_rls_bob;
DROP VIEW rls_view;
--
-- Security invoker views should follow policy for current user.
--
-- View and table owner are the same.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE VIEW rls_view WITH (security_invoker) AS
SELECT * FROM z1 WHERE f_leak(b);
GRANT SELECT ON rls_view TO regress_rls_bob;
GRANT SELECT ON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records.
SELECT * FROM rls_view;
NOTICE: f_leak => aba
NOTICE: f_leak => bbb
NOTICE: f_leak => ccc
NOTICE: f_leak => dad
a | b
---+-----
1 | aba
2 | bbb
3 | ccc
4 | dad
(4 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
---------------------
Seq Scan on z1
Filter: f_leak(b)
(2 rows)
-- Queries as other users.
-- Should return records based on current user's policies.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
NOTICE: f_leak => dad
a | b
---+-----
2 | bbb
4 | dad
(2 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
-----------------------------------------
Seq Scan on z1
Filter: (((a % 2) = 0) AND f_leak(b))
(2 rows)
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
NOTICE: f_leak => aba
NOTICE: f_leak => ccc
a | b
---+-----
1 | aba
3 | ccc
(2 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
-----------------------------------------
Seq Scan on z1
Filter: (((a % 2) = 1) AND f_leak(b))
(2 rows)
-- View and table owners are different.
SET SESSION AUTHORIZATION regress_rls_alice;
DROP VIEW rls_view;
SET SESSION AUTHORIZATION regress_rls_bob;
CREATE VIEW rls_view WITH (security_invoker) AS
SELECT * FROM z1 WHERE f_leak(b);
GRANT SELECT ON rls_view TO regress_rls_alice;
GRANT SELECT ON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records.
SET SESSION AUTHORIZATION regress_rls_alice;
SELECT * FROM rls_view;
NOTICE: f_leak => aba
NOTICE: f_leak => bbb
NOTICE: f_leak => ccc
NOTICE: f_leak => dad
a | b
---+-----
1 | aba
2 | bbb
3 | ccc
4 | dad
(4 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
---------------------
Seq Scan on z1
Filter: f_leak(b)
(2 rows)
-- Queries as other users.
-- Should return records based on current user's policies.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
NOTICE: f_leak => dad
a | b
---+-----
2 | bbb
4 | dad
(2 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
-----------------------------------------
Seq Scan on z1
Filter: (((a % 2) = 0) AND f_leak(b))
(2 rows)
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
NOTICE: f_leak => aba
NOTICE: f_leak => ccc
a | b
---+-----
1 | aba
3 | ccc
(2 rows)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
-----------------------------------------
Seq Scan on z1
Filter: (((a % 2) = 1) AND f_leak(b))
(2 rows)
-- Policy requiring access to another table.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
-- Query as role that is not owner of table but is owner of view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
NOTICE: f_leak => bbb
a | b
---+-----
2 | bbb
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
----------------------------------------------------------------------
Seq Scan on z1
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 0) AND f_leak(b))
SubPlan 1
-> Seq Scan on z1_blacklist
(4 rows)
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
ERROR: permission denied for table z1_blacklist
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
NOTICE: f_leak => aba
a | b
---+-----
1 | aba
(1 row)
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
QUERY PLAN
----------------------------------------------------------------------
Seq Scan on z1
Filter: ((NOT (hashed SubPlan 1)) AND ((a % 2) = 1) AND f_leak(b))
SubPlan 1
-> Seq Scan on z1_blacklist
(4 rows)
SET SESSION AUTHORIZATION regress_rls_bob;
DROP VIEW rls_view;
--
@ -3987,7 +4241,7 @@ RESET SESSION AUTHORIZATION;
--
RESET SESSION AUTHORIZATION;
DROP SCHEMA regress_rls_schema CASCADE;
NOTICE: drop cascades to 29 other objects
NOTICE: drop cascades to 30 other objects
DETAIL: drop cascades to function f_leak(text)
drop cascades to table uaccount
drop cascades to table category
@ -4005,6 +4259,7 @@ drop cascades to table b1
drop cascades to view bv1
drop cascades to table z1
drop cascades to table z2
drop cascades to table z1_blacklist
drop cascades to table x1
drop cascades to table y1
drop cascades to table y2

View File

@ -3496,3 +3496,33 @@ SELECT * FROM ruletest2;
DROP TABLE ruletest1;
DROP TABLE ruletest2;
--
-- Test non-SELECT rule on security invoker view.
-- Should use view owner's permissions.
--
CREATE USER regress_rule_user1;
CREATE TABLE ruletest_t1 (x int);
CREATE TABLE ruletest_t2 (x int);
CREATE VIEW ruletest_v1 WITH (security_invoker=true) AS
SELECT * FROM ruletest_t1;
GRANT INSERT ON ruletest_v1 TO regress_rule_user1;
CREATE RULE rule1 AS ON INSERT TO ruletest_v1
DO INSTEAD INSERT INTO ruletest_t2 VALUES (NEW.*);
SET SESSION AUTHORIZATION regress_rule_user1;
INSERT INTO ruletest_v1 VALUES (1);
RESET SESSION AUTHORIZATION;
SELECT * FROM ruletest_t1;
x
---
(0 rows)
SELECT * FROM ruletest_t2;
x
---
1
(1 row)
DROP VIEW ruletest_v1;
DROP TABLE ruletest_t2;
DROP TABLE ruletest_t1;
DROP USER regress_rule_user1;

View File

@ -979,6 +979,7 @@ drop cascades to function rw_view1_aa(rw_view1)
-- permissions checks
CREATE USER regress_view_user1;
CREATE USER regress_view_user2;
CREATE USER regress_view_user3;
SET SESSION AUTHORIZATION regress_view_user1;
CREATE TABLE base_tbl(a int, b text, c float);
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
@ -1205,8 +1206,244 @@ DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to view rw_view1
drop cascades to view rw_view2
-- security invoker view permissions
SET SESSION AUTHORIZATION regress_view_user1;
CREATE TABLE base_tbl(a int, b text, c float);
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
ALTER VIEW rw_view1 SET (security_invoker = true);
INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
GRANT SELECT ON rw_view1 TO regress_view_user2;
GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM base_tbl; -- not allowed
ERROR: permission denied for table base_tbl
SELECT * FROM rw_view1; -- not allowed
ERROR: permission denied for table base_tbl
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
ERROR: permission denied for table base_tbl
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
ERROR: permission denied for view rw_view1
UPDATE base_tbl SET a=a; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
ERROR: permission denied for table base_tbl
DELETE FROM base_tbl; -- not allowed
ERROR: permission denied for table base_tbl
DELETE FROM rw_view1; -- not allowed
ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM base_tbl; -- ok
a | b | c
---+-------+---
1 | Row 1 | 1
2 | Row 2 | 2
(2 rows)
SELECT * FROM rw_view1; -- ok
bb | cc | aa
-------+----+----
Row 1 | 1 | 1
Row 2 | 2 | 2
(2 rows)
UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET cc=cc; -- ok
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view1 SET bb=bb; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
ERROR: permission denied for view rw_view1
DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
ERROR: permission denied for table base_tbl
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
DELETE FROM rw_view1 WHERE aa=2; -- ok
SELECT * FROM base_tbl; -- ok
a | b | c
---+-------+---
3 | Row 3 | 3
4 | Row 4 | 4
(2 rows)
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to view rw_view1
-- ordinary view on top of security invoker view permissions
CREATE TABLE base_tbl(a int, b text, c float);
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
SET SESSION AUTHORIZATION regress_view_user1;
CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
ALTER VIEW rw_view1 SET (security_invoker = true);
SELECT * FROM rw_view1; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user1;
GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1; -- ok
bb | cc | aa
-------+----+----
Row 1 | 1 | 1
(1 row)
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
UPDATE rw_view1 SET cc=cc; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
-----+-----+-------
1 | 1 | Row 1
(1 row)
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- not allowed
ERROR: permission denied for table base_tbl
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user3;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3;
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
-----+-----+-------
1 | 1 | Row 1
(1 row)
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view1 SET aa=aa; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
-----+-----+-------
1 | 1 | Row 1
(1 row)
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
-----+-----+-------
1 | 1 | Row 1
(1 row)
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- not allowed
ERROR: permission denied for table base_tbl
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
ccc | aaa | bbb
-----+-----+-------
1 | 1 | Row 1
(1 row)
UPDATE rw_view2 SET aaa=aaa; -- not allowed
ERROR: permission denied for view rw_view1
UPDATE rw_view2 SET bbb=bbb; -- not allowed
ERROR: permission denied for table base_tbl
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to view rw_view1
drop cascades to view rw_view2
DROP USER regress_view_user1;
DROP USER regress_view_user2;
DROP USER regress_view_user3;
-- column defaults
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified', c serial);
INSERT INTO base_tbl VALUES (1, 'Row 1');

View File

@ -254,9 +254,19 @@ CREATE VIEW mysecview5 WITH (security_barrier=100) -- Error
AS SELECT * FROM tbl1 WHERE a > 100;
CREATE VIEW mysecview6 WITH (invalid_option) -- Error
AS SELECT * FROM tbl1 WHERE a < 100;
CREATE VIEW mysecview7 WITH (security_invoker=true)
AS SELECT * FROM tbl1 WHERE a = 100;
CREATE VIEW mysecview8 WITH (security_invoker=false, security_barrier=true)
AS SELECT * FROM tbl1 WHERE a > 100;
CREATE VIEW mysecview9 WITH (security_invoker)
AS SELECT * FROM tbl1 WHERE a < 100;
CREATE VIEW mysecview10 WITH (security_invoker=100) -- Error
AS SELECT * FROM tbl1 WHERE a <> 100;
SELECT relname, relkind, reloptions FROM pg_class
WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
'mysecview3'::regclass, 'mysecview4'::regclass)
'mysecview3'::regclass, 'mysecview4'::regclass,
'mysecview7'::regclass, 'mysecview8'::regclass,
'mysecview9'::regclass)
ORDER BY relname;
CREATE OR REPLACE VIEW mysecview1
@ -267,9 +277,17 @@ CREATE OR REPLACE VIEW mysecview3 WITH (security_barrier=true)
AS SELECT * FROM tbl1 WHERE a < 256;
CREATE OR REPLACE VIEW mysecview4 WITH (security_barrier=false)
AS SELECT * FROM tbl1 WHERE a <> 256;
CREATE OR REPLACE VIEW mysecview7
AS SELECT * FROM tbl1 WHERE a > 256;
CREATE OR REPLACE VIEW mysecview8 WITH (security_invoker=true)
AS SELECT * FROM tbl1 WHERE a < 256;
CREATE OR REPLACE VIEW mysecview9 WITH (security_invoker=false, security_barrier=true)
AS SELECT * FROM tbl1 WHERE a <> 256;
SELECT relname, relkind, reloptions FROM pg_class
WHERE oid in ('mysecview1'::regclass, 'mysecview2'::regclass,
'mysecview3'::regclass, 'mysecview4'::regclass)
'mysecview3'::regclass, 'mysecview4'::regclass,
'mysecview7'::regclass, 'mysecview8'::regclass,
'mysecview9'::regclass)
ORDER BY relname;
-- Check that unknown literals are converted to "text" in CREATE VIEW,

View File

@ -122,10 +122,59 @@ BEGIN;
LOCK TABLE ONLY lock_tbl1;
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_tbl1 FROM regress_rol_lock1;
-- Tables referred to by views are locked without explicit permission to do so
-- as long as we have permission to lock the view itself.
SET ROLE regress_rol_lock1;
-- fail without permissions on the view
BEGIN;
LOCK TABLE lock_view1;
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_view1 TO regress_rol_lock1;
SET ROLE regress_rol_lock1;
BEGIN;
LOCK TABLE lock_view1 IN ACCESS EXCLUSIVE MODE;
-- lock_view1 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked.
select relname from pg_locks l, pg_class c
where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock'
order by relname;
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_view1 FROM regress_rol_lock1;
-- Tables referred to by security invoker views require explicit permission to
-- be locked.
CREATE VIEW lock_view8 WITH (security_invoker) AS SELECT * FROM lock_tbl1;
SET ROLE regress_rol_lock1;
-- fail without permissions on the view
BEGIN;
LOCK TABLE lock_view8;
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_view8 TO regress_rol_lock1;
SET ROLE regress_rol_lock1;
-- fail without permissions on the table referenced by the view
BEGIN;
LOCK TABLE lock_view8;
ROLLBACK;
RESET ROLE;
GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1;
BEGIN;
LOCK TABLE lock_view8 IN ACCESS EXCLUSIVE MODE;
-- lock_view8 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked.
select relname from pg_locks l, pg_class c
where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock'
order by relname;
ROLLBACK;
RESET ROLE;
REVOKE UPDATE ON TABLE lock_view8 FROM regress_rol_lock1;
--
-- Clean up
--
DROP VIEW lock_view8;
DROP VIEW lock_view7;
DROP VIEW lock_view6;
DROP VIEW lock_view5;

View File

@ -912,6 +912,128 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
GRANT SELECT ON rls_view TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Policy requiring access to another table.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE TABLE z1_blacklist (a int);
INSERT INTO z1_blacklist VALUES (3), (4);
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view; --fail - permission denied.
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not owner of table but is owner of view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_alice;
REVOKE SELECT ON z1_blacklist FROM regress_rls_bob;
DROP POLICY p3 ON z1;
SET SESSION AUTHORIZATION regress_rls_bob;
DROP VIEW rls_view;
--
-- Security invoker views should follow policy for current user.
--
-- View and table owner are the same.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE VIEW rls_view WITH (security_invoker) AS
SELECT * FROM z1 WHERE f_leak(b);
GRANT SELECT ON rls_view TO regress_rls_bob;
GRANT SELECT ON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records.
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Queries as other users.
-- Should return records based on current user's policies.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- View and table owners are different.
SET SESSION AUTHORIZATION regress_rls_alice;
DROP VIEW rls_view;
SET SESSION AUTHORIZATION regress_rls_bob;
CREATE VIEW rls_view WITH (security_invoker) AS
SELECT * FROM z1 WHERE f_leak(b);
GRANT SELECT ON rls_view TO regress_rls_alice;
GRANT SELECT ON rls_view TO regress_rls_carol;
-- Query as table owner. Should return all records.
SET SESSION AUTHORIZATION regress_rls_alice;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Queries as other users.
-- Should return records based on current user's policies.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Policy requiring access to another table.
SET SESSION AUTHORIZATION regress_rls_alice;
CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
-- Query as role that is not owner of table but is owner of view without permissions.
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view; --fail - permission denied.
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not owner of table but is owner of view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_bob;
SET SESSION AUTHORIZATION regress_rls_bob;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
-- Query as role that is not the owner of the table or view without permissions.
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view; --fail - permission denied.
EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
-- Query as role that is not the owner of the table or view with permissions.
SET SESSION AUTHORIZATION regress_rls_alice;
GRANT SELECT ON z1_blacklist TO regress_rls_carol;
SET SESSION AUTHORIZATION regress_rls_carol;
SELECT * FROM rls_view;
EXPLAIN (COSTS OFF) SELECT * FROM rls_view;

View File

@ -1257,3 +1257,31 @@ SELECT * FROM ruletest2;
DROP TABLE ruletest1;
DROP TABLE ruletest2;
--
-- Test non-SELECT rule on security invoker view.
-- Should use view owner's permissions.
--
CREATE USER regress_rule_user1;
CREATE TABLE ruletest_t1 (x int);
CREATE TABLE ruletest_t2 (x int);
CREATE VIEW ruletest_v1 WITH (security_invoker=true) AS
SELECT * FROM ruletest_t1;
GRANT INSERT ON ruletest_v1 TO regress_rule_user1;
CREATE RULE rule1 AS ON INSERT TO ruletest_v1
DO INSTEAD INSERT INTO ruletest_t2 VALUES (NEW.*);
SET SESSION AUTHORIZATION regress_rule_user1;
INSERT INTO ruletest_v1 VALUES (1);
RESET SESSION AUTHORIZATION;
SELECT * FROM ruletest_t1;
SELECT * FROM ruletest_t2;
DROP VIEW ruletest_v1;
DROP TABLE ruletest_t2;
DROP TABLE ruletest_t1;
DROP USER regress_rule_user1;

View File

@ -410,6 +410,7 @@ DROP TABLE base_tbl CASCADE;
CREATE USER regress_view_user1;
CREATE USER regress_view_user2;
CREATE USER regress_view_user3;
SET SESSION AUTHORIZATION regress_view_user1;
CREATE TABLE base_tbl(a int, b text, c float);
@ -552,8 +553,187 @@ RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
-- security invoker view permissions
SET SESSION AUTHORIZATION regress_view_user1;
CREATE TABLE base_tbl(a int, b text, c float);
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
ALTER VIEW rw_view1 SET (security_invoker = true);
INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
GRANT SELECT ON rw_view1 TO regress_view_user2;
GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM base_tbl; -- not allowed
SELECT * FROM rw_view1; -- not allowed
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
UPDATE base_tbl SET a=a; -- not allowed
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
DELETE FROM base_tbl; -- not allowed
DELETE FROM rw_view1; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM base_tbl; -- ok
SELECT * FROM rw_view1; -- ok
UPDATE base_tbl SET a=a, c=c; -- ok
UPDATE base_tbl SET b=b; -- not allowed
UPDATE rw_view1 SET cc=cc; -- ok
UPDATE rw_view1 SET aa=aa; -- not allowed
UPDATE rw_view1 SET bb=bb; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
DELETE FROM base_tbl WHERE a=1; -- ok
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
DELETE FROM rw_view1 WHERE aa=2; -- ok
SELECT * FROM base_tbl; -- ok
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
-- ordinary view on top of security invoker view permissions
CREATE TABLE base_tbl(a int, b text, c float);
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
SET SESSION AUTHORIZATION regress_view_user1;
CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
ALTER VIEW rw_view1 SET (security_invoker = true);
SELECT * FROM rw_view1; -- not allowed
UPDATE rw_view1 SET aa=aa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user1;
GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1; -- ok
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
UPDATE rw_view1 SET cc=cc; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user1;
GRANT SELECT ON rw_view1 TO regress_view_user2;
GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user2;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- not allowed
RESET SESSION AUTHORIZATION;
GRANT SELECT ON base_tbl TO regress_view_user3;
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3;
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
SET SESSION AUTHORIZATION regress_view_user1;
SELECT * FROM rw_view1; -- not allowed
UPDATE rw_view1 SET aa=aa; -- not allowed
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2;
SET SESSION AUTHORIZATION regress_view_user2;
SELECT * FROM rw_view2; -- not allowed
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- not allowed
SET SESSION AUTHORIZATION regress_view_user3;
SELECT * FROM rw_view2; -- ok
UPDATE rw_view2 SET aaa=aaa; -- not allowed
UPDATE rw_view2 SET bbb=bbb; -- not allowed
UPDATE rw_view2 SET ccc=ccc; -- ok
RESET SESSION AUTHORIZATION;
DROP TABLE base_tbl CASCADE;
DROP USER regress_view_user1;
DROP USER regress_view_user2;
DROP USER regress_view_user3;
-- column defaults