mirror of
https://github.com/postgres/postgres.git
synced 2025-05-12 16:21:30 +03:00
Fix some places where pgindent did silly stuff, often because project style wasn't followed to begin with. (I've not touched the atomics headers, though.)
637 lines
21 KiB
C
637 lines
21 KiB
C
/*
|
|
* rewrite/rowsecurity.c
|
|
* Routines to support policies for row level security (aka RLS).
|
|
*
|
|
* Policies in PostgreSQL provide a mechanism to limit what records are
|
|
* returned to a user and what records a user is permitted to add to a table.
|
|
*
|
|
* Policies can be defined for specific roles, specific commands, or provided
|
|
* by an extension. Row security can also be enabled for a table without any
|
|
* policies being explicitly defined, in which case a default-deny policy is
|
|
* applied.
|
|
*
|
|
* Any part of the system which is returning records back to the user, or
|
|
* which is accepting records from the user to add to a table, needs to
|
|
* consider the policies associated with the table (if any). For normal
|
|
* queries, this is handled by calling prepend_row_security_policies() during
|
|
* rewrite, which looks at each RTE and adds the expressions defined by the
|
|
* policies to the securityQuals list for the RTE. For queries which modify
|
|
* the relation, any WITH CHECK policies are added to the list of
|
|
* WithCheckOptions for the Query and checked against each row which is being
|
|
* added to the table. Other parts of the system (eg: COPY) simply construct
|
|
* a normal query and use that, if RLS is to be applied.
|
|
*
|
|
* The check to see if RLS should be enabled is provided through
|
|
* check_enable_rls(), which returns an enum (defined in rowsecurity.h) to
|
|
* indicate if RLS should be enabled (RLS_ENABLED), or bypassed (RLS_NONE or
|
|
* RLS_NONE_ENV). RLS_NONE_ENV indicates that RLS should be bypassed
|
|
* in the current environment, but that may change if the row_security GUC or
|
|
* the current role changes.
|
|
*
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/htup_details.h"
|
|
#include "access/sysattr.h"
|
|
#include "catalog/pg_class.h"
|
|
#include "catalog/pg_inherits_fn.h"
|
|
#include "catalog/pg_policy.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "nodes/plannodes.h"
|
|
#include "parser/parsetree.h"
|
|
#include "rewrite/rewriteHandler.h"
|
|
#include "rewrite/rewriteManip.h"
|
|
#include "rewrite/rowsecurity.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/rls.h"
|
|
#include "utils/syscache.h"
|
|
#include "tcop/utility.h"
|
|
|
|
static List *pull_row_security_policies(CmdType cmd, Relation relation,
|
|
Oid user_id);
|
|
static void process_policies(Query *root, List *policies, int rt_index,
|
|
Expr **final_qual,
|
|
Expr **final_with_check_qual,
|
|
bool *hassublinks,
|
|
BoolExprType boolop);
|
|
static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
|
|
|
|
/*
|
|
* 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_permissive = NULL;
|
|
row_security_policy_hook_type row_security_policy_hook_restrictive = NULL;
|
|
|
|
/*
|
|
* Get any row security quals and check quals that should be applied to the
|
|
* specified RTE.
|
|
*
|
|
* 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.
|
|
*/
|
|
void
|
|
get_row_security_policies(Query *root, CmdType commandType, 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_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_restrictive = NIL;
|
|
List *hook_policies_permissive = NIL;
|
|
|
|
Relation rel;
|
|
Oid user_id;
|
|
int sec_context;
|
|
int rls_status;
|
|
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);
|
|
|
|
/* Switch to checkAsUser if it's set */
|
|
user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
|
|
|
|
/*
|
|
* If this is not a normal relation, or we have been told to explicitly
|
|
* skip RLS (perhaps because this is an FK check) then just return
|
|
* immediately.
|
|
*/
|
|
if (rte->relid < FirstNormalObjectId
|
|
|| rte->relkind != RELKIND_RELATION
|
|
|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
|
|
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;
|
|
|
|
/*
|
|
* RLS_NONE_ENV means we are not doing any RLS now, but that may change
|
|
* with changes to the environment, so we mark it as hasRowSecurity to
|
|
* force a re-plan when the environment changes.
|
|
*/
|
|
if (rls_status == RLS_NONE_ENV)
|
|
{
|
|
/*
|
|
* Indicate that this query may involve RLS and must therefore be
|
|
* replanned if the environment changes (GUCs, role), but we are not
|
|
* adding anything here.
|
|
*/
|
|
*hasRowSecurity = true;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Grab the built-in policies which should be applied to this relation. */
|
|
rel = heap_open(rte->relid, NoLock);
|
|
|
|
rowsec_policies = pull_row_security_policies(commandType, rel,
|
|
user_id);
|
|
|
|
/*
|
|
* Check if this is only the default-deny policy.
|
|
*
|
|
* Normally, if the table has row security enabled but there are no
|
|
* policies, we use a default-deny policy and not allow anything. However,
|
|
* when an extension uses the hook to add their own policies, we don't
|
|
* want to include the default deny policy or there won't be any way for a
|
|
* user to use an extension exclusively for the policies to be used.
|
|
*/
|
|
if (((RowSecurityPolicy *) linitial(rowsec_policies))->policy_id
|
|
== InvalidOid)
|
|
defaultDeny = true;
|
|
|
|
/* Now that we have our policies, build the expressions from them. */
|
|
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 (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
|
|
* handled.
|
|
*
|
|
* The only caveat to this is that if there are NO internal policies
|
|
* defined, there ARE policies returned by the extension, and RLS is
|
|
* enabled on the table, then we will ignore the internally-generated
|
|
* default-deny policy and use only the policies returned by the
|
|
* extension.
|
|
*/
|
|
if (row_security_policy_hook_restrictive)
|
|
{
|
|
hook_policies_restrictive = (*row_security_policy_hook_restrictive) (commandType, rel);
|
|
|
|
/* Build the expression from any policies returned. */
|
|
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) (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, we will apply both sets below.
|
|
*/
|
|
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 Query's
|
|
* withCheckOptions to verify that any new records pass the WITH CHECK
|
|
* policy (this will be a copy of the USING policy, if no explicit WITH
|
|
* CHECK policy exists).
|
|
*/
|
|
if (commandType == CMD_INSERT || commandType == CMD_UPDATE)
|
|
{
|
|
/*
|
|
* WITH CHECK OPTIONS wants a WCO node which wraps each Expr, so
|
|
* create them as necessary.
|
|
*/
|
|
|
|
/*
|
|
* Handle any restrictive policies first.
|
|
*
|
|
* They can simply be added.
|
|
*/
|
|
if (hook_with_check_expr_restrictive)
|
|
{
|
|
WithCheckOption *wco;
|
|
|
|
wco = (WithCheckOption *) makeNode(WithCheckOption);
|
|
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
|
|
WCO_RLS_UPDATE_CHECK;
|
|
wco->relname = pstrdup(RelationGetRelationName(rel));
|
|
wco->qual = (Node *) hook_with_check_expr_restrictive;
|
|
wco->cascaded = false;
|
|
*withCheckOptions = lappend(*withCheckOptions, wco);
|
|
}
|
|
|
|
/*
|
|
* Handle built-in policies, if there are no permissive policies from
|
|
* the hook.
|
|
*/
|
|
if (rowsec_with_check_expr && !hook_with_check_expr_permissive)
|
|
{
|
|
WithCheckOption *wco;
|
|
|
|
wco = (WithCheckOption *) makeNode(WithCheckOption);
|
|
wco->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
|
|
WCO_RLS_UPDATE_CHECK;
|
|
wco->relname = pstrdup(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->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
|
|
WCO_RLS_UPDATE_CHECK;
|
|
wco->relname = pstrdup(RelationGetRelationName(rel));
|
|
wco->qual = (Node *) hook_with_check_expr_permissive;
|
|
wco->cascaded = false;
|
|
*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->kind = commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
|
|
WCO_RLS_UPDATE_CHECK;
|
|
wco->relname = pstrdup(RelationGetRelationName(rel));
|
|
wco->qual = (Node *) combined_qual_eval;
|
|
wco->cascaded = false;
|
|
*withCheckOptions = lappend(*withCheckOptions, wco);
|
|
}
|
|
|
|
/*
|
|
* ON CONFLICT DO UPDATE has an RTE that is subject to both INSERT and
|
|
* UPDATE RLS enforcement. Those are enforced (as a special, distinct
|
|
* kind of WCO) on the target tuple.
|
|
*
|
|
* Make a second, recursive pass over the RTE for this, gathering
|
|
* UPDATE-applicable RLS checks/WCOs, and gathering and converting
|
|
* UPDATE-applicable security quals into WCO_RLS_CONFLICT_CHECK RLS
|
|
* checks/WCOs. Finally, these distinct kinds of RLS checks/WCOs are
|
|
* concatenated with our own INSERT-applicable list.
|
|
*/
|
|
if (root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE &&
|
|
commandType == CMD_INSERT)
|
|
{
|
|
List *conflictSecurityQuals = NIL;
|
|
List *conflictWCOs = NIL;
|
|
ListCell *item;
|
|
bool conflictHasRowSecurity = false;
|
|
bool conflictHasSublinks = false;
|
|
|
|
/* Assume that RTE is target resultRelation */
|
|
get_row_security_policies(root, CMD_UPDATE, rte, rt_index,
|
|
&conflictSecurityQuals, &conflictWCOs,
|
|
&conflictHasRowSecurity,
|
|
&conflictHasSublinks);
|
|
|
|
if (conflictHasRowSecurity)
|
|
*hasRowSecurity = true;
|
|
if (conflictHasSublinks)
|
|
*hasSubLinks = true;
|
|
|
|
/*
|
|
* Append WITH CHECK OPTIONs/RLS checks, which should not conflict
|
|
* between this INSERT and the auxiliary UPDATE
|
|
*/
|
|
*withCheckOptions = list_concat(*withCheckOptions,
|
|
conflictWCOs);
|
|
|
|
foreach(item, conflictSecurityQuals)
|
|
{
|
|
Expr *conflict_rowsec_expr = (Expr *) lfirst(item);
|
|
WithCheckOption *wco;
|
|
|
|
wco = (WithCheckOption *) makeNode(WithCheckOption);
|
|
|
|
wco->kind = WCO_RLS_CONFLICT_CHECK;
|
|
wco->relname = pstrdup(RelationGetRelationName(rel));
|
|
wco->qual = (Node *) copyObject(conflict_rowsec_expr);
|
|
wco->cascaded = false;
|
|
*withCheckOptions = lappend(*withCheckOptions, wco);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* For SELECT, UPDATE, and DELETE, set the security quals */
|
|
if (commandType == CMD_SELECT
|
|
|| commandType == CMD_UPDATE
|
|
|| commandType == CMD_DELETE)
|
|
{
|
|
/* 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;
|
|
|
|
combined_quals = lcons(copyObject(rowsec_expr), combined_quals);
|
|
combined_quals = lcons(copyObject(hook_expr_permissive),
|
|
combined_quals);
|
|
|
|
combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
|
|
|
|
*securityQuals = lappend(*securityQuals, combined_qual_eval);
|
|
}
|
|
}
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
/*
|
|
* Mark this query as having row security, so plancache can invalidate it
|
|
* when necessary (eg: role changes)
|
|
*/
|
|
*hasRowSecurity = true;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* pull_row_security_policies
|
|
*
|
|
* Returns the list of policies to be added for this relation, based on the
|
|
* type of command and the roles to which it applies, from the relation cache.
|
|
*
|
|
*/
|
|
static List *
|
|
pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
|
|
{
|
|
List *policies = NIL;
|
|
ListCell *item;
|
|
|
|
/*
|
|
* Row security is enabled for the relation and the row security GUC is
|
|
* either 'on' or 'force' here, so find the policies to apply to the
|
|
* table. There must always be at least one policy defined (may be the
|
|
* simple 'default-deny' policy, if none are explicitly defined on the
|
|
* table).
|
|
*/
|
|
foreach(item, relation->rd_rsdesc->policies)
|
|
{
|
|
RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
|
|
|
|
/* Always add ALL policies, if they exist. */
|
|
if (policy->polcmd == '*' &&
|
|
check_role_for_policy(policy->roles, user_id))
|
|
policies = lcons(policy, policies);
|
|
|
|
/* Add relevant command-specific policies to the list. */
|
|
switch (cmd)
|
|
{
|
|
case CMD_SELECT:
|
|
if (policy->polcmd == ACL_SELECT_CHR
|
|
&& check_role_for_policy(policy->roles, user_id))
|
|
policies = lcons(policy, policies);
|
|
break;
|
|
case CMD_INSERT:
|
|
/* If INSERT then only need to add the WITH CHECK qual */
|
|
if (policy->polcmd == ACL_INSERT_CHR
|
|
&& check_role_for_policy(policy->roles, user_id))
|
|
policies = lcons(policy, policies);
|
|
break;
|
|
case CMD_UPDATE:
|
|
if (policy->polcmd == ACL_UPDATE_CHR
|
|
&& check_role_for_policy(policy->roles, user_id))
|
|
policies = lcons(policy, policies);
|
|
break;
|
|
case CMD_DELETE:
|
|
if (policy->polcmd == ACL_DELETE_CHR
|
|
&& check_role_for_policy(policy->roles, user_id))
|
|
policies = lcons(policy, policies);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized policy command type %d", (int) cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There should always be a policy applied. If there are none found then
|
|
* create a simply defauly-deny policy (might be that policies exist but
|
|
* that none of them apply to the role which is querying the table).
|
|
*/
|
|
if (policies == NIL)
|
|
{
|
|
RowSecurityPolicy *policy = NULL;
|
|
Datum role;
|
|
|
|
role = ObjectIdGetDatum(ACL_ID_PUBLIC);
|
|
|
|
policy = palloc0(sizeof(RowSecurityPolicy));
|
|
policy->policy_name = pstrdup("default-deny 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(false),
|
|
false, true);
|
|
policy->with_check_qual = copyObject(policy->qual);
|
|
policy->hassublinks = false;
|
|
|
|
policies = list_make1(policy);
|
|
}
|
|
|
|
Assert(policies != NIL);
|
|
|
|
return policies;
|
|
}
|
|
|
|
/*
|
|
* process_policies
|
|
*
|
|
* This will step through the policies which are passed in (which would come
|
|
* from either the built-in ones created on a table, or from policies provided
|
|
* by an extension through the hook provided), work out how to combine them,
|
|
* rewrite them as necessary, and produce an Expr for the normal security
|
|
* quals and an Expr for the with check quals.
|
|
*
|
|
* qual_eval, with_check_eval, and hassublinks are output variables
|
|
*/
|
|
static void
|
|
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;
|
|
List *with_check_quals = NIL;
|
|
|
|
/*
|
|
* Extract the USING and WITH CHECK quals from each of the policies and
|
|
* add them to our lists. We only want WITH CHECK quals if this RTE is
|
|
* the query's result relation.
|
|
*/
|
|
foreach(item, policies)
|
|
{
|
|
RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
|
|
|
|
if (policy->qual != NULL)
|
|
quals = lcons(copyObject(policy->qual), quals);
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* If we end up without any normal quals (perhaps the only policy matched
|
|
* was for INSERT), then create a single all-false one.
|
|
*/
|
|
if (quals == NIL)
|
|
quals = lcons(makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
|
|
BoolGetDatum(false), false, true), 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);
|
|
|
|
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
|
|
* combined together.
|
|
*/
|
|
if (list_length(quals) > 1)
|
|
*qual_eval = makeBoolExpr(boolop, quals, -1);
|
|
else
|
|
*qual_eval = (Expr *) linitial(quals);
|
|
|
|
/*
|
|
* Similarly, 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(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;
|
|
}
|
|
|
|
/*
|
|
* check_role_for_policy -
|
|
* determines if the policy should be applied for the current role
|
|
*/
|
|
static bool
|
|
check_role_for_policy(ArrayType *policy_roles, Oid user_id)
|
|
{
|
|
int i;
|
|
Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
|
|
|
|
/* Quick fall-thru for policies applied to all roles */
|
|
if (roles[0] == ACL_ID_PUBLIC)
|
|
return true;
|
|
|
|
for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
|
|
{
|
|
if (has_privs_of_role(user_id, roles[i]))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|