diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/access/common/reloptions.c | 11 | ||||
-rw-r--r-- | src/backend/commands/lockcmds.c | 19 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 18 | ||||
-rw-r--r-- | src/backend/utils/cache/relcache.c | 73 | ||||
-rw-r--r-- | src/include/utils/rel.h | 11 | ||||
-rw-r--r-- | src/test/regress/expected/create_view.out | 46 | ||||
-rw-r--r-- | src/test/regress/expected/lock.out | 66 | ||||
-rw-r--r-- | src/test/regress/expected/rowsecurity.out | 257 | ||||
-rw-r--r-- | src/test/regress/expected/rules.out | 30 | ||||
-rw-r--r-- | src/test/regress/expected/updatable_views.out | 237 | ||||
-rw-r--r-- | src/test/regress/sql/create_view.sql | 22 | ||||
-rw-r--r-- | src/test/regress/sql/lock.sql | 49 | ||||
-rw-r--r-- | src/test/regress/sql/rowsecurity.sql | 122 | ||||
-rw-r--r-- | src/test/regress/sql/rules.sql | 28 | ||||
-rw-r--r-- | src/test/regress/sql/updatable_views.sql | 180 |
15 files changed, 1120 insertions, 49 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index d592655258a..599e160ca64 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -142,6 +142,15 @@ static relopt_bool boolRelOpts[] = }, { { + "security_invoker", + "Privileges on underlying relations are checked as the invoking user, not the view owner", + RELOPT_KIND_VIEW, + AccessExclusiveLock + }, + false + }, + { + { "vacuum_truncate", "Enables vacuum to truncate empty pages at the end of this table", RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, @@ -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)} }; diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 4b3f79704f8..b97b8b0435c 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -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); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 3d82138cb39..4eeed580b16 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -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; /* diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index fccffce5729..fbd11883e17 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -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; @@ -1217,6 +1212,30 @@ retry: 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 */ RelationInitLockInfo(relation); /* see lmgr.c */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 3b4ab65ae20..7a8ed943b75 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -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; @@ -412,6 +413,16 @@ typedef struct ViewOptions ((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 * or the cascaded check option. Note multiple eval of argument! diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index ae7c04353cf..32385bbb0ef 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -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 diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out index 01d467a6e05..ad137d3645d 100644 --- a/src/test/regress/expected/lock.out +++ b/src/test/regress/expected/lock.out @@ -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; diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out index 89397e41f01..d32a40ede33 100644 --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@ -2431,6 +2431,182 @@ 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 + 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) + +-- 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 @@ -2447,6 +2623,84 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view; 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 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index ac468568a1a..6cb6388880d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -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; diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index cdff914b93a..d57eeb761c0 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -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'); diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 829f3ddbe68..50acfe96e69 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -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, diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql index b867e0f9944..b88488c6d0f 100644 --- a/src/test/regress/sql/lock.sql +++ b/src/test/regress/sql/lock.sql @@ -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; diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql index 44deb42bad5..b310acdd27c 100644 --- a/src/test/regress/sql/rowsecurity.sql +++ b/src/test/regress/sql/rowsecurity.sql @@ -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; diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index 8bdab6dec30..aae2ba32e80 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -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; diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index 09328e582b2..fa206a8fe7e 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -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 |