*** 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;