diff options
Diffstat (limited to 'src/backend/rewrite/rowsecurity.c')
-rw-r--r-- | src/backend/rewrite/rowsecurity.c | 260 |
1 files changed, 175 insertions, 85 deletions
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index 7669130e2b6..bad166ac3ad 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -58,46 +58,63 @@ 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; } @@ -418,40 +502,46 @@ process_policies(List *policies, int rt_index, Expr **qual_eval, 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 * expression, updating any references to varno 1 to the varno * 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; } |