| /* |
| * 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 get_row_security_policies() during |
| * rewrite, for each RTE in the query. This returns the expressions defined |
| * by the table's policies as a list that is prepended to the securityQuals |
| * list for the RTE. For queries which modify the table, any WITH CHECK |
| * clauses from the table's policies are also returned and prepended to the |
| * list of WithCheckOptions for the Query to check each row that 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-2023, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup_details.h" |
| #include "access/sysattr.h" |
| #include "access/table.h" |
| #include "catalog/pg_class.h" |
| #include "catalog/pg_inherits.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 "parser/parse_relation.h" |
| #include "rewrite/rewriteDefine.h" |
| #include "rewrite/rewriteHandler.h" |
| #include "rewrite/rewriteManip.h" |
| #include "rewrite/rowsecurity.h" |
| #include "tcop/utility.h" |
| #include "utils/acl.h" |
| #include "utils/lsyscache.h" |
| #include "utils/rel.h" |
| #include "utils/rls.h" |
| #include "utils/syscache.h" |
| |
| static void get_policies_for_relation(Relation relation, |
| CmdType cmd, Oid user_id, |
| List **permissive_policies, |
| List **restrictive_policies); |
| |
| static void sort_policies_by_name(List *policies); |
| |
| static int row_security_policy_cmp(const ListCell *a, const ListCell *b); |
| |
| static void add_security_quals(int rt_index, |
| List *permissive_policies, |
| List *restrictive_policies, |
| List **securityQuals, |
| bool *hasSubLinks); |
| |
| static void add_with_check_options(Relation rel, |
| int rt_index, |
| WCOKind kind, |
| List *permissive_policies, |
| List *restrictive_policies, |
| List **withCheckOptions, |
| bool *hasSubLinks, |
| bool force_using); |
| |
| 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 combined with the other permissive policies, using OR. |
| * |
| * row_security_policy_hook_restrictive can be used to add policies which |
| * are enforced, regardless of other policies (they are combined using AND). |
| */ |
| 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 WithCheckOption checks 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, RangeTblEntry *rte, int rt_index, |
| List **securityQuals, List **withCheckOptions, |
| bool *hasRowSecurity, bool *hasSubLinks) |
| { |
| Oid user_id; |
| int rls_status; |
| Relation rel; |
| CmdType commandType; |
| List *permissive_policies; |
| List *restrictive_policies; |
| RTEPermissionInfo *perminfo; |
| |
| /* Defaults for the return values */ |
| *securityQuals = NIL; |
| *withCheckOptions = NIL; |
| *hasRowSecurity = false; |
| *hasSubLinks = false; |
| |
| Assert(rte->rtekind == RTE_RELATION); |
| |
| /* If this is not a normal relation, just return immediately */ |
| if (rte->relkind != RELKIND_RELATION && |
| rte->relkind != RELKIND_PARTITIONED_TABLE) |
| return; |
| |
| perminfo = getRTEPermissionInfo(root->rteperminfos, rte); |
| |
| /* Switch to checkAsUser if it's set */ |
| user_id = OidIsValid(perminfo->checkAsUser) ? |
| perminfo->checkAsUser : GetUserId(); |
| |
| /* Determine the state of RLS for this, pass checkAsUser explicitly */ |
| rls_status = check_enable_rls(rte->relid, perminfo->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; |
| } |
| |
| /* |
| * RLS is enabled for this relation. |
| * |
| * Get the security policies that should be applied, based on the command |
| * type. Note that if this isn't the target relation, we actually want |
| * the relation's SELECT policies, regardless of the query command type, |
| * for example in UPDATE t1 ... FROM t2 we need to apply t1's UPDATE |
| * policies and t2's SELECT policies. |
| */ |
| rel = table_open(rte->relid, NoLock); |
| |
| commandType = rt_index == root->resultRelation ? |
| root->commandType : CMD_SELECT; |
| |
| /* |
| * In some cases, we need to apply USING policies (which control the |
| * visibility of records) associated with multiple command types (see |
| * specific cases below). |
| * |
| * When considering the order in which to apply these USING policies, we |
| * prefer to apply higher privileged policies, those which allow the user |
| * to lock records (UPDATE and DELETE), first, followed by policies which |
| * don't (SELECT). |
| * |
| * Note that the optimizer is free to push down and reorder quals which |
| * use leakproof functions. |
| * |
| * In all cases, if there are no policy clauses allowing access to rows in |
| * the table for the specific type of operation, then a single |
| * always-false clause (a default-deny policy) will be added (see |
| * add_security_quals). |
| */ |
| |
| /* |
| * For a SELECT, if UPDATE privileges are required (eg: the user has |
| * specified FOR [KEY] UPDATE/SHARE), then add the UPDATE USING quals |
| * first. |
| * |
| * This way, we filter out any records from the SELECT FOR SHARE/UPDATE |
| * which the user does not have access to via the UPDATE USING policies, |
| * similar to how we require normal UPDATE rights for these queries. |
| */ |
| if (commandType == CMD_SELECT && perminfo->requiredPerms & ACL_UPDATE) |
| { |
| List *update_permissive_policies; |
| List *update_restrictive_policies; |
| |
| get_policies_for_relation(rel, CMD_UPDATE, user_id, |
| &update_permissive_policies, |
| &update_restrictive_policies); |
| |
| add_security_quals(rt_index, |
| update_permissive_policies, |
| update_restrictive_policies, |
| securityQuals, |
| hasSubLinks); |
| } |
| |
| /* |
| * For SELECT, UPDATE and DELETE, add security quals to enforce the USING |
| * policies. These security quals control access to existing table rows. |
| * Restrictive policies are combined together using AND, and permissive |
| * policies are combined together using OR. |
| */ |
| |
| get_policies_for_relation(rel, commandType, user_id, &permissive_policies, |
| &restrictive_policies); |
| |
| if (commandType == CMD_SELECT || |
| commandType == CMD_UPDATE || |
| commandType == CMD_DELETE) |
| add_security_quals(rt_index, |
| permissive_policies, |
| restrictive_policies, |
| securityQuals, |
| hasSubLinks); |
| |
| /* |
| * Similar to above, during an UPDATE, DELETE, or MERGE, if SELECT rights |
| * are also required (eg: when a RETURNING clause exists, or the user has |
| * provided a WHERE clause which involves columns from the relation), we |
| * collect up CMD_SELECT policies and add them via add_security_quals |
| * first. |
| * |
| * This way, we filter out any records which are not visible through an |
| * ALL or SELECT USING policy. |
| */ |
| if ((commandType == CMD_UPDATE || commandType == CMD_DELETE || |
| commandType == CMD_MERGE) && |
| perminfo->requiredPerms & ACL_SELECT) |
| { |
| List *select_permissive_policies; |
| List *select_restrictive_policies; |
| |
| get_policies_for_relation(rel, CMD_SELECT, user_id, |
| &select_permissive_policies, |
| &select_restrictive_policies); |
| |
| add_security_quals(rt_index, |
| select_permissive_policies, |
| select_restrictive_policies, |
| securityQuals, |
| hasSubLinks); |
| } |
| |
| /* |
| * For INSERT and UPDATE, add withCheckOptions to verify that any new |
| * records added are consistent with the security policies. This will use |
| * each policy's WITH CHECK clause, or its USING clause if no explicit |
| * WITH CHECK clause is defined. |
| */ |
| if (commandType == CMD_INSERT || commandType == CMD_UPDATE) |
| { |
| /* This should be the target relation */ |
| Assert(rt_index == root->resultRelation); |
| |
| add_with_check_options(rel, rt_index, |
| commandType == CMD_INSERT ? |
| WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, |
| permissive_policies, |
| restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| false); |
| |
| /* |
| * Get and add ALL/SELECT policies, if SELECT rights are required for |
| * this relation (eg: when RETURNING is used). These are added as WCO |
| * policies rather than security quals to ensure that an error is |
| * raised if a policy is violated; otherwise, we might end up silently |
| * dropping rows to be added. |
| */ |
| if (perminfo->requiredPerms & ACL_SELECT) |
| { |
| List *select_permissive_policies = NIL; |
| List *select_restrictive_policies = NIL; |
| |
| get_policies_for_relation(rel, CMD_SELECT, user_id, |
| &select_permissive_policies, |
| &select_restrictive_policies); |
| add_with_check_options(rel, rt_index, |
| commandType == CMD_INSERT ? |
| WCO_RLS_INSERT_CHECK : WCO_RLS_UPDATE_CHECK, |
| select_permissive_policies, |
| select_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| } |
| |
| /* |
| * For INSERT ... ON CONFLICT DO UPDATE we need additional policy |
| * checks for the UPDATE which may be applied to the same RTE. |
| */ |
| if (commandType == CMD_INSERT && |
| root->onConflict && root->onConflict->action == ONCONFLICT_UPDATE) |
| { |
| List *conflict_permissive_policies; |
| List *conflict_restrictive_policies; |
| List *conflict_select_permissive_policies = NIL; |
| List *conflict_select_restrictive_policies = NIL; |
| |
| /* Get the policies that apply to the auxiliary UPDATE */ |
| get_policies_for_relation(rel, CMD_UPDATE, user_id, |
| &conflict_permissive_policies, |
| &conflict_restrictive_policies); |
| |
| /* |
| * Enforce the USING clauses of the UPDATE policies using WCOs |
| * rather than security quals. This ensures that an error is |
| * raised if the conflicting row cannot be updated due to RLS, |
| * rather than the change being silently dropped. |
| */ |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_CONFLICT_CHECK, |
| conflict_permissive_policies, |
| conflict_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| |
| /* |
| * Get and add ALL/SELECT policies, as WCO_RLS_CONFLICT_CHECK WCOs |
| * to ensure they are considered when taking the UPDATE path of an |
| * INSERT .. ON CONFLICT DO UPDATE, if SELECT rights are required |
| * for this relation, also as WCO policies, again, to avoid |
| * silently dropping data. See above. |
| */ |
| if (perminfo->requiredPerms & ACL_SELECT) |
| { |
| get_policies_for_relation(rel, CMD_SELECT, user_id, |
| &conflict_select_permissive_policies, |
| &conflict_select_restrictive_policies); |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_CONFLICT_CHECK, |
| conflict_select_permissive_policies, |
| conflict_select_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| } |
| |
| /* Enforce the WITH CHECK clauses of the UPDATE policies */ |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_UPDATE_CHECK, |
| conflict_permissive_policies, |
| conflict_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| false); |
| |
| /* |
| * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure |
| * that the final updated row is visible when taking the UPDATE |
| * path of an INSERT .. ON CONFLICT DO UPDATE, if SELECT rights |
| * are required for this relation. |
| */ |
| if (perminfo->requiredPerms & ACL_SELECT) |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_UPDATE_CHECK, |
| conflict_select_permissive_policies, |
| conflict_select_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| } |
| } |
| |
| /* |
| * FOR MERGE, we fetch policies for UPDATE, DELETE and INSERT (and ALL) |
| * and set them up so that we can enforce the appropriate policy depending |
| * on the final action we take. |
| * |
| * We already fetched the SELECT policies above, to check existing rows, |
| * but we must also check that new rows created by UPDATE actions are |
| * visible, if SELECT rights are required for this relation. We don't do |
| * this for INSERT actions, since an INSERT command would only do this |
| * check if it had a RETURNING list, and MERGE does not support RETURNING. |
| * |
| * We don't push the UPDATE/DELETE USING quals to the RTE because we don't |
| * really want to apply them while scanning the relation since we don't |
| * know whether we will be doing an UPDATE or a DELETE at the end. We |
| * apply the respective policy once we decide the final action on the |
| * target tuple. |
| * |
| * XXX We are setting up USING quals as WITH CHECK. If RLS prohibits |
| * UPDATE/DELETE on the target row, we shall throw an error instead of |
| * silently ignoring the row. This is different than how normal |
| * UPDATE/DELETE works and more in line with INSERT ON CONFLICT DO UPDATE |
| * handling. |
| */ |
| if (commandType == CMD_MERGE) |
| { |
| List *merge_update_permissive_policies; |
| List *merge_update_restrictive_policies; |
| List *merge_delete_permissive_policies; |
| List *merge_delete_restrictive_policies; |
| List *merge_insert_permissive_policies; |
| List *merge_insert_restrictive_policies; |
| |
| /* |
| * Fetch the UPDATE policies and set them up to execute on the |
| * existing target row before doing UPDATE. |
| */ |
| get_policies_for_relation(rel, CMD_UPDATE, user_id, |
| &merge_update_permissive_policies, |
| &merge_update_restrictive_policies); |
| |
| /* |
| * WCO_RLS_MERGE_UPDATE_CHECK is used to check UPDATE USING quals on |
| * the existing target row. |
| */ |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_MERGE_UPDATE_CHECK, |
| merge_update_permissive_policies, |
| merge_update_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| |
| /* Enforce the WITH CHECK clauses of the UPDATE policies */ |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_UPDATE_CHECK, |
| merge_update_permissive_policies, |
| merge_update_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| false); |
| |
| /* |
| * Add ALL/SELECT policies as WCO_RLS_UPDATE_CHECK WCOs, to ensure |
| * that the updated row is visible when executing an UPDATE action, if |
| * SELECT rights are required for this relation. |
| */ |
| if (perminfo->requiredPerms & ACL_SELECT) |
| { |
| List *merge_select_permissive_policies; |
| List *merge_select_restrictive_policies; |
| |
| get_policies_for_relation(rel, CMD_SELECT, user_id, |
| &merge_select_permissive_policies, |
| &merge_select_restrictive_policies); |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_UPDATE_CHECK, |
| merge_select_permissive_policies, |
| merge_select_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| } |
| |
| /* |
| * Fetch the DELETE policies and set them up to execute on the |
| * existing target row before doing DELETE. |
| */ |
| get_policies_for_relation(rel, CMD_DELETE, user_id, |
| &merge_delete_permissive_policies, |
| &merge_delete_restrictive_policies); |
| |
| /* |
| * WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on |
| * the existing target row. |
| */ |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_MERGE_DELETE_CHECK, |
| merge_delete_permissive_policies, |
| merge_delete_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| true); |
| |
| /* |
| * No special handling is required for INSERT policies. They will be |
| * checked and enforced during ExecInsert(). But we must add them to |
| * withCheckOptions. |
| */ |
| get_policies_for_relation(rel, CMD_INSERT, user_id, |
| &merge_insert_permissive_policies, |
| &merge_insert_restrictive_policies); |
| |
| add_with_check_options(rel, rt_index, |
| WCO_RLS_INSERT_CHECK, |
| merge_insert_permissive_policies, |
| merge_insert_restrictive_policies, |
| withCheckOptions, |
| hasSubLinks, |
| false); |
| } |
| |
| table_close(rel, NoLock); |
| |
| /* |
| * Copy checkAsUser to the row security quals and WithCheckOption checks, |
| * in case they contain any subqueries referring to other relations. |
| */ |
| setRuleCheckAsUser((Node *) *securityQuals, perminfo->checkAsUser); |
| setRuleCheckAsUser((Node *) *withCheckOptions, perminfo->checkAsUser); |
| |
| /* |
| * Mark this query as having row security, so plancache can invalidate it |
| * when necessary (eg: role changes) |
| */ |
| *hasRowSecurity = true; |
| } |
| |
| /* |
| * get_policies_for_relation |
| * |
| * Returns lists of permissive and restrictive policies to be applied to the |
| * specified relation, based on the command type and role. |
| * |
| * This includes any policies added by extensions. |
| */ |
| static void |
| get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, |
| List **permissive_policies, |
| List **restrictive_policies) |
| { |
| ListCell *item; |
| |
| *permissive_policies = NIL; |
| *restrictive_policies = NIL; |
| |
| /* First find all internal policies for the relation. */ |
| foreach(item, relation->rd_rsdesc->policies) |
| { |
| bool cmd_matches = false; |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| |
| /* Always add ALL policies, if they exist. */ |
| if (policy->polcmd == '*') |
| cmd_matches = true; |
| else |
| { |
| /* Check whether the policy applies to the specified command type */ |
| switch (cmd) |
| { |
| case CMD_SELECT: |
| if (policy->polcmd == ACL_SELECT_CHR) |
| cmd_matches = true; |
| break; |
| case CMD_INSERT: |
| if (policy->polcmd == ACL_INSERT_CHR) |
| cmd_matches = true; |
| break; |
| case CMD_UPDATE: |
| if (policy->polcmd == ACL_UPDATE_CHR) |
| cmd_matches = true; |
| break; |
| case CMD_DELETE: |
| if (policy->polcmd == ACL_DELETE_CHR) |
| cmd_matches = true; |
| break; |
| case CMD_MERGE: |
| |
| /* |
| * We do not support a separate policy for MERGE command. |
| * Instead it derives from the policies defined for other |
| * commands. |
| */ |
| break; |
| default: |
| elog(ERROR, "unrecognized policy command type %d", |
| (int) cmd); |
| break; |
| } |
| } |
| |
| /* |
| * Add this policy to the relevant list of policies if it applies to |
| * the specified role. |
| */ |
| if (cmd_matches && check_role_for_policy(policy->roles, user_id)) |
| { |
| if (policy->permissive) |
| *permissive_policies = lappend(*permissive_policies, policy); |
| else |
| *restrictive_policies = lappend(*restrictive_policies, policy); |
| } |
| } |
| |
| /* |
| * We sort restrictive policies by name so that any WCOs they generate are |
| * checked in a well-defined order. |
| */ |
| sort_policies_by_name(*restrictive_policies); |
| |
| /* |
| * Then add any permissive or restrictive policies defined by extensions. |
| * These are simply appended to the lists of internal policies, if they |
| * apply to the specified role. |
| */ |
| if (row_security_policy_hook_restrictive) |
| { |
| List *hook_policies = |
| (*row_security_policy_hook_restrictive) (cmd, relation); |
| |
| /* |
| * As with built-in restrictive policies, we sort any hook-provided |
| * restrictive policies by name also. Note that we also intentionally |
| * always check all built-in restrictive policies, in name order, |
| * before checking restrictive policies added by hooks, in name order. |
| */ |
| sort_policies_by_name(hook_policies); |
| |
| foreach(item, hook_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| |
| if (check_role_for_policy(policy->roles, user_id)) |
| *restrictive_policies = lappend(*restrictive_policies, policy); |
| } |
| } |
| |
| if (row_security_policy_hook_permissive) |
| { |
| List *hook_policies = |
| (*row_security_policy_hook_permissive) (cmd, relation); |
| |
| foreach(item, hook_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| |
| if (check_role_for_policy(policy->roles, user_id)) |
| *permissive_policies = lappend(*permissive_policies, policy); |
| } |
| } |
| } |
| |
| /* |
| * sort_policies_by_name |
| * |
| * This is only used for restrictive policies, ensuring that any |
| * WithCheckOptions they generate are applied in a well-defined order. |
| * This is not necessary for permissive policies, since they are all combined |
| * together using OR into a single WithCheckOption check. |
| */ |
| static void |
| sort_policies_by_name(List *policies) |
| { |
| list_sort(policies, row_security_policy_cmp); |
| } |
| |
| /* |
| * list_sort comparator to sort RowSecurityPolicy entries by name |
| */ |
| static int |
| row_security_policy_cmp(const ListCell *a, const ListCell *b) |
| { |
| const RowSecurityPolicy *pa = (const RowSecurityPolicy *) lfirst(a); |
| const RowSecurityPolicy *pb = (const RowSecurityPolicy *) lfirst(b); |
| |
| /* Guard against NULL policy names from extensions */ |
| if (pa->policy_name == NULL) |
| return pb->policy_name == NULL ? 0 : 1; |
| if (pb->policy_name == NULL) |
| return -1; |
| |
| return strcmp(pa->policy_name, pb->policy_name); |
| } |
| |
| /* |
| * add_security_quals |
| * |
| * Add security quals to enforce the specified RLS policies, restricting |
| * access to existing data in a table. If there are no policies controlling |
| * access to the table, then all access is prohibited --- i.e., an implicit |
| * default-deny policy is used. |
| * |
| * New security quals are added to securityQuals, and hasSubLinks is set to |
| * true if any of the quals added contain sublink subqueries. |
| */ |
| static void |
| add_security_quals(int rt_index, |
| List *permissive_policies, |
| List *restrictive_policies, |
| List **securityQuals, |
| bool *hasSubLinks) |
| { |
| ListCell *item; |
| List *permissive_quals = NIL; |
| Expr *rowsec_expr; |
| |
| /* |
| * First collect up the permissive quals. If we do not find any |
| * permissive policies then no rows are visible (this is handled below). |
| */ |
| foreach(item, permissive_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| |
| if (policy->qual != NULL) |
| { |
| permissive_quals = lappend(permissive_quals, |
| copyObject(policy->qual)); |
| *hasSubLinks |= policy->hassublinks; |
| } |
| } |
| |
| /* |
| * We must have permissive quals, always, or no rows are visible. |
| * |
| * If we do not, then we simply return a single 'false' qual which results |
| * in no rows being visible. |
| */ |
| if (permissive_quals != NIL) |
| { |
| /* |
| * We now know that permissive policies exist, so we can now add |
| * security quals based on the USING clauses from the restrictive |
| * policies. Since these need to be combined together using AND, we |
| * can just add them one at a time. |
| */ |
| foreach(item, restrictive_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| Expr *qual; |
| |
| if (policy->qual != NULL) |
| { |
| qual = copyObject(policy->qual); |
| ChangeVarNodes((Node *) qual, 1, rt_index, 0); |
| |
| *securityQuals = list_append_unique(*securityQuals, qual); |
| *hasSubLinks |= policy->hassublinks; |
| } |
| } |
| |
| /* |
| * Then add a single security qual combining together the USING |
| * clauses from all the permissive policies using OR. |
| */ |
| if (list_length(permissive_quals) == 1) |
| rowsec_expr = (Expr *) linitial(permissive_quals); |
| else |
| rowsec_expr = makeBoolExpr(OR_EXPR, permissive_quals, -1); |
| |
| ChangeVarNodes((Node *) rowsec_expr, 1, rt_index, 0); |
| *securityQuals = list_append_unique(*securityQuals, rowsec_expr); |
| } |
| else |
| |
| /* |
| * A permissive policy must exist for rows to be visible at all. |
| * Therefore, if there were no permissive policies found, return a |
| * single always-false clause. |
| */ |
| *securityQuals = lappend(*securityQuals, |
| makeConst(BOOLOID, -1, InvalidOid, |
| sizeof(bool), BoolGetDatum(false), |
| false, true)); |
| } |
| |
| /* |
| * add_with_check_options |
| * |
| * Add WithCheckOptions of the specified kind to check that new records |
| * added by an INSERT or UPDATE are consistent with the specified RLS |
| * policies. Normally new data must satisfy the WITH CHECK clauses from the |
| * policies. If a policy has no explicit WITH CHECK clause, its USING clause |
| * is used instead. In the special case of an UPDATE arising from an |
| * INSERT ... ON CONFLICT DO UPDATE, existing records are first checked using |
| * a WCO_RLS_CONFLICT_CHECK WithCheckOption, which always uses the USING |
| * clauses from RLS policies. |
| * |
| * New WCOs are added to withCheckOptions, and hasSubLinks is set to true if |
| * any of the check clauses added contain sublink subqueries. |
| */ |
| static void |
| add_with_check_options(Relation rel, |
| int rt_index, |
| WCOKind kind, |
| List *permissive_policies, |
| List *restrictive_policies, |
| List **withCheckOptions, |
| bool *hasSubLinks, |
| bool force_using) |
| { |
| ListCell *item; |
| List *permissive_quals = NIL; |
| |
| #define QUAL_FOR_WCO(policy) \ |
| ( !force_using && \ |
| (policy)->with_check_qual != NULL ? \ |
| (policy)->with_check_qual : (policy)->qual ) |
| |
| /* |
| * First collect up the permissive policy clauses, similar to |
| * add_security_quals. |
| */ |
| foreach(item, permissive_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| Expr *qual = QUAL_FOR_WCO(policy); |
| |
| if (qual != NULL) |
| { |
| permissive_quals = lappend(permissive_quals, copyObject(qual)); |
| *hasSubLinks |= policy->hassublinks; |
| } |
| } |
| |
| /* |
| * There must be at least one permissive qual found or no rows are allowed |
| * to be added. This is the same as in add_security_quals. |
| * |
| * If there are no permissive_quals then we fall through and return a |
| * single 'false' WCO, preventing all new rows. |
| */ |
| if (permissive_quals != NIL) |
| { |
| /* |
| * Add a single WithCheckOption for all the permissive policy clauses, |
| * combining them together using OR. This check has no policy name, |
| * since if the check fails it means that no policy granted permission |
| * to perform the update, rather than any particular policy being |
| * violated. |
| */ |
| WithCheckOption *wco; |
| |
| wco = makeNode(WithCheckOption); |
| wco->kind = kind; |
| wco->relname = pstrdup(RelationGetRelationName(rel)); |
| wco->polname = NULL; |
| wco->cascaded = false; |
| |
| if (list_length(permissive_quals) == 1) |
| wco->qual = (Node *) linitial(permissive_quals); |
| else |
| wco->qual = (Node *) makeBoolExpr(OR_EXPR, permissive_quals, -1); |
| |
| ChangeVarNodes(wco->qual, 1, rt_index, 0); |
| |
| *withCheckOptions = list_append_unique(*withCheckOptions, wco); |
| |
| /* |
| * Now add WithCheckOptions for each of the restrictive policy clauses |
| * (which will be combined together using AND). We use a separate |
| * WithCheckOption for each restrictive policy to allow the policy |
| * name to be included in error reports if the policy is violated. |
| */ |
| foreach(item, restrictive_policies) |
| { |
| RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item); |
| Expr *qual = QUAL_FOR_WCO(policy); |
| |
| if (qual != NULL) |
| { |
| qual = copyObject(qual); |
| ChangeVarNodes((Node *) qual, 1, rt_index, 0); |
| |
| wco = makeNode(WithCheckOption); |
| wco->kind = kind; |
| wco->relname = pstrdup(RelationGetRelationName(rel)); |
| wco->polname = pstrdup(policy->policy_name); |
| wco->qual = (Node *) qual; |
| wco->cascaded = false; |
| |
| *withCheckOptions = list_append_unique(*withCheckOptions, wco); |
| *hasSubLinks |= policy->hassublinks; |
| } |
| } |
| } |
| else |
| { |
| /* |
| * If there were no policy clauses to check new data, add a single |
| * always-false WCO (a default-deny policy). |
| */ |
| WithCheckOption *wco; |
| |
| wco = makeNode(WithCheckOption); |
| wco->kind = kind; |
| wco->relname = pstrdup(RelationGetRelationName(rel)); |
| wco->polname = NULL; |
| wco->qual = (Node *) makeConst(BOOLOID, -1, InvalidOid, |
| sizeof(bool), BoolGetDatum(false), |
| false, true); |
| wco->cascaded = false; |
| |
| *withCheckOptions = lappend(*withCheckOptions, wco); |
| } |
| } |
| |
| /* |
| * 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; |
| } |