]> granicus.if.org Git - postgresql/commitdiff
RLS fixes, new hooks, and new test module
authorStephen Frost <sfrost@snowman.net>
Wed, 22 Apr 2015 16:01:06 +0000 (12:01 -0400)
committerStephen Frost <sfrost@snowman.net>
Wed, 22 Apr 2015 16:01:06 +0000 (12:01 -0400)
In prepend_row_security_policies(), defaultDeny was always true, so if
there were any hook policies, the RLS policies on the table would just
get discarded.  Fixed to start off with defaultDeny as false and then
properly set later if we detect that only the default deny policy exists
for the internal policies.

The infinite recursion detection in fireRIRrules() didn't properly
manage the activeRIRs list in the case of WCOs, so it would incorrectly
report infinite recusion if the same relation with RLS appeared more
than once in the rtable, for example "UPDATE t ... FROM t ...".

Further, the RLS expansion code in fireRIRrules() was handling RLS in
the main loop through the rtable, which lead to RTEs being visited twice
if they contained sublink subqueries, which
prepend_row_security_policies() attempted to handle by exiting early if
the RTE already had securityQuals.  That doesn't work, however, since
if the query involved a security barrier view on top of a table with
RLS, the RTE would already have securityQuals (from the view) by the
time fireRIRrules() was invoked, and so the table's RLS policies would
be ignored.  This is fixed in fireRIRrules() by handling RLS in a
separate loop at the end, after dealing with any other sublink
subqueries, thus ensuring that each RTE is only visited once for RLS
expansion.

The inheritance planner code didn't correctly handle non-target
relations with RLS, which would get turned into subqueries during
planning. Thus an update of the form "UPDATE t1 ... FROM t2 ..." where
t1 has inheritance and t2 has RLS quals would fail.  Fix by making sure
to copy in and update the securityQuals when they exist for non-target
relations.

process_policies() was adding WCOs to non-target relations, which is
unnecessary, and could lead to a lot of wasted time in the rewriter and
the planner. Fix by only adding WCO policies when working on the result
relation.  Also in process_policies, we should be copying the USING
policies to the WITH CHECK policies on a per-policy basis, fix by moving
the copying up into the per-policy loop.

Lastly, as noted by Dean, we were simply adding policies returned by the
hook provided to the list of quals being AND'd, meaning that they would
actually restrict records returned and there was no option to have
internal policies and hook-based policies work together permissively (as
all internal policies currently work).  Instead, explicitly add support
for both permissive and restrictive policies by having a hook for each
and combining the results appropriately.  To ensure this is all done
correctly, add a new test module (test_rls_hooks) to test the various
combinations of internal, permissive, and restrictive hook policies.

Largely from Dean Rasheed (thanks!):

CAEZATCVmFUfUOwwhnBTcgi6AquyjQ0-1fyKd0T3xBWJvn+xsFA@mail.gmail.com

Author: Dean Rasheed, though I added the new hooks and test module.

16 files changed:
src/backend/optimizer/plan/planner.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rowsecurity.c
src/include/rewrite/rowsecurity.h
src/test/modules/Makefile
src/test/modules/test_rls_hooks/.gitignore [new file with mode: 0644]
src/test/modules/test_rls_hooks/Makefile [new file with mode: 0644]
src/test/modules/test_rls_hooks/README [new file with mode: 0644]
src/test/modules/test_rls_hooks/expected/test_rls_hooks.out [new file with mode: 0644]
src/test/modules/test_rls_hooks/rls_hooks.conf [new file with mode: 0644]
src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql [new file with mode: 0644]
src/test/modules/test_rls_hooks/test_rls_hooks.c [new file with mode: 0644]
src/test/modules/test_rls_hooks/test_rls_hooks.control [new file with mode: 0644]
src/test/modules/test_rls_hooks/test_rls_hooks.h [new file with mode: 0644]
src/test/regress/expected/rowsecurity.out
src/test/regress/sql/rowsecurity.sql

index 876a87ff52a6cf757d46679eae31c20069a66496..ea4d4c55cbd2c90683eb16c284e42a73952064d7 100644 (file)
@@ -790,6 +790,7 @@ inheritance_planner(PlannerInfo *root)
 {
        Query      *parse = root->parse;
        int                     parentRTindex = parse->resultRelation;
+       Bitmapset  *resultRTindexes = NULL;
        int                     nominalRelation = -1;
        List       *final_rtable = NIL;
        int                     save_rel_array_size = 0;
@@ -815,7 +816,21 @@ inheritance_planner(PlannerInfo *root)
         * (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);
@@ -886,21 +901,29 @@ inheritance_planner(PlannerInfo *root)
                        {
                                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);
                                }
@@ -2283,7 +2306,19 @@ select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength)
                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:
index 9d2c280b32899a92ba905ac005bb8403b44b5107..08ec13ccdd03a03e342936a3618298f6c1ac88a7 100644 (file)
@@ -1714,51 +1714,6 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
                                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);
        }
@@ -1780,6 +1735,88 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
                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;
 }
 
index 7669130e2b67592a5c270432c919f349ed1172ef..bad166ac3ad1af202ff1db28b5206de8dedff27b 100644 (file)
 
 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);
@@ -113,14 +130,14 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
        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
@@ -134,18 +151,11 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
                 * 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);
 
@@ -167,20 +177,17 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
                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
@@ -192,23 +199,42 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
         * 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
@@ -222,29 +248,65 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
                 * 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);
                }
        }
 
@@ -253,12 +315,29 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
                || 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);
@@ -267,17 +346,9 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
         * 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;
 }
 
 /*
@@ -292,7 +363,6 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
 {
        List                       *policies = NIL;
        ListCell                   *item;
-       RowSecurityPolicy  *policy;
 
        /*
         * Row security is enabled for the relation and the row security GUC is
@@ -302,7 +372,7 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
         */
        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 == '*' &&
@@ -383,8 +453,9 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
  * 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;
@@ -392,7 +463,8 @@ process_policies(List *policies, int rt_index, Expr **qual_eval,
 
        /*
         * 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)
        {
@@ -401,10 +473,22 @@ process_policies(List *policies, int rt_index, Expr **qual_eval,
                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;
        }
@@ -417,13 +501,6 @@ process_policies(List *policies, int rt_index, Expr **qual_eval,
                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
@@ -431,27 +508,40 @@ process_policies(List *policies, int rt_index, Expr **qual_eval,
         * 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;
 }
index 8d19bfdf4ae0dfb7e1b942d518411afb7b66092a..115c9a8e430ec584eac453fada1cfcabb4050575 100644 (file)
@@ -37,9 +37,12 @@ typedef struct RowSecurityDesc
 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 */
index 93d93af2cdb0e6422e4f52ab5757d0d45d947350..730fa75a35e30ddfb0d1e9b7d3f9f67253bcb117 100644 (file)
@@ -8,6 +8,7 @@ SUBDIRS = \
                  commit_ts \
                  worker_spi \
                  dummy_seclabel \
+                 test_rls_hooks \
                  test_shm_mq \
                  test_parser
 
diff --git a/src/test/modules/test_rls_hooks/.gitignore b/src/test/modules/test_rls_hooks/.gitignore
new file mode 100644 (file)
index 0000000..5dcb3ff
--- /dev/null
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_rls_hooks/Makefile b/src/test/modules/test_rls_hooks/Makefile
new file mode 100644 (file)
index 0000000..6b772c4
--- /dev/null
@@ -0,0 +1,22 @@
+# 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
diff --git a/src/test/modules/test_rls_hooks/README b/src/test/modules/test_rls_hooks/README
new file mode 100644 (file)
index 0000000..b61dace
--- /dev/null
@@ -0,0 +1,16 @@
+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.
diff --git a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
new file mode 100644 (file)
index 0000000..d55aea7
--- /dev/null
@@ -0,0 +1,193 @@
+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"
diff --git a/src/test/modules/test_rls_hooks/rls_hooks.conf b/src/test/modules/test_rls_hooks/rls_hooks.conf
new file mode 100644 (file)
index 0000000..a522c0e
--- /dev/null
@@ -0,0 +1 @@
+shared_preload_libraries = test_rls_hooks
diff --git a/src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql b/src/test/modules/test_rls_hooks/sql/test_rls_hooks.sql
new file mode 100644 (file)
index 0000000..be13ab4
--- /dev/null
@@ -0,0 +1,157 @@
+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);
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.c b/src/test/modules/test_rls_hooks/test_rls_hooks.c
new file mode 100644 (file)
index 0000000..f2f87c3
--- /dev/null
@@ -0,0 +1,170 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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;
+}
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.control b/src/test/modules/test_rls_hooks/test_rls_hooks.control
new file mode 100644 (file)
index 0000000..9f9f13f
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test code for RLS hooks'
+default_version = '1.0'
+module_pathname = '$libdir/test_rls_hooks'
+relocatable = true
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.h b/src/test/modules/test_rls_hooks/test_rls_hooks.h
new file mode 100644 (file)
index 0000000..c8a0330
--- /dev/null
@@ -0,0 +1,25 @@
+/*--------------------------------------------------------------------------
+ *
+ * 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
index 44e8dab44c27f9ff130be8d5f7d89898b79012b4..56760796edd8558687b640afdddc94fc190f1933 100644 (file)
@@ -466,9 +466,11 @@ ALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor
 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
@@ -1117,22 +1119,216 @@ NOTICE:  f_leak => yyyyyy
  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;
@@ -1192,6 +1388,103 @@ NOTICE:  f_leak => yyyyyy
  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
 --
index ed7adbf8d1325e35236058c373d1bdeb61ad55bd..4da5035f5be8d484fae64a4cf2b9279217cee2e7 100644 (file)
@@ -207,6 +207,8 @@ 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);
 201    1       abc     1.1
 202    2       bcd     2.2
@@ -216,6 +218,8 @@ 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);
 301    1       xxx     X
 302    2       yyy     Y
@@ -423,9 +427,45 @@ UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
 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;
@@ -435,6 +475,39 @@ EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
 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
 --