*** a/doc/src/sgml/ref/alter_view.sgml --- b/doc/src/sgml/ref/alter_view.sgml *************** *** 142,147 **** ALTER VIEW [ IF EXISTS ] name RESET --- 142,157 ---- + + security_definer (boolean) + + + Changes the security-definer property of the view. The value must + be Boolean value, such as true + or false. + + + *** a/doc/src/sgml/ref/create_view.sgml --- b/doc/src/sgml/ref/create_view.sgml *************** *** 139,145 **** CREATE VIEW name AS WITH RECURSIVE name ( ! security_barrier (string) This should be used if the view is intended to provide row-level --- 139,145 ---- ! security_barrier (boolean) This should be used if the view is intended to provide row-level *************** *** 147,152 **** CREATE VIEW name AS WITH RECURSIVE name ( + + + security_definer (boolean) + + + This should be used if the view is intended to be executed with + owner privileges rather than the current user. + + + *************** *** 276,281 **** CREATE VIEW vista AS SELECT text 'Hello World' AS hello; --- 286,322 ---- to replace it (this includes being a member of the owning role). + + Security definer Views + + + security definer views + + + + Security definer views uses the view owner id instead of the current user + in the following conditions, otherwise the current user is used to verify + the privileges and etc. + + + + The view is used in INSERT, UPDATE + and DELETE statements in the same way as on a + regular table. + + + + + + To apply row-level security policies on the underlying base + relations of the view, based on the security definer, the + corresponding policies related to the user are applied. + + + + + + Updatable Views *** a/src/backend/access/common/reloptions.c --- b/src/backend/access/common/reloptions.c *************** *** 89,94 **** static relopt_bool boolRelOpts[] = --- 89,103 ---- }, false }, + { + { + "security_definer", + "specifies that the view is to be executed with the privileges of the user that created it.", + RELOPT_KIND_VIEW, + AccessExclusiveLock + }, + false + }, /* list terminator */ {{NULL}} }; *************** *** 1320,1325 **** view_reloptions(Datum reloptions, bool validate) --- 1329,1336 ---- static const relopt_parse_elt tab[] = { {"security_barrier", RELOPT_TYPE_BOOL, offsetof(ViewOptions, security_barrier)}, + { "security_definer", RELOPT_TYPE_BOOL, + offsetof(ViewOptions, security_definer) }, {"check_option", RELOPT_TYPE_STRING, offsetof(ViewOptions, check_option_offset)} }; *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** *** 24,29 **** --- 24,30 ---- #include "catalog/pg_type.h" #include "commands/trigger.h" #include "foreign/fdwapi.h" + #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/analyze.h" *************** *** 1616,1621 **** fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown) --- 1617,1623 ---- int origResultRelation = parsetree->resultRelation; int rt_index; ListCell *lc; + bool security_definer_view = false; /* * don't try to convert this into a foreach loop, because rtable list can *************** *** 1777,1794 **** fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown) ++rt_index; /* Only normal relations can have RLS policies */ ! if (rte->rtekind != RTE_RELATION || ! rte->relkind != RELKIND_RELATION) continue; ! rel = heap_open(rte->relid, NoLock); /* * Fetch any new security quals that must be applied to this RTE. */ get_row_security_policies(parsetree, rte, rt_index, &securityQuals, &withCheckOptions, ! &hasRowSecurity, &hasSubLinks); if (securityQuals != NIL || withCheckOptions != NIL) { --- 1779,1805 ---- ++rt_index; /* Only normal relations can have RLS policies */ ! if (rte->rtekind != RTE_RELATION) continue; ! rel = heap_open(rte->relid, NoLock); + if (rte->relkind == RELKIND_VIEW && RelationIsSecurityDefinerView(rel)) + security_definer_view = true; + + if (rte->relkind != RELKIND_RELATION) + { + heap_close(rel, NoLock); + continue; + } + /* * Fetch any new security quals that must be applied to this RTE. */ get_row_security_policies(parsetree, rte, rt_index, &securityQuals, &withCheckOptions, ! &hasRowSecurity, &hasSubLinks, ! security_definer_view); if (securityQuals != NIL || withCheckOptions != NIL) { *************** *** 2824,2830 **** rewriteTargetView(Query *parsetree, Relation view) * the executor still performs appropriate permissions checks for the * query caller's use of the view. */ ! new_rte->checkAsUser = view->rd_rel->relowner; new_rte->requiredPerms = view_rte->requiredPerms; /* --- 2835,2844 ---- * the executor still performs appropriate permissions checks for the * query caller's use of the view. */ ! if (RelationIsSecurityDefinerView(view)) ! new_rte->checkAsUser = view->rd_rel->relowner; ! else ! new_rte->checkAsUser = GetUserId(); new_rte->requiredPerms = view_rte->requiredPerms; /* *** a/src/backend/rewrite/rowsecurity.c --- b/src/backend/rewrite/rowsecurity.c *************** *** 105,111 **** row_security_policy_hook_type row_security_policy_hook_restrictive = NULL; void get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, List **securityQuals, List **withCheckOptions, ! bool *hasRowSecurity, bool *hasSubLinks) { Oid user_id; int rls_status; --- 105,112 ---- void get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, List **securityQuals, List **withCheckOptions, ! bool *hasRowSecurity, bool *hasSubLinks, ! bool security_definer_view) { Oid user_id; int rls_status; *************** *** 125,134 **** get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, return; /* Switch to checkAsUser if it's set */ ! user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Determine the state of RLS for this, pass checkAsUser explicitly */ ! rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false); /* If there is no RLS on this table at all, nothing to do */ if (rls_status == RLS_NONE) --- 126,138 ---- return; /* Switch to checkAsUser if it's set */ ! if (security_definer_view) ! user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId(); ! else ! user_id = GetUserId(); /* Determine the state of RLS for this, pass checkAsUser explicitly */ ! rls_status = check_enable_rls(rte->relid, user_id, false); /* If there is no RLS on this table at all, nothing to do */ if (rls_status == RLS_NONE) *** a/src/include/rewrite/rowsecurity.h --- b/src/include/rewrite/rowsecurity.h *************** *** 43,48 **** extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook_restri extern void get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, List **securityQuals, List **withCheckOptions, ! bool *hasRowSecurity, bool *hasSubLinks); #endif /* ROWSECURITY_H */ --- 43,49 ---- extern void get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, List **securityQuals, List **withCheckOptions, ! bool *hasRowSecurity, bool *hasSubLinks, ! bool security_definer_view); #endif /* ROWSECURITY_H */ *** a/src/include/utils/rel.h --- b/src/include/utils/rel.h *************** *** 265,270 **** typedef struct ViewOptions --- 265,271 ---- { int32 vl_len_; /* varlena header (do not touch directly!) */ bool security_barrier; + bool security_definer; int check_option_offset; } ViewOptions; *************** *** 278,283 **** typedef struct ViewOptions --- 279,292 ---- ((ViewOptions *) (relation)->rd_options)->security_barrier : false) /* + * RelationIsSecurityDefiner + * Returns whether the relation is security definer, or not. + */ + #define RelationIsSecurityDefinerView(relation) \ + ((relation)->rd_options ? \ + ((ViewOptions *) (relation)->rd_options)->security_definer : 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! *** a/src/test/regress/expected/rowsecurity.out --- b/src/test/regress/expected/rowsecurity.out *************** *** 1425,1431 **** CREATE POLICY p1 ON b1 USING (a % 2 = 0); ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; GRANT ALL ON b1 TO rls_regress_user1; SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; GRANT ALL ON bv1 TO rls_regress_user2; SET SESSION AUTHORIZATION rls_regress_user2; EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b); --- 1425,1431 ---- ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; GRANT ALL ON b1 TO rls_regress_user1; SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW bv1 WITH (security_barrier, security_definer) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; GRANT ALL ON bv1 TO rls_regress_user2; SET SESSION AUTHORIZATION rls_regress_user2; EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b); *************** *** 1740,1746 **** EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); -- -- View and Table owner are the same. SET SESSION AUTHORIZATION rls_regress_user0; ! CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user1; -- Query as role that is not owner of view or table. Should return all records. SET SESSION AUTHORIZATION rls_regress_user1; --- 1740,1746 ---- -- -- View and Table owner are the same. SET SESSION AUTHORIZATION rls_regress_user0; ! CREATE VIEW rls_view WITH (security_definer) AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user1; -- Query as role that is not owner of view or table. Should return all records. SET SESSION AUTHORIZATION rls_regress_user1; *************** *** 1789,1795 **** EXPLAIN (COSTS OFF) SELECT * FROM rls_view; DROP VIEW rls_view; -- View and Table owners are different. SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user0; -- Query as role that is not owner of view but is owner of table. -- Should return records based on view owner policies. --- 1789,1795 ---- DROP VIEW rls_view; -- View and Table owners are different. SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW rls_view WITH (security_definer) AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user0; -- Query as role that is not owner of view but is owner of table. -- Should return records based on view owner policies. *** a/src/test/regress/expected/updatable_views.out --- b/src/test/regress/expected/updatable_views.out *************** *** 966,972 **** CREATE USER view_user2; SET SESSION AUTHORIZATION 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; INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); GRANT SELECT ON base_tbl TO view_user2; GRANT SELECT ON rw_view1 TO view_user2; --- 966,972 ---- SET SESSION AUTHORIZATION 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 WITH (security_definer) AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); GRANT SELECT ON base_tbl TO view_user2; GRANT SELECT ON rw_view1 TO view_user2; *** a/src/test/regress/sql/rowsecurity.sql --- b/src/test/regress/sql/rowsecurity.sql *************** *** 491,497 **** ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; GRANT ALL ON b1 TO rls_regress_user1; SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; GRANT ALL ON bv1 TO rls_regress_user2; SET SESSION AUTHORIZATION rls_regress_user2; --- 491,497 ---- GRANT ALL ON b1 TO rls_regress_user1; SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW bv1 WITH (security_barrier, security_definer) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; GRANT ALL ON bv1 TO rls_regress_user2; SET SESSION AUTHORIZATION rls_regress_user2; *************** *** 665,671 **** EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); -- -- View and Table owner are the same. SET SESSION AUTHORIZATION rls_regress_user0; ! CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user1; -- Query as role that is not owner of view or table. Should return all records. --- 665,671 ---- -- -- View and Table owner are the same. SET SESSION AUTHORIZATION rls_regress_user0; ! CREATE VIEW rls_view WITH (security_definer) AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user1; -- Query as role that is not owner of view or table. Should return all records. *************** *** 681,687 **** DROP VIEW rls_view; -- View and Table owners are different. SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user0; -- Query as role that is not owner of view but is owner of table. --- 681,687 ---- -- View and Table owners are different. SET SESSION AUTHORIZATION rls_regress_user1; ! CREATE VIEW rls_view WITH (security_definer) AS SELECT * FROM z1 WHERE f_leak(b); GRANT SELECT ON rls_view TO rls_regress_user0; -- Query as role that is not owner of view but is owner of table. *** a/src/test/regress/sql/updatable_views.sql --- b/src/test/regress/sql/updatable_views.sql *************** *** 397,403 **** CREATE USER view_user2; SET SESSION AUTHORIZATION 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; INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); GRANT SELECT ON base_tbl TO view_user2; --- 397,403 ---- SET SESSION AUTHORIZATION 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 WITH (security_definer) AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); GRANT SELECT ON base_tbl TO view_user2;