{
Query *parse = root->parse;
int parentRTindex = parse->resultRelation;
+ Bitmapset *resultRTindexes = NULL;
int nominalRelation = -1;
List *final_rtable = NIL;
int save_rel_array_size = 0;
* (1) would result in a rangetable of length O(N^2) for N targets, with
* at least O(N^3) work expended here; and (2) would greatly complicate
* management of the rowMarks list.
+ *
+ * Note that any RTEs with security barrier quals will be turned into
+ * subqueries during planning, and so we must create copies of them too,
+ * except where they are target relations, which will each only be used
+ * in a single plan.
*/
+ resultRTindexes = bms_add_member(resultRTindexes, parentRTindex);
+ foreach(lc, root->append_rel_list)
+ {
+ AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+ if (appinfo->parent_relid == parentRTindex)
+ resultRTindexes = bms_add_member(resultRTindexes,
+ appinfo->child_relid);
+ }
+
foreach(lc, root->append_rel_list)
{
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr);
- if (rte->rtekind == RTE_SUBQUERY)
+ /*
+ * Copy subquery RTEs and RTEs with security barrier quals
+ * that will be turned into subqueries, except those that are
+ * target relations.
+ */
+ if (rte->rtekind == RTE_SUBQUERY ||
+ (rte->securityQuals != NIL &&
+ !bms_is_member(rti, resultRTindexes)))
{
Index newrti;
/*
* The RTE can't contain any references to its own RT
- * index, so we can save a few cycles by applying
- * ChangeVarNodes before we append the RTE to the
- * rangetable.
+ * index, except in the security barrier quals, so we can
+ * save a few cycles by applying ChangeVarNodes before we
+ * append the RTE to the rangetable.
*/
newrti = list_length(subroot.parse->rtable) + 1;
ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0);
rte = copyObject(rte);
+ ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0);
subroot.parse->rtable = lappend(subroot.parse->rtable,
rte);
}
switch (strength)
{
case LCS_NONE:
- /* don't need tuple lock, only ability to re-fetch the row */
+ /*
+ * We don't need a tuple lock, only the ability to re-fetch
+ * the row. Regular tables support ROW_MARK_REFERENCE, but if
+ * this RTE has security barrier quals, it will be turned into
+ * a subquery during planning, so use ROW_MARK_COPY.
+ *
+ * This is only necessary for LCS_NONE, since real tuple locks
+ * on an RTE with security barrier quals are supported by
+ * pushing the lock down into the subquery --- see
+ * expand_security_qual.
+ */
+ if (rte->securityQuals != NIL)
+ return ROW_MARK_COPY;
return ROW_MARK_REFERENCE;
break;
case LCS_FORKEYSHARE:
activeRIRs = list_delete_first(activeRIRs);
}
}
- /*
- * If the RTE has row security quals, apply them and recurse into the
- * securityQuals.
- */
- if (prepend_row_security_policies(parsetree, rte, rt_index))
- {
- /*
- * We applied security quals, check for infinite recursion and
- * then expand any nested queries.
- */
- if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("infinite recursion detected in policy for relation \"%s\"",
- RelationGetRelationName(rel))));
-
- /*
- * Make sure we check for recursion in either securityQuals or
- * WITH CHECK quals.
- */
- if (rte->securityQuals != NIL)
- {
- activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
-
- expression_tree_walker( (Node*) rte->securityQuals,
- fireRIRonSubLink, (void*)activeRIRs );
-
- activeRIRs = list_delete_first(activeRIRs);
- }
-
- if (parsetree->withCheckOptions != NIL)
- {
- WithCheckOption *wco;
- List *quals = NIL;
-
- wco = (WithCheckOption *) makeNode(WithCheckOption);
- quals = lcons(wco->qual, quals);
-
- activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
-
- expression_tree_walker( (Node*) quals, fireRIRonSubLink,
- (void*)activeRIRs);
- }
-
- }
heap_close(rel, NoLock);
}
query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
QTW_IGNORE_RC_SUBQUERIES);
+ /*
+ * Apply any row level security policies. We do this last because it
+ * requires special recursion detection if the new quals have sublink
+ * subqueries, and if we did it in the loop above query_tree_walker
+ * would then recurse into those quals a second time.
+ */
+ rt_index = 0;
+ foreach(lc, parsetree->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ Relation rel;
+ List *securityQuals;
+ List *withCheckOptions;
+ bool hasRowSecurity;
+ bool hasSubLinks;
+
+ ++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)
+ {
+ if (hasSubLinks)
+ {
+ /*
+ * Recursively process the new quals, checking for infinite
+ * recursion.
+ */
+ if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("infinite recursion detected in policy for relation \"%s\"",
+ RelationGetRelationName(rel))));
+
+ activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+
+ expression_tree_walker( (Node*) securityQuals,
+ fireRIRonSubLink, (void*)activeRIRs );
+
+ expression_tree_walker( (Node*) withCheckOptions,
+ fireRIRonSubLink, (void*)activeRIRs );
+
+ activeRIRs = list_delete_first(activeRIRs);
+ }
+
+ /*
+ * Add the new security quals to the start of the RTE's list so
+ * that they get applied before any existing security quals (which
+ * might have come from a user-written security barrier view, and
+ * might contain malicious code).
+ */
+ rte->securityQuals = list_concat(securityQuals,
+ rte->securityQuals);
+
+ parsetree->withCheckOptions = list_concat(withCheckOptions,
+ parsetree->withCheckOptions);
+ }
+
+ /*
+ * Make sure the query is marked correctly if row level security
+ * applies, or if the new quals had sublinks.
+ */
+ if (hasRowSecurity)
+ parsetree->hasRowSecurity = true;
+ if (hasSubLinks)
+ parsetree->hasSubLinks = true;
+
+ heap_close(rel, NoLock);
+ }
+
return parsetree;
}
static List *pull_row_security_policies(CmdType cmd, Relation relation,
Oid user_id);
-static void process_policies(List *policies, int rt_index,
+static void process_policies(Query* root, List *policies, int rt_index,
Expr **final_qual,
Expr **final_with_check_qual,
- bool *hassublinks);
+ bool *hassublinks,
+ BoolExprType boolop);
static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
/*
- * hook to allow extensions to apply their own security policy
+ * hooks to allow extensions to add their own security policies
+ *
+ * row_security_policy_hook_permissive can be used to add policies which
+ * are included in the "OR"d set of policies.
+ *
+ * row_security_policy_hook_restrictive can be used to add policies which
+ * are enforced, regardless of other policies (they are "AND"d).
*
* See below where the hook is called in prepend_row_security_policies for
* insight into how to use this hook.
*/
-row_security_policy_hook_type row_security_policy_hook = NULL;
+row_security_policy_hook_type row_security_policy_hook_permissive = NULL;
+row_security_policy_hook_type row_security_policy_hook_restrictive = NULL;
/*
- * Check the given RTE to see whether it's already had row security quals
- * expanded and, if not, prepend any row security rules from built-in or
- * plug-in sources to the securityQuals. The security quals are rewritten (for
- * view expansion, etc) before being added to the RTE.
+ * Get any row security quals and check quals that should be applied to the
+ * specified RTE.
*
- * Returns true if any quals were added. Note that quals may have been found
- * but not added if user rights make the user exempt from row security.
+ * In addition, hasRowSecurity is set to true if row level security is enabled
+ * (even if this RTE doesn't have any row security quals), and hasSubLinks is
+ * set to true if any of the quals returned contain sublinks.
*/
-bool
-prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
+void
+get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
+ List **securityQuals, List **withCheckOptions,
+ bool *hasRowSecurity, bool *hasSubLinks)
{
Expr *rowsec_expr = NULL;
Expr *rowsec_with_check_expr = NULL;
- Expr *hook_expr = NULL;
- Expr *hook_with_check_expr = NULL;
+ Expr *hook_expr_restrictive = NULL;
+ Expr *hook_with_check_expr_restrictive = NULL;
+ Expr *hook_expr_permissive = NULL;
+ Expr *hook_with_check_expr_permissive = NULL;
List *rowsec_policies;
- List *hook_policies = NIL;
+ List *hook_policies_restrictive = NIL;
+ List *hook_policies_permissive = NIL;
Relation rel;
Oid user_id;
int sec_context;
int rls_status;
- bool defaultDeny = true;
- bool hassublinks = false;
+ bool defaultDeny = false;
+
+ /* Defaults for the return values */
+ *securityQuals = NIL;
+ *withCheckOptions = NIL;
+ *hasRowSecurity = false;
+ *hasSubLinks = false;
/* This is just to get the security context */
GetUserIdAndSecContext(&user_id, &sec_context);
if (rte->relid < FirstNormalObjectId
|| rte->relkind != RELKIND_RELATION
|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
- return false;
+ return;
/* 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)
- return false;
+ return;
/*
* RLS_NONE_ENV means we are not doing any RLS now, but that may change
* be replanned if the environment changes (GUCs, role), but we
* are not adding anything here.
*/
- root->hasRowSecurity = true;
+ *hasRowSecurity = true;
- return false;
+ return;
}
- /*
- * We may end up getting called multiple times for the same RTE, so check
- * to make sure we aren't doing double-work.
- */
- if (rte->securityQuals != NIL)
- return false;
-
/* Grab the built-in policies which should be applied to this relation. */
rel = heap_open(rte->relid, NoLock);
defaultDeny = true;
/* Now that we have our policies, build the expressions from them. */
- process_policies(rowsec_policies, rt_index, &rowsec_expr,
- &rowsec_with_check_expr, &hassublinks);
+ process_policies(root, rowsec_policies, rt_index, &rowsec_expr,
+ &rowsec_with_check_expr, hasSubLinks, OR_EXPR);
/*
* Also, allow extensions to add their own policies.
*
+ * extensions can add either permissive or restrictive policies.
+ *
* Note that, as with the internal policies, if multiple policies are
* returned then they will be combined into a single expression with
- * all of them OR'd together. However, to avoid the situation of an
- * extension granting more access to a table than the internal policies
- * would allow, the extension's policies are AND'd with the internal
- * policies. In other words - extensions can only provide further
- * filtering of the result set (or further reduce the set of records
- * allowed to be added).
+ * all of them OR'd (for permissive) or AND'd (for restrictive) together.
*
* If only a USING policy is returned by the extension then it will be
* used for WITH CHECK as well, similar to how internal policies are
* default-deny policy and use only the policies returned by the
* extension.
*/
- if (row_security_policy_hook)
+ if (row_security_policy_hook_restrictive)
{
- hook_policies = (*row_security_policy_hook)(root->commandType, rel);
+ hook_policies_restrictive = (*row_security_policy_hook_restrictive)(root->commandType, rel);
/* Build the expression from any policies returned. */
- process_policies(hook_policies, rt_index, &hook_expr,
- &hook_with_check_expr, &hassublinks);
+ if (hook_policies_restrictive != NIL)
+ process_policies(root, hook_policies_restrictive, rt_index,
+ &hook_expr_restrictive,
+ &hook_with_check_expr_restrictive,
+ hasSubLinks,
+ AND_EXPR);
+ }
+
+ if (row_security_policy_hook_permissive)
+ {
+ hook_policies_permissive = (*row_security_policy_hook_permissive)(root->commandType, rel);
+
+ /* Build the expression from any policies returned. */
+ if (hook_policies_permissive != NIL)
+ process_policies(root, hook_policies_permissive, rt_index,
+ &hook_expr_permissive,
+ &hook_with_check_expr_permissive, hasSubLinks,
+ OR_EXPR);
}
/*
* If the only built-in policy is the default-deny one, and hook
* policies exist, then use the hook policies only and do not apply
- * the default-deny policy. Otherwise, apply both sets (AND'd
- * together).
+ * the default-deny policy. Otherwise, we will apply both sets below.
*/
- if (defaultDeny && hook_policies != NIL)
+ if (defaultDeny &&
+ (hook_policies_restrictive != NIL || hook_policies_permissive != NIL))
+ {
rowsec_expr = NULL;
+ rowsec_with_check_expr = NULL;
+ }
/*
* For INSERT or UPDATE, we need to add the WITH CHECK quals to
* WITH CHECK OPTIONS wants a WCO node which wraps each Expr, so
* create them as necessary.
*/
- if (rowsec_with_check_expr)
+
+ /*
+ * Handle any restrictive policies first.
+ *
+ * They can simply be added.
+ */
+ if (hook_with_check_expr_restrictive)
{
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->viewname = RelationGetRelationName(rel);
- wco->qual = (Node *) rowsec_with_check_expr;
+ wco->qual = (Node *) hook_with_check_expr_restrictive;
wco->cascaded = false;
- root->withCheckOptions = lcons(wco, root->withCheckOptions);
+ *withCheckOptions = lappend(*withCheckOptions, wco);
}
/*
- * Ditto for the expression, if any, returned from the extension.
+ * Handle built-in policies, if there are no permissive
+ * policies from the hook.
*/
- if (hook_with_check_expr)
+ if (rowsec_with_check_expr && !hook_with_check_expr_permissive)
+ {
+ WithCheckOption *wco;
+
+ wco = (WithCheckOption *) makeNode(WithCheckOption);
+ wco->viewname = RelationGetRelationName(rel);
+ wco->qual = (Node *) rowsec_with_check_expr;
+ wco->cascaded = false;
+ *withCheckOptions = lappend(*withCheckOptions, wco);
+ }
+ /* Handle the hook policies, if there are no built-in ones. */
+ else if (!rowsec_with_check_expr && hook_with_check_expr_permissive)
{
WithCheckOption *wco;
wco = (WithCheckOption *) makeNode(WithCheckOption);
wco->viewname = RelationGetRelationName(rel);
- wco->qual = (Node *) hook_with_check_expr;
+ wco->qual = (Node *) hook_with_check_expr_permissive;
wco->cascaded = false;
- root->withCheckOptions = lcons(wco, root->withCheckOptions);
+ *withCheckOptions = lappend(*withCheckOptions, wco);
+ }
+ /* Handle the case where there are both. */
+ else if (rowsec_with_check_expr && hook_with_check_expr_permissive)
+ {
+ WithCheckOption *wco;
+ List *combined_quals = NIL;
+ Expr *combined_qual_eval;
+
+ combined_quals = lcons(copyObject(rowsec_with_check_expr), combined_quals);
+ combined_quals = lcons(copyObject(hook_with_check_expr_permissive), combined_quals);
+
+ combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
+
+ wco = (WithCheckOption *) makeNode(WithCheckOption);
+ wco->viewname = RelationGetRelationName(rel);
+ wco->qual = (Node *) combined_qual_eval;
+ wco->cascaded = false;
+ *withCheckOptions = lappend(*withCheckOptions, wco);
}
}
|| root->commandType == CMD_UPDATE
|| root->commandType == CMD_DELETE)
{
- if (rowsec_expr)
- rte->securityQuals = lcons(rowsec_expr, rte->securityQuals);
+ /* restrictive policies can simply be added to the list first */
+ if (hook_expr_restrictive)
+ *securityQuals = lappend(*securityQuals, hook_expr_restrictive);
+
+ /* If we only have internal permissive, then just add those */
+ if (rowsec_expr && !hook_expr_permissive)
+ *securityQuals = lappend(*securityQuals, rowsec_expr);
+ /* .. and if we have only permissive policies from the hook */
+ else if (!rowsec_expr && hook_expr_permissive)
+ *securityQuals = lappend(*securityQuals, hook_expr_permissive);
+ /* if we have both, we have to combine them with an OR */
+ else if (rowsec_expr && hook_expr_permissive)
+ {
+ List *combined_quals = NIL;
+ Expr *combined_qual_eval;
- if (hook_expr)
- rte->securityQuals = lcons(hook_expr,
- rte->securityQuals);
+ combined_quals = lcons(copyObject(rowsec_expr), combined_quals);
+ combined_quals = lcons(copyObject(hook_expr_permissive), combined_quals);
+
+ combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
+
+ *securityQuals = lappend(*securityQuals, combined_qual_eval);
+ }
}
heap_close(rel, NoLock);
* Mark this query as having row security, so plancache can invalidate
* it when necessary (eg: role changes)
*/
- root->hasRowSecurity = true;
+ *hasRowSecurity = true;
- /*
- * If we have sublinks added because of the policies being added to the
- * query, then set hasSubLinks on the Query to force subLinks to be
- * properly expanded.
- */
- root->hasSubLinks |= hassublinks;
-
- /* If we got this far, we must have added quals */
- return true;
+ return;
}
/*
{
List *policies = NIL;
ListCell *item;
- RowSecurityPolicy *policy;
/*
* Row security is enabled for the relation and the row security GUC is
*/
foreach(item, relation->rd_rsdesc->policies)
{
- policy = (RowSecurityPolicy *) lfirst(item);
+ RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
/* Always add ALL policies, if they exist. */
if (policy->polcmd == '*' &&
* qual_eval, with_check_eval, and hassublinks are output variables
*/
static void
-process_policies(List *policies, int rt_index, Expr **qual_eval,
- Expr **with_check_eval, bool *hassublinks)
+process_policies(Query* root, List *policies, int rt_index, Expr **qual_eval,
+ Expr **with_check_eval, bool *hassublinks,
+ BoolExprType boolop)
{
ListCell *item;
List *quals = NIL;
/*
* Extract the USING and WITH CHECK quals from each of the policies
- * and add them to our lists.
+ * and add them to our lists. We only want WITH CHECK quals if this
+ * RTE is the query's result relation.
*/
foreach(item, policies)
{
if (policy->qual != NULL)
quals = lcons(copyObject(policy->qual), quals);
- if (policy->with_check_qual != NULL)
+ if (policy->with_check_qual != NULL &&
+ rt_index == root->resultRelation)
with_check_quals = lcons(copyObject(policy->with_check_qual),
with_check_quals);
+ /*
+ * For each policy, if there is only a USING clause then copy/use it for
+ * the WITH CHECK policy also, if this RTE is the query's result
+ * relation.
+ */
+ if (policy->qual != NULL && policy->with_check_qual == NULL &&
+ rt_index == root->resultRelation)
+ with_check_quals = lcons(copyObject(policy->qual),
+ with_check_quals);
+
+
if (policy->hassublinks)
*hassublinks = true;
}
quals = lcons(makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
BoolGetDatum(false), false, true), quals);
- /*
- * If we end up with only USING quals, then use those as
- * WITH CHECK quals also.
- */
- if (with_check_quals == NIL)
- with_check_quals = copyObject(quals);
-
/*
* Row security quals always have the target table as varno 1, as no
* joins are permitted in row security expressions. We must walk the
* the table has in the outer query.
*
* We rewrite the expression in-place.
+ *
+ * We must have some quals at this point; the default-deny policy, if
+ * nothing else. Note that we might not have any WITH CHECK quals-
+ * that's fine, as this might not be the resultRelation.
*/
+ Assert(quals != NIL);
+
ChangeVarNodes((Node *) quals, 1, rt_index, 0);
- ChangeVarNodes((Node *) with_check_quals, 1, rt_index, 0);
+
+ if (with_check_quals != NIL)
+ ChangeVarNodes((Node *) with_check_quals, 1, rt_index, 0);
/*
* If more than one security qual is returned, then they need to be
- * OR'ed together.
+ * combined together.
*/
if (list_length(quals) > 1)
- *qual_eval = makeBoolExpr(OR_EXPR, quals, -1);
+ *qual_eval = makeBoolExpr(boolop, quals, -1);
else
*qual_eval = (Expr*) linitial(quals);
/*
- * If more than one WITH CHECK qual is returned, then they need to
- * be OR'ed together.
+ * Similairly, if more than one WITH CHECK qual is returned, then
+ * they need to be combined together.
+ *
+ * with_check_quals is allowed to be NIL here since this might not be the
+ * resultRelation (see above).
*/
if (list_length(with_check_quals) > 1)
- *with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1);
- else
+ *with_check_eval = makeBoolExpr(boolop, with_check_quals, -1);
+ else if (with_check_quals != NIL)
*with_check_eval = (Expr*) linitial(with_check_quals);
+ else
+ *with_check_eval = NULL;
return;
}
typedef List *(*row_security_policy_hook_type)(CmdType cmdtype,
Relation relation);
-extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
+extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook_permissive;
-extern bool prepend_row_security_policies(Query* root, RangeTblEntry* rte,
- int rt_index);
+extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook_restrictive;
+
+extern void get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
+ List **securityQuals, List **withCheckOptions,
+ bool *hasRowSecurity, bool *hasSubLinks);
#endif /* ROWSECURITY_H */
commit_ts \
worker_spi \
dummy_seclabel \
+ test_rls_hooks \
test_shm_mq \
test_parser
--- /dev/null
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
--- /dev/null
+# src/test/modules/test_rls_hooks/Makefile
+
+MODULE_big = test_rls_hooks
+OBJS = test_rls_hooks.o $(WIN32RES)
+PGFILEDESC = "test_rls_hooks - example use of RLS hooks"
+
+EXTENSION = test_rls_hooks
+# DATA = test_rls_hooks--1.0.sql
+
+REGRESS = test_rls_hooks
+REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_rls_hooks/rls_hooks.conf
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_rls_hooks
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
--- /dev/null
+test_rls_hooks is an example of how to use the hooks provided for RLS to
+define additional policies to be used.
+
+Functions
+=========
+test_rls_hook_permissive(CmdType cmdtype, Relation relation)
+ RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with OR.
+
+test_rls_hook_restrictive(CmdType cmdtype, Relation relation)
+ RETURNS List*
+
+Returns a list of policies which should be added to any existing
+policies on the relation, combined with AND.
--- /dev/null
+CREATE TABLE rls_test_permissive (
+ username name,
+ supervisor name,
+ data integer
+);
+-- initial test data
+INSERT INTO rls_test_permissive VALUES ('r1','s1',4);
+INSERT INTO rls_test_permissive VALUES ('r2','s2',5);
+INSERT INTO rls_test_permissive VALUES ('r3','s3',6);
+CREATE TABLE rls_test_restrictive (
+ username name,
+ supervisor name,
+ data integer
+);
+-- initial test data
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',1);
+INSERT INTO rls_test_restrictive VALUES ('r2','s2',2);
+INSERT INTO rls_test_restrictive VALUES ('r3','s3',3);
+CREATE TABLE rls_test_both (
+ username name,
+ supervisor name,
+ data integer
+);
+-- initial test data
+INSERT INTO rls_test_both VALUES ('r1','s1',7);
+INSERT INTO rls_test_both VALUES ('r2','s2',8);
+INSERT INTO rls_test_both VALUES ('r3','s3',9);
+ALTER TABLE rls_test_permissive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_restrictive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_both ENABLE ROW LEVEL SECURITY;
+CREATE ROLE r1;
+CREATE ROLE s1;
+GRANT SELECT,INSERT ON rls_test_permissive TO r1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO r1;
+GRANT SELECT,INSERT ON rls_test_both TO r1;
+GRANT SELECT,INSERT ON rls_test_permissive TO s1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO s1;
+GRANT SELECT,INSERT ON rls_test_both TO s1;
+SET ROLE r1;
+-- With only the hook's policies, permissive
+-- hook's policy is current_user = username
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on rls_test_permissive
+ Filter: ("current_user"() = username)
+(2 rows)
+
+SELECT * FROM rls_test_permissive;
+ username | supervisor | data
+----------+------------+------
+ r1 | s1 | 4
+(1 row)
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('r1','s1',10);
+-- failure
+INSERT INTO rls_test_permissive VALUES ('r4','s4',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_permissive"
+SET ROLE s1;
+-- With only the hook's policies, restrictive
+-- hook's policy is current_user = supervisor
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+ QUERY PLAN
+-------------------------------------------
+ Seq Scan on rls_test_restrictive
+ Filter: ("current_user"() = supervisor)
+(2 rows)
+
+SELECT * FROM rls_test_restrictive;
+ username | supervisor | data
+----------+------------+------
+ r1 | s1 | 1
+(1 row)
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',10);
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r4','s4',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive"
+SET ROLE s1;
+-- With only the hook's policies, both
+-- permissive hook's policy is current_user = username
+-- restrictive hook's policy is current_user = superuser
+-- combined with AND, results in nothing being allowed
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+ QUERY PLAN
+-------------------------------------------------------
+ Subquery Scan on rls_test_both
+ Filter: ("current_user"() = rls_test_both.username)
+ -> Seq Scan on rls_test_both rls_test_both_1
+ Filter: ("current_user"() = supervisor)
+(4 rows)
+
+SELECT * FROM rls_test_both;
+ username | supervisor | data
+----------+------------+------
+(0 rows)
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r1','s1',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s1',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s4',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
+RESET ROLE;
+-- Create "internal" policies, to check that the policies from
+-- the hooks are combined correctly.
+CREATE POLICY p1 ON rls_test_permissive USING (data % 2 = 0);
+CREATE POLICY p1 ON rls_test_restrictive USING (data % 2 = 0);
+CREATE POLICY p1 ON rls_test_both USING (data % 2 = 0);
+SET ROLE r1;
+-- With both internal and hook policies, permissive
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+ QUERY PLAN
+---------------------------------------------------------------
+ Seq Scan on rls_test_permissive
+ Filter: (("current_user"() = username) OR ((data % 2) = 0))
+(2 rows)
+
+SELECT * FROM rls_test_permissive;
+ username | supervisor | data
+----------+------------+------
+ r1 | s1 | 4
+ r3 | s3 | 6
+ r1 | s1 | 10
+(3 rows)
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('r1','s1',7);
+-- success
+INSERT INTO rls_test_permissive VALUES ('r3','s3',10);
+-- failure
+INSERT INTO rls_test_permissive VALUES ('r4','s4',7);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_permissive"
+SET ROLE s1;
+-- With both internal and hook policies, restrictive
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+ QUERY PLAN
+---------------------------------------------------------------
+ Subquery Scan on rls_test_restrictive
+ Filter: ((rls_test_restrictive.data % 2) = 0)
+ -> Seq Scan on rls_test_restrictive rls_test_restrictive_1
+ Filter: ("current_user"() = supervisor)
+(4 rows)
+
+SELECT * FROM rls_test_restrictive;
+ username | supervisor | data
+----------+------------+------
+ r1 | s1 | 10
+(1 row)
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',8);
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r3','s3',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive"
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',7);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive"
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r4','s4',7);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_restrictive"
+-- With both internal and hook policies, both permissive
+-- and restrictive hook policies
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ Subquery Scan on rls_test_both
+ Filter: (("current_user"() = rls_test_both.username) OR ((rls_test_both.data % 2) = 0))
+ -> Seq Scan on rls_test_both rls_test_both_1
+ Filter: ("current_user"() = supervisor)
+(4 rows)
+
+SELECT * FROM rls_test_both;
+ username | supervisor | data
+----------+------------+------
+(0 rows)
+
+-- success
+INSERT INTO rls_test_both VALUES ('r1','s1',8);
+-- failure
+INSERT INTO rls_test_both VALUES ('r3','s3',10);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('r1','s1',7);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s4',7);
+ERROR: new row violates WITH CHECK OPTION for "rls_test_both"
--- /dev/null
+shared_preload_libraries = test_rls_hooks
--- /dev/null
+CREATE TABLE rls_test_permissive (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- initial test data
+INSERT INTO rls_test_permissive VALUES ('r1','s1',4);
+INSERT INTO rls_test_permissive VALUES ('r2','s2',5);
+INSERT INTO rls_test_permissive VALUES ('r3','s3',6);
+
+CREATE TABLE rls_test_restrictive (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- initial test data
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',1);
+INSERT INTO rls_test_restrictive VALUES ('r2','s2',2);
+INSERT INTO rls_test_restrictive VALUES ('r3','s3',3);
+
+CREATE TABLE rls_test_both (
+ username name,
+ supervisor name,
+ data integer
+);
+
+-- initial test data
+INSERT INTO rls_test_both VALUES ('r1','s1',7);
+INSERT INTO rls_test_both VALUES ('r2','s2',8);
+INSERT INTO rls_test_both VALUES ('r3','s3',9);
+
+ALTER TABLE rls_test_permissive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_restrictive ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_test_both ENABLE ROW LEVEL SECURITY;
+
+CREATE ROLE r1;
+CREATE ROLE s1;
+
+GRANT SELECT,INSERT ON rls_test_permissive TO r1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO r1;
+GRANT SELECT,INSERT ON rls_test_both TO r1;
+
+GRANT SELECT,INSERT ON rls_test_permissive TO s1;
+GRANT SELECT,INSERT ON rls_test_restrictive TO s1;
+GRANT SELECT,INSERT ON rls_test_both TO s1;
+
+SET ROLE r1;
+
+-- With only the hook's policies, permissive
+-- hook's policy is current_user = username
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+
+SELECT * FROM rls_test_permissive;
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('r1','s1',10);
+
+-- failure
+INSERT INTO rls_test_permissive VALUES ('r4','s4',10);
+
+SET ROLE s1;
+
+-- With only the hook's policies, restrictive
+-- hook's policy is current_user = supervisor
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+
+SELECT * FROM rls_test_restrictive;
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',10);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r4','s4',10);
+
+SET ROLE s1;
+
+-- With only the hook's policies, both
+-- permissive hook's policy is current_user = username
+-- restrictive hook's policy is current_user = superuser
+-- combined with AND, results in nothing being allowed
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+
+SELECT * FROM rls_test_both;
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r1','s1',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s1',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s4',10);
+
+RESET ROLE;
+
+-- Create "internal" policies, to check that the policies from
+-- the hooks are combined correctly.
+CREATE POLICY p1 ON rls_test_permissive USING (data % 2 = 0);
+
+CREATE POLICY p1 ON rls_test_restrictive USING (data % 2 = 0);
+
+CREATE POLICY p1 ON rls_test_both USING (data % 2 = 0);
+
+SET ROLE r1;
+
+-- With both internal and hook policies, permissive
+EXPLAIN (costs off) SELECT * FROM rls_test_permissive;
+
+SELECT * FROM rls_test_permissive;
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('r1','s1',7);
+
+-- success
+INSERT INTO rls_test_permissive VALUES ('r3','s3',10);
+
+-- failure
+INSERT INTO rls_test_permissive VALUES ('r4','s4',7);
+
+SET ROLE s1;
+
+-- With both internal and hook policies, restrictive
+EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
+
+SELECT * FROM rls_test_restrictive;
+
+-- success
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',8);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r3','s3',10);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r1','s1',7);
+
+-- failure
+INSERT INTO rls_test_restrictive VALUES ('r4','s4',7);
+
+-- With both internal and hook policies, both permissive
+-- and restrictive hook policies
+EXPLAIN (costs off) SELECT * FROM rls_test_both;
+
+SELECT * FROM rls_test_both;
+
+-- success
+INSERT INTO rls_test_both VALUES ('r1','s1',8);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r3','s3',10);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r1','s1',7);
+
+-- failure
+INSERT INTO rls_test_both VALUES ('r4','s4',7);
--- /dev/null
+/*--------------------------------------------------------------------------
+ *
+ * test.c
+ * Test harness code for shared memory message queues.
+ *
+ * Copyright (C) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rls_hooks/test_rls_hooks.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "miscadmin.h"
+
+#include "test_rls_hooks.h"
+
+#include <catalog/pg_type.h>
+#include <nodes/makefuncs.h>
+#include <nodes/makefuncs.h>
+#include <parser/parse_clause.h>
+#include <parser/parse_node.h>
+#include <parser/parse_relation.h>
+#include <rewrite/rowsecurity.h>
+#include <utils/acl.h>
+#include <utils/rel.h>
+#include <utils/relcache.h>
+
+PG_MODULE_MAGIC;
+
+/* Saved hook values in case of unload */
+static row_security_policy_hook_type prev_row_security_policy_hook_permissive = NULL;
+static row_security_policy_hook_type prev_row_security_policy_hook_restrictive = NULL;
+
+void _PG_init(void);
+void _PG_fini(void);
+
+/* Install hooks */
+void _PG_init(void)
+{
+ /* Save values for unload */
+ prev_row_security_policy_hook_permissive = row_security_policy_hook_permissive;
+ prev_row_security_policy_hook_restrictive = row_security_policy_hook_restrictive;
+
+ /* Set our hooks */
+ row_security_policy_hook_permissive = test_rls_hooks_permissive;
+ row_security_policy_hook_restrictive = test_rls_hooks_restrictive;
+}
+
+/* Uninstall hooks */
+void _PG_fini(void)
+{
+ row_security_policy_hook_permissive = prev_row_security_policy_hook_permissive;
+ row_security_policy_hook_restrictive = prev_row_security_policy_hook_restrictive;
+}
+
+/*
+ * Return permissive policies to be added
+ */
+List*
+test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
+{
+ List *policies = NIL;
+ RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
+ Datum role;
+ FuncCall *n;
+ Node *e;
+ ColumnRef *c;
+ ParseState *qual_pstate;
+ RangeTblEntry *rte;
+
+ if (strcmp(RelationGetRelationName(relation),"rls_test_permissive")
+ && strcmp(RelationGetRelationName(relation),"rls_test_both"))
+ return NIL;
+
+ qual_pstate = make_parsestate(NULL);
+
+ rte = addRangeTableEntryForRelation(qual_pstate, relation, NULL, false,
+ false);
+ addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+ role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ policy->policy_name = pstrdup("extension policy");
+ policy->policy_id = InvalidOid;
+ policy->polcmd = '*';
+ policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, 'i');
+ /*
+ policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
+ sizeof(bool), BoolGetDatum(true),
+ false, true);
+ */
+
+ n = makeFuncCall(list_make2(makeString("pg_catalog"),
+ makeString("current_user")), NIL, 0);
+
+ c = makeNode(ColumnRef);
+ c->fields = list_make1(makeString("username"));
+ c->location = 0;
+
+ e = (Node*) makeSimpleA_Expr(AEXPR_OP, "=", (Node*) n, (Node*) c, 0);
+
+ policy->qual = (Expr*) transformWhereClause(qual_pstate, copyObject(e),
+ EXPR_KIND_WHERE,
+ "POLICY");
+
+ policy->with_check_qual = copyObject(policy->qual);
+ policy->hassublinks = false;
+
+ policies = list_make1(policy);
+
+ return policies;
+}
+
+/*
+ * Return restrictive policies to be added
+ */
+List*
+test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
+{
+ List *policies = NIL;
+ RowSecurityPolicy *policy = palloc0(sizeof(RowSecurityPolicy));
+ Datum role;
+ FuncCall *n;
+ Node *e;
+ ColumnRef *c;
+ ParseState *qual_pstate;
+ RangeTblEntry *rte;
+
+
+ if (strcmp(RelationGetRelationName(relation),"rls_test_restrictive")
+ && strcmp(RelationGetRelationName(relation),"rls_test_both"))
+ return NIL;
+
+ qual_pstate = make_parsestate(NULL);
+
+ rte = addRangeTableEntryForRelation(qual_pstate, relation, NULL, false,
+ false);
+ addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+ role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+ policy->policy_name = pstrdup("extension policy");
+ policy->policy_id = InvalidOid;
+ policy->polcmd = '*';
+ policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true, 'i');
+
+ n = makeFuncCall(list_make2(makeString("pg_catalog"),
+ makeString("current_user")), NIL, 0);
+
+ c = makeNode(ColumnRef);
+ c->fields = list_make1(makeString("supervisor"));
+ c->location = 0;
+
+ e = (Node*) makeSimpleA_Expr(AEXPR_OP, "=", (Node*) n, (Node*) c, 0);
+
+ policy->qual = (Expr*) transformWhereClause(qual_pstate, copyObject(e),
+ EXPR_KIND_WHERE,
+ "POLICY");
+
+ policy->with_check_qual = copyObject(policy->qual);
+ policy->hassublinks = false;
+
+ policies = list_make1(policy);
+
+ return policies;
+}
--- /dev/null
+comment = 'Test code for RLS hooks'
+default_version = '1.0'
+module_pathname = '$libdir/test_rls_hooks'
+relocatable = true
--- /dev/null
+/*--------------------------------------------------------------------------
+ *
+ * test_rls_hooks.h
+ * Definitions for RLS hooks
+ *
+ * Copyright (C) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_rls_hooks/test_rls_hooks.h
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#ifndef TEST_RLS_HOOKS_H
+#define TEST_RLS_HOOKS_H
+
+#include <rewrite/rowsecurity.h>
+
+/* Return set of permissive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_permissive(CmdType cmdtype, Relation relation);
+
+/* Return set of restrictive hooks based on CmdType and Relation */
+extern List *test_rls_hooks_restrictive(CmdType cmdtype, Relation relation);
+
+#endif
GRANT ALL ON t1 TO public;
COPY t1 FROM stdin WITH (oids);
CREATE TABLE t2 (c float) INHERITS (t1);
+GRANT ALL ON t2 TO public;
COPY t2 FROM stdin WITH (oids);
CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
ALTER TABLE t3 INHERIT t1;
+GRANT ALL ON t3 TO public;
COPY t3(a,b,c) FROM stdin WITH (oids);
CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
302 | 2 | yyyyyy | (2,yyyyyy)
(5 rows)
+-- updates with from clause
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on t2 t2_1
+ -> Nested Loop
+ -> Subquery Scan on t2
+ Filter: f_leak(t2.b)
+ -> LockRows
+ -> Seq Scan on t2 t2_2
+ Filter: ((a = 3) AND ((a % 2) = 1))
+ -> Seq Scan on t3
+ Filter: (f_leak(b) AND (a = 2))
+(9 rows)
+
+UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+NOTICE: f_leak => cde
+NOTICE: f_leak => xxx
+NOTICE: f_leak => zzz
+NOTICE: f_leak => yyyyyy
+EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on t1 t1_3
+ Update on t1 t1_3
+ Update on t2 t1
+ Update on t3 t1
+ -> Nested Loop
+ -> Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> LockRows
+ -> Seq Scan on t1 t1_4
+ Filter: ((a = 3) AND ((a % 2) = 0))
+ -> Subquery Scan on t2
+ Filter: f_leak(t2.b)
+ -> Seq Scan on t2 t2_3
+ Filter: ((a = 3) AND ((a % 2) = 1))
+ -> Nested Loop
+ -> Subquery Scan on t1_1
+ Filter: f_leak(t1_1.b)
+ -> LockRows
+ -> Seq Scan on t2 t2_4
+ Filter: ((a = 3) AND ((a % 2) = 0))
+ -> Subquery Scan on t2_1
+ Filter: f_leak(t2_1.b)
+ -> Seq Scan on t2 t2_5
+ Filter: ((a = 3) AND ((a % 2) = 1))
+ -> Nested Loop
+ -> Subquery Scan on t1_2
+ Filter: f_leak(t1_2.b)
+ -> LockRows
+ -> Seq Scan on t3
+ Filter: ((a = 3) AND ((a % 2) = 0))
+ -> Subquery Scan on t2_2
+ Filter: f_leak(t2_2.b)
+ -> Seq Scan on t2 t2_6
+ Filter: ((a = 3) AND ((a % 2) = 1))
+(34 rows)
+
+UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ QUERY PLAN
+---------------------------------------------------------------------
+ Update on t2 t2_1
+ -> Nested Loop
+ -> Subquery Scan on t2
+ Filter: f_leak(t2.b)
+ -> LockRows
+ -> Seq Scan on t2 t2_2
+ Filter: ((a = 3) AND ((a % 2) = 1))
+ -> Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> Result
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t2_3
+ Filter: ((a = 3) AND ((a % 2) = 0))
+ -> Seq Scan on t3
+ Filter: ((a = 3) AND ((a % 2) = 0))
+(17 rows)
+
+UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE: f_leak => cde
+-- updates with from clause self join
+EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on t2 t2_1_1
+ -> Nested Loop
+ Join Filter: (t2_1.b = t2_2.b)
+ -> Subquery Scan on t2_1
+ Filter: f_leak(t2_1.b)
+ -> LockRows
+ -> Seq Scan on t2 t2_1_2
+ Filter: ((a = 3) AND ((a % 2) = 1))
+ -> Subquery Scan on t2_2
+ Filter: f_leak(t2_2.b)
+ -> Seq Scan on t2 t2_2_1
+ Filter: ((a = 3) AND ((a % 2) = 1))
+(12 rows)
+
+UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+NOTICE: f_leak => cde
+NOTICE: f_leak => cde
+ a | b | c | a | b | c | t2_1 | t2_2
+---+-----+-----+---+-----+-----+-------------+-------------
+ 3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)
+(1 row)
+
+EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+ QUERY PLAN
+---------------------------------------------------------------
+ Update on t1 t1_1_3
+ Update on t1 t1_1_3
+ Update on t2 t1_1
+ Update on t3 t1_1
+ -> Nested Loop
+ Join Filter: (t1_1.b = t1_2.b)
+ -> Subquery Scan on t1_1
+ Filter: f_leak(t1_1.b)
+ -> LockRows
+ -> Seq Scan on t1 t1_1_4
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Subquery Scan on t1_2
+ Filter: f_leak(t1_2.b)
+ -> Append
+ -> Seq Scan on t1 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2_4
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_2_5
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Nested Loop
+ Join Filter: (t1_1_1.b = t1_2_1.b)
+ -> Subquery Scan on t1_1_1
+ Filter: f_leak(t1_1_1.b)
+ -> LockRows
+ -> Seq Scan on t2 t1_1_5
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Subquery Scan on t1_2_1
+ Filter: f_leak(t1_2_1.b)
+ -> Append
+ -> Seq Scan on t1 t1_2_6
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2_7
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_2_8
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Nested Loop
+ Join Filter: (t1_1_2.b = t1_2_2.b)
+ -> Subquery Scan on t1_1_2
+ Filter: f_leak(t1_1_2.b)
+ -> LockRows
+ -> Seq Scan on t3 t1_1_6
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Subquery Scan on t1_2_2
+ Filter: f_leak(t1_2_2.b)
+ -> Append
+ -> Seq Scan on t1 t1_2_9
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2_10
+ Filter: ((a = 4) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_2_11
+ Filter: ((a = 4) AND ((a % 2) = 0))
+(52 rows)
+
+UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+NOTICE: f_leak => dddddd_updt
+NOTICE: f_leak => dddddd_updt
+NOTICE: f_leak => defdef
+NOTICE: f_leak => defdef
+NOTICE: f_leak => dddddd_updt
+NOTICE: f_leak => defdef
+ a | b | a | b | t1_1 | t1_2
+---+-------------+---+-------------+-----------------+-----------------
+ 4 | dddddd_updt | 4 | dddddd_updt | (4,dddddd_updt) | (4,dddddd_updt)
+ 4 | defdef | 4 | defdef | (4,defdef) | (4,defdef)
+(2 rows)
+
RESET SESSION AUTHORIZATION;
SET row_security TO OFF;
-SELECT * FROM t1;
+SELECT * FROM t1 ORDER BY a,b;
a | b
---+-------------
1 | aaa
- 3 | ccc
- 2 | bbbbbb_updt
- 4 | dddddd_updt
1 | abc
- 3 | cde
- 2 | bcdbcd
- 4 | defdef
1 | xxx
- 3 | zzz
+ 2 | bbbbbb_updt
+ 2 | bcdbcd
2 | yyyyyy
+ 3 | ccc
+ 3 | cde
+ 3 | zzz
+ 4 | dddddd_updt
+ 4 | defdef
(11 rows)
SET SESSION AUTHORIZATION rls_regress_user1;
302 | 2 | yyyyyy | (2,yyyyyy)
(3 rows)
+--
+-- S.b. view on top of Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE b1 (a int, b text);
+INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+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);
+ QUERY PLAN
+---------------------------------------------
+ Subquery Scan on bv1
+ Filter: f_leak(bv1.b)
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND ((a % 2) = 0))
+(4 rows)
+
+SELECT * FROM bv1 WHERE f_leak(b);
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+ a | b
+----+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+(5 rows)
+
+INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+ERROR: new row violates WITH CHECK OPTION for "b1"
+INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+ERROR: new row violates WITH CHECK OPTION for "b1"
+INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Update on b1 b1_1
+ -> Subquery Scan on b1
+ Filter: f_leak(b1.b)
+ -> Subquery Scan on b1_2
+ -> LockRows
+ -> Seq Scan on b1 b1_3
+ Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0))
+(7 rows)
+
+UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Delete on b1 b1_1
+ -> Subquery Scan on b1
+ Filter: f_leak(b1.b)
+ -> Subquery Scan on b1_2
+ -> LockRows
+ -> Seq Scan on b1 b1_3
+ Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0))
+(7 rows)
+
+DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM b1;
+ a | b
+-----+----------------------------------
+ -10 | 1b0fd9efa5279c4203b7c70233f86dbf
+ -9 | 252e691406782824eec43d7eadc3d256
+ -8 | a8d2ec85eaf98407310b72eb73dda247
+ -7 | 74687a12d3915d3c4d83f1af7b3683d5
+ -6 | 596a3d04481816330f07e4f97510c28f
+ -5 | 47c1b025fa18ea96c33fbb6718688c0f
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+ -3 | b3149ecea4628efd23d2f86e5a723472
+ -2 | 5d7b9adcbe1c629ec722529dd12e5129
+ -1 | 6bb61e3b7bce0931da574d19d1d82c88
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 1 | c4ca4238a0b923820dcc509a6f75849b
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 5 | e4da3b7fbbce2345d7772b0674a318d5
+ 7 | 8f14e45fceea167a5a36dedd4bea2543
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | xxx
+ 4 | yyy
+(21 rows)
+
--
-- ROLE/GROUP
--
\.
CREATE TABLE t2 (c float) INHERITS (t1);
+GRANT ALL ON t2 TO public;
+
COPY t2 FROM stdin WITH (oids);
201 1 abc 1.1
202 2 bcd 2.2
CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
ALTER TABLE t3 INHERIT t1;
+GRANT ALL ON t3 TO public;
+
COPY t3(a,b,c) FROM stdin WITH (oids);
301 1 xxx X
302 2 yyy Y
UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+-- updates with from clause
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+
+UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+
+EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+
+UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+
+UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+
+-- updates with from clause self join
+EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+
+UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+
+EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+
+UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+
RESET SESSION AUTHORIZATION;
SET row_security TO OFF;
-SELECT * FROM t1;
+SELECT * FROM t1 ORDER BY a,b;
SET SESSION AUTHORIZATION rls_regress_user1;
SET row_security TO ON;
DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
+--
+-- S.b. view on top of Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE b1 (a int, b text);
+INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+
+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);
+SELECT * FROM bv1 WHERE f_leak(b);
+
+INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+
+EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+
+EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM b1;
+
--
-- ROLE/GROUP
--