diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/commands/policy.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/commands/policy.c')
-rw-r--r-- | src/backend/commands/policy.c | 1285 |
1 files changed, 1285 insertions, 0 deletions
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c new file mode 100644 index 0000000..a225141 --- /dev/null +++ b/src/backend/commands/policy.c @@ -0,0 +1,1285 @@ +/*------------------------------------------------------------------------- + * + * policy.c + * Commands for manipulating policies. + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/commands/policy.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup.h" +#include "access/htup_details.h" +#include "access/relation.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_policy.h" +#include "catalog/pg_type.h" +#include "commands/policy.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/pg_list.h" +#include "parser/parse_clause.h" +#include "parser/parse_collate.h" +#include "parser/parse_node.h" +#include "parser/parse_relation.h" +#include "rewrite/rewriteManip.h" +#include "rewrite/rowsecurity.h" +#include "storage/lock.h" +#include "utils/acl.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +static void RangeVarCallbackForPolicy(const RangeVar *rv, + Oid relid, Oid oldrelid, void *arg); +static char parse_policy_command(const char *cmd_name); +static Datum *policy_role_list_to_array(List *roles, int *num_roles); + +/* + * Callback to RangeVarGetRelidExtended(). + * + * Checks the following: + * - the relation specified is a table. + * - current user owns the table. + * - the table is not a system table. + * + * If any of these checks fails then an error is raised. + */ +static void +RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, + void *arg) +{ + HeapTuple tuple; + Form_pg_class classform; + char relkind; + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + return; + + classform = (Form_pg_class) GETSTRUCT(tuple); + relkind = classform->relkind; + + /* Must own relation. */ + if (!pg_class_ownercheck(relid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); + + /* No system table modifications unless explicitly allowed. */ + if (!allowSystemTableMods && IsSystemClass(relid, classform)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + rv->relname))); + + /* Relation type MUST be a table. */ + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", rv->relname))); + + ReleaseSysCache(tuple); +} + +/* + * parse_policy_command - + * helper function to convert full command strings to their char + * representation. + * + * cmd_name - full string command name. Valid values are 'all', 'select', + * 'insert', 'update' and 'delete'. + * + */ +static char +parse_policy_command(const char *cmd_name) +{ + char polcmd; + + if (!cmd_name) + elog(ERROR, "unrecognized policy command"); + + if (strcmp(cmd_name, "all") == 0) + polcmd = '*'; + else if (strcmp(cmd_name, "select") == 0) + polcmd = ACL_SELECT_CHR; + else if (strcmp(cmd_name, "insert") == 0) + polcmd = ACL_INSERT_CHR; + else if (strcmp(cmd_name, "update") == 0) + polcmd = ACL_UPDATE_CHR; + else if (strcmp(cmd_name, "delete") == 0) + polcmd = ACL_DELETE_CHR; + else + elog(ERROR, "unrecognized policy command"); + + return polcmd; +} + +/* + * policy_role_list_to_array + * helper function to convert a list of RoleSpecs to an array of + * role id Datums. + */ +static Datum * +policy_role_list_to_array(List *roles, int *num_roles) +{ + Datum *role_oids; + ListCell *cell; + int i = 0; + + /* Handle no roles being passed in as being for public */ + if (roles == NIL) + { + *num_roles = 1; + role_oids = (Datum *) palloc(*num_roles * sizeof(Datum)); + role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC); + + return role_oids; + } + + *num_roles = list_length(roles); + role_oids = (Datum *) palloc(*num_roles * sizeof(Datum)); + + foreach(cell, roles) + { + RoleSpec *spec = lfirst(cell); + + /* + * PUBLIC covers all roles, so it only makes sense alone. + */ + if (spec->roletype == ROLESPEC_PUBLIC) + { + if (*num_roles != 1) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ignoring specified roles other than PUBLIC"), + errhint("All roles are members of the PUBLIC role."))); + *num_roles = 1; + } + role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC); + + return role_oids; + } + else + role_oids[i++] = + ObjectIdGetDatum(get_rolespec_oid(spec, false)); + } + + return role_oids; +} + +/* + * Load row security policy from the catalog, and store it in + * the relation's relcache entry. + * + * Note that caller should have verified that pg_class.relrowsecurity + * is true for this relation. + */ +void +RelationBuildRowSecurity(Relation relation) +{ + MemoryContext rscxt; + MemoryContext oldcxt = CurrentMemoryContext; + RowSecurityDesc *rsdesc; + Relation catalog; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + + /* + * Create a memory context to hold everything associated with this + * relation's row security policy. This makes it easy to clean up during + * a relcache flush. However, to cover the possibility of an error + * partway through, we don't make the context long-lived till we're done. + */ + rscxt = AllocSetContextCreate(CurrentMemoryContext, + "row security descriptor", + ALLOCSET_SMALL_SIZES); + MemoryContextCopyAndSetIdentifier(rscxt, + RelationGetRelationName(relation)); + + rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc)); + rsdesc->rscxt = rscxt; + + /* + * Now scan pg_policy for RLS policies associated with this relation. + * Because we use the index on (polrelid, polname), we should consistently + * visit the rel's policies in name order, at least when system indexes + * aren't disabled. This simplifies equalRSDesc(). + */ + catalog = table_open(PolicyRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(tuple = systable_getnext(sscan))) + { + Form_pg_policy policy_form = (Form_pg_policy) GETSTRUCT(tuple); + RowSecurityPolicy *policy; + Datum datum; + bool isnull; + char *str_value; + + policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy)); + + /* + * Note: we must be sure that pass-by-reference data gets copied into + * rscxt. We avoid making that context current over wider spans than + * we have to, though. + */ + + /* Get policy command */ + policy->polcmd = policy_form->polcmd; + + /* Get policy, permissive or restrictive */ + policy->permissive = policy_form->polpermissive; + + /* Get policy name */ + policy->policy_name = + MemoryContextStrdup(rscxt, NameStr(policy_form->polname)); + + /* Get policy roles */ + datum = heap_getattr(tuple, Anum_pg_policy_polroles, + RelationGetDescr(catalog), &isnull); + /* shouldn't be null, but let's check for luck */ + if (isnull) + elog(ERROR, "unexpected null value in pg_policy.polroles"); + MemoryContextSwitchTo(rscxt); + policy->roles = DatumGetArrayTypePCopy(datum); + MemoryContextSwitchTo(oldcxt); + + /* Get policy qual */ + datum = heap_getattr(tuple, Anum_pg_policy_polqual, + RelationGetDescr(catalog), &isnull); + if (!isnull) + { + str_value = TextDatumGetCString(datum); + MemoryContextSwitchTo(rscxt); + policy->qual = (Expr *) stringToNode(str_value); + MemoryContextSwitchTo(oldcxt); + pfree(str_value); + } + else + policy->qual = NULL; + + /* Get WITH CHECK qual */ + datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck, + RelationGetDescr(catalog), &isnull); + if (!isnull) + { + str_value = TextDatumGetCString(datum); + MemoryContextSwitchTo(rscxt); + policy->with_check_qual = (Expr *) stringToNode(str_value); + MemoryContextSwitchTo(oldcxt); + pfree(str_value); + } + else + policy->with_check_qual = NULL; + + /* We want to cache whether there are SubLinks in these expressions */ + policy->hassublinks = checkExprHasSubLink((Node *) policy->qual) || + checkExprHasSubLink((Node *) policy->with_check_qual); + + /* + * Add this object to list. For historical reasons, the list is built + * in reverse order. + */ + MemoryContextSwitchTo(rscxt); + rsdesc->policies = lcons(policy, rsdesc->policies); + MemoryContextSwitchTo(oldcxt); + } + + systable_endscan(sscan); + table_close(catalog, AccessShareLock); + + /* + * Success. Reparent the descriptor's memory context under + * CacheMemoryContext so that it will live indefinitely, then attach the + * policy descriptor to the relcache entry. + */ + MemoryContextSetParent(rscxt, CacheMemoryContext); + + relation->rd_rsdesc = rsdesc; +} + +/* + * RemovePolicyById - + * remove a policy by its OID. If a policy does not exist with the provided + * oid, then an error is raised. + * + * policy_id - the oid of the policy. + */ +void +RemovePolicyById(Oid policy_id) +{ + Relation pg_policy_rel; + SysScanDesc sscan; + ScanKeyData skey[1]; + HeapTuple tuple; + Oid relid; + Relation rel; + + pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); + + /* + * Find the policy to delete. + */ + ScanKeyInit(&skey[0], + Anum_pg_policy_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policy_id)); + + sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true, + NULL, 1, skey); + + tuple = systable_getnext(sscan); + + /* If the policy exists, then remove it, otherwise raise an error. */ + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for policy %u", policy_id); + + /* + * Open and exclusive-lock the relation the policy belongs to. (We need + * exclusive lock to lock out queries that might otherwise depend on the + * set of policies the rel has; furthermore we've got to hold the lock + * till commit.) + */ + relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; + + rel = table_open(relid, AccessExclusiveLock); + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(rel)))); + + if (!allowSystemTableMods && IsSystemRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied: \"%s\" is a system catalog", + RelationGetRelationName(rel)))); + + CatalogTupleDelete(pg_policy_rel, &tuple->t_self); + + systable_endscan(sscan); + + /* + * Note that, unlike some of the other flags in pg_class, relrowsecurity + * is not just an indication of if policies exist. When relrowsecurity is + * set by a user, then all access to the relation must be through a + * policy. If no policy is defined for the relation then a default-deny + * policy is created and all records are filtered (except for queries from + * the owner). + */ + CacheInvalidateRelcache(rel); + + table_close(rel, NoLock); + + /* Clean up */ + table_close(pg_policy_rel, RowExclusiveLock); +} + +/* + * RemoveRoleFromObjectPolicy - + * remove a role from a policy's applicable-roles list. + * + * Returns true if the role was successfully removed from the policy. + * Returns false if the role was not removed because it would have left + * polroles empty (which is disallowed, though perhaps it should not be). + * On false return, the caller should instead drop the policy altogether. + * + * roleid - the oid of the role to remove + * classid - should always be PolicyRelationId + * policy_id - the oid of the policy. + */ +bool +RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id) +{ + Relation pg_policy_rel; + SysScanDesc sscan; + ScanKeyData skey[1]; + HeapTuple tuple; + Oid relid; + ArrayType *policy_roles; + Datum roles_datum; + Oid *roles; + int num_roles; + Datum *role_oids; + bool attr_isnull; + bool keep_policy = true; + int i, + j; + + Assert(classid == PolicyRelationId); + + pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); + + /* + * Find the policy to update. + */ + ScanKeyInit(&skey[0], + Anum_pg_policy_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(policy_id)); + + sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true, + NULL, 1, skey); + + tuple = systable_getnext(sscan); + + /* Raise an error if we don't find the policy. */ + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for policy %u", policy_id); + + /* Identify rel the policy belongs to */ + relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; + + /* Get the current set of roles */ + roles_datum = heap_getattr(tuple, + Anum_pg_policy_polroles, + RelationGetDescr(pg_policy_rel), + &attr_isnull); + + Assert(!attr_isnull); + + policy_roles = DatumGetArrayTypePCopy(roles_datum); + roles = (Oid *) ARR_DATA_PTR(policy_roles); + num_roles = ARR_DIMS(policy_roles)[0]; + + /* + * Rebuild the polroles array, without any mentions of the target role. + * Ordinarily there'd be exactly one, but we must cope with duplicate + * mentions, since CREATE/ALTER POLICY historically have allowed that. + */ + role_oids = (Datum *) palloc(num_roles * sizeof(Datum)); + for (i = 0, j = 0; i < num_roles; i++) + { + if (roles[i] != roleid) + role_oids[j++] = ObjectIdGetDatum(roles[i]); + } + num_roles = j; + + /* If any roles remain, update the policy entry. */ + if (num_roles > 0) + { + ArrayType *role_ids; + Datum values[Natts_pg_policy]; + bool isnull[Natts_pg_policy]; + bool replaces[Natts_pg_policy]; + HeapTuple new_tuple; + HeapTuple reltup; + ObjectAddress target; + ObjectAddress myself; + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(replaces, 0, sizeof(replaces)); + memset(isnull, 0, sizeof(isnull)); + + /* This is the array for the new tuple */ + role_ids = construct_array(role_oids, num_roles, OIDOID, + sizeof(Oid), true, TYPALIGN_INT); + + replaces[Anum_pg_policy_polroles - 1] = true; + values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); + + new_tuple = heap_modify_tuple(tuple, + RelationGetDescr(pg_policy_rel), + values, isnull, replaces); + CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple); + + /* Remove all the old shared dependencies (roles) */ + deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0); + + /* Record the new shared dependencies (roles) */ + myself.classId = PolicyRelationId; + myself.objectId = policy_id; + myself.objectSubId = 0; + + target.classId = AuthIdRelationId; + target.objectSubId = 0; + for (i = 0; i < num_roles; i++) + { + target.objectId = DatumGetObjectId(role_oids[i]); + /* no need for dependency on the public role */ + if (target.objectId != ACL_ID_PUBLIC) + recordSharedDependencyOn(&myself, &target, + SHARED_DEPENDENCY_POLICY); + } + + InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0); + + heap_freetuple(new_tuple); + + /* Make updates visible */ + CommandCounterIncrement(); + + /* + * Invalidate relcache entry for rel the policy belongs to, to force + * redoing any dependent plans. In case of a race condition where the + * rel was just dropped, we need do nothing. + */ + reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (HeapTupleIsValid(reltup)) + { + CacheInvalidateRelcacheByTuple(reltup); + ReleaseSysCache(reltup); + } + } + else + { + /* No roles would remain, so drop the policy instead. */ + keep_policy = false; + } + + /* Clean up. */ + systable_endscan(sscan); + + table_close(pg_policy_rel, RowExclusiveLock); + + return keep_policy; +} + +/* + * CreatePolicy - + * handles the execution of the CREATE POLICY command. + * + * stmt - the CreatePolicyStmt that describes the policy to create. + */ +ObjectAddress +CreatePolicy(CreatePolicyStmt *stmt) +{ + Relation pg_policy_rel; + Oid policy_id; + Relation target_table; + Oid table_id; + char polcmd; + Datum *role_oids; + int nitems = 0; + ArrayType *role_ids; + ParseState *qual_pstate; + ParseState *with_check_pstate; + ParseNamespaceItem *nsitem; + Node *qual; + Node *with_check_qual; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple policy_tuple; + Datum values[Natts_pg_policy]; + bool isnull[Natts_pg_policy]; + ObjectAddress target; + ObjectAddress myself; + int i; + + /* Parse command */ + polcmd = parse_policy_command(stmt->cmd_name); + + /* + * If the command is SELECT or DELETE then WITH CHECK should be NULL. + */ + if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR) + && stmt->with_check != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("WITH CHECK cannot be applied to SELECT or DELETE"))); + + /* + * If the command is INSERT then WITH CHECK should be the only expression + * provided. + */ + if (polcmd == ACL_INSERT_CHR && stmt->qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only WITH CHECK expression allowed for INSERT"))); + + /* Collect role ids */ + role_oids = policy_role_list_to_array(stmt->roles, &nitems); + role_ids = construct_array(role_oids, nitems, OIDOID, + sizeof(Oid), true, TYPALIGN_INT); + + /* Parse the supplied clause */ + qual_pstate = make_parsestate(NULL); + with_check_pstate = make_parsestate(NULL); + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(isnull, 0, sizeof(isnull)); + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, + 0, + RangeVarCallbackForPolicy, + (void *) stmt); + + /* Open target_table to build quals. No additional lock is necessary. */ + target_table = relation_open(table_id, NoLock); + + /* Add for the regular security quals */ + nsitem = addRangeTableEntryForRelation(qual_pstate, target_table, + AccessShareLock, + NULL, false, false); + addNSItemToQuery(qual_pstate, nsitem, false, true, true); + + /* Add for the with-check quals */ + nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table, + AccessShareLock, + NULL, false, false); + addNSItemToQuery(with_check_pstate, nsitem, false, true, true); + + qual = transformWhereClause(qual_pstate, + stmt->qual, + EXPR_KIND_POLICY, + "POLICY"); + + with_check_qual = transformWhereClause(with_check_pstate, + stmt->with_check, + EXPR_KIND_POLICY, + "POLICY"); + + /* Fix up collation information */ + assign_expr_collations(qual_pstate, qual); + assign_expr_collations(with_check_pstate, with_check_qual); + + /* Open pg_policy catalog */ + pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); + + /* Set key - policy's relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Set key - policy's name. */ + ScanKeyInit(&skey[1], + Anum_pg_policy_polname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->policy_name)); + + sscan = systable_beginscan(pg_policy_rel, + PolicyPolrelidPolnameIndexId, true, NULL, 2, + skey); + + policy_tuple = systable_getnext(sscan); + + /* Complain if the policy name already exists for the table */ + if (HeapTupleIsValid(policy_tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("policy \"%s\" for table \"%s\" already exists", + stmt->policy_name, RelationGetRelationName(target_table)))); + + policy_id = GetNewOidWithIndex(pg_policy_rel, PolicyOidIndexId, + Anum_pg_policy_oid); + values[Anum_pg_policy_oid - 1] = ObjectIdGetDatum(policy_id); + values[Anum_pg_policy_polrelid - 1] = ObjectIdGetDatum(table_id); + values[Anum_pg_policy_polname - 1] = DirectFunctionCall1(namein, + CStringGetDatum(stmt->policy_name)); + values[Anum_pg_policy_polcmd - 1] = CharGetDatum(polcmd); + values[Anum_pg_policy_polpermissive - 1] = BoolGetDatum(stmt->permissive); + values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); + + /* Add qual if present. */ + if (qual) + values[Anum_pg_policy_polqual - 1] = CStringGetTextDatum(nodeToString(qual)); + else + isnull[Anum_pg_policy_polqual - 1] = true; + + /* Add WITH CHECK qual if present */ + if (with_check_qual) + values[Anum_pg_policy_polwithcheck - 1] = CStringGetTextDatum(nodeToString(with_check_qual)); + else + isnull[Anum_pg_policy_polwithcheck - 1] = true; + + policy_tuple = heap_form_tuple(RelationGetDescr(pg_policy_rel), values, + isnull); + + CatalogTupleInsert(pg_policy_rel, policy_tuple); + + /* Record Dependencies */ + target.classId = RelationRelationId; + target.objectId = table_id; + target.objectSubId = 0; + + myself.classId = PolicyRelationId; + myself.objectId = policy_id; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + + recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable, + DEPENDENCY_NORMAL); + + recordDependencyOnExpr(&myself, with_check_qual, + with_check_pstate->p_rtable, DEPENDENCY_NORMAL); + + /* Register role dependencies */ + target.classId = AuthIdRelationId; + target.objectSubId = 0; + for (i = 0; i < nitems; i++) + { + target.objectId = DatumGetObjectId(role_oids[i]); + /* no dependency if public */ + if (target.objectId != ACL_ID_PUBLIC) + recordSharedDependencyOn(&myself, &target, + SHARED_DEPENDENCY_POLICY); + } + + InvokeObjectPostCreateHook(PolicyRelationId, policy_id, 0); + + /* Invalidate Relation Cache */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + heap_freetuple(policy_tuple); + free_parsestate(qual_pstate); + free_parsestate(with_check_pstate); + systable_endscan(sscan); + relation_close(target_table, NoLock); + table_close(pg_policy_rel, RowExclusiveLock); + + return myself; +} + +/* + * AlterPolicy - + * handles the execution of the ALTER POLICY command. + * + * stmt - the AlterPolicyStmt that describes the policy and how to alter it. + */ +ObjectAddress +AlterPolicy(AlterPolicyStmt *stmt) +{ + Relation pg_policy_rel; + Oid policy_id; + Relation target_table; + Oid table_id; + Datum *role_oids = NULL; + int nitems = 0; + ArrayType *role_ids = NULL; + List *qual_parse_rtable = NIL; + List *with_check_parse_rtable = NIL; + Node *qual = NULL; + Node *with_check_qual = NULL; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple policy_tuple; + HeapTuple new_tuple; + Datum values[Natts_pg_policy]; + bool isnull[Natts_pg_policy]; + bool replaces[Natts_pg_policy]; + ObjectAddress target; + ObjectAddress myself; + Datum polcmd_datum; + char polcmd; + bool polcmd_isnull; + int i; + + /* Parse role_ids */ + if (stmt->roles != NULL) + { + role_oids = policy_role_list_to_array(stmt->roles, &nitems); + role_ids = construct_array(role_oids, nitems, OIDOID, + sizeof(Oid), true, TYPALIGN_INT); + } + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock, + 0, + RangeVarCallbackForPolicy, + (void *) stmt); + + target_table = relation_open(table_id, NoLock); + + /* Parse the using policy clause */ + if (stmt->qual) + { + ParseNamespaceItem *nsitem; + ParseState *qual_pstate = make_parsestate(NULL); + + nsitem = addRangeTableEntryForRelation(qual_pstate, target_table, + AccessShareLock, + NULL, false, false); + + addNSItemToQuery(qual_pstate, nsitem, false, true, true); + + qual = transformWhereClause(qual_pstate, stmt->qual, + EXPR_KIND_POLICY, + "POLICY"); + + /* Fix up collation information */ + assign_expr_collations(qual_pstate, qual); + + qual_parse_rtable = qual_pstate->p_rtable; + free_parsestate(qual_pstate); + } + + /* Parse the with-check policy clause */ + if (stmt->with_check) + { + ParseNamespaceItem *nsitem; + ParseState *with_check_pstate = make_parsestate(NULL); + + nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table, + AccessShareLock, + NULL, false, false); + + addNSItemToQuery(with_check_pstate, nsitem, false, true, true); + + with_check_qual = transformWhereClause(with_check_pstate, + stmt->with_check, + EXPR_KIND_POLICY, + "POLICY"); + + /* Fix up collation information */ + assign_expr_collations(with_check_pstate, with_check_qual); + + with_check_parse_rtable = with_check_pstate->p_rtable; + free_parsestate(with_check_pstate); + } + + /* zero-clear */ + memset(values, 0, sizeof(values)); + memset(replaces, 0, sizeof(replaces)); + memset(isnull, 0, sizeof(isnull)); + + /* Find policy to update. */ + pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); + + /* Set key - policy's relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Set key - policy's name. */ + ScanKeyInit(&skey[1], + Anum_pg_policy_polname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->policy_name)); + + sscan = systable_beginscan(pg_policy_rel, + PolicyPolrelidPolnameIndexId, true, NULL, 2, + skey); + + policy_tuple = systable_getnext(sscan); + + /* Check that the policy is found, raise an error if not. */ + if (!HeapTupleIsValid(policy_tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("policy \"%s\" for table \"%s\" does not exist", + stmt->policy_name, + RelationGetRelationName(target_table)))); + + /* Get policy command */ + polcmd_datum = heap_getattr(policy_tuple, Anum_pg_policy_polcmd, + RelationGetDescr(pg_policy_rel), + &polcmd_isnull); + Assert(!polcmd_isnull); + polcmd = DatumGetChar(polcmd_datum); + + /* + * If the command is SELECT or DELETE then WITH CHECK should be NULL. + */ + if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR) + && stmt->with_check != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only USING expression allowed for SELECT, DELETE"))); + + /* + * If the command is INSERT then WITH CHECK should be the only expression + * provided. + */ + if ((polcmd == ACL_INSERT_CHR) + && stmt->qual != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("only WITH CHECK expression allowed for INSERT"))); + + policy_id = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; + + if (role_ids != NULL) + { + replaces[Anum_pg_policy_polroles - 1] = true; + values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids); + } + else + { + Oid *roles; + Datum roles_datum; + bool attr_isnull; + ArrayType *policy_roles; + + /* + * We need to pull the set of roles this policy applies to from what's + * in the catalog, so that we can recreate the dependencies correctly + * for the policy. + */ + + roles_datum = heap_getattr(policy_tuple, Anum_pg_policy_polroles, + RelationGetDescr(pg_policy_rel), + &attr_isnull); + Assert(!attr_isnull); + + policy_roles = DatumGetArrayTypePCopy(roles_datum); + + roles = (Oid *) ARR_DATA_PTR(policy_roles); + + nitems = ARR_DIMS(policy_roles)[0]; + + role_oids = (Datum *) palloc(nitems * sizeof(Datum)); + + for (i = 0; i < nitems; i++) + role_oids[i] = ObjectIdGetDatum(roles[i]); + } + + if (qual != NULL) + { + replaces[Anum_pg_policy_polqual - 1] = true; + values[Anum_pg_policy_polqual - 1] + = CStringGetTextDatum(nodeToString(qual)); + } + else + { + Datum value_datum; + bool attr_isnull; + + /* + * We need to pull the USING expression and build the range table for + * the policy from what's in the catalog, so that we can recreate the + * dependencies correctly for the policy. + */ + + /* Check if the policy has a USING expr */ + value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polqual, + RelationGetDescr(pg_policy_rel), + &attr_isnull); + if (!attr_isnull) + { + char *qual_value; + ParseState *qual_pstate; + + /* parsestate is built just to build the range table */ + qual_pstate = make_parsestate(NULL); + + qual_value = TextDatumGetCString(value_datum); + qual = stringToNode(qual_value); + + /* Add this rel to the parsestate's rangetable, for dependencies */ + (void) addRangeTableEntryForRelation(qual_pstate, target_table, + AccessShareLock, + NULL, false, false); + + qual_parse_rtable = qual_pstate->p_rtable; + free_parsestate(qual_pstate); + } + } + + if (with_check_qual != NULL) + { + replaces[Anum_pg_policy_polwithcheck - 1] = true; + values[Anum_pg_policy_polwithcheck - 1] + = CStringGetTextDatum(nodeToString(with_check_qual)); + } + else + { + Datum value_datum; + bool attr_isnull; + + /* + * We need to pull the WITH CHECK expression and build the range table + * for the policy from what's in the catalog, so that we can recreate + * the dependencies correctly for the policy. + */ + + /* Check if the policy has a WITH CHECK expr */ + value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polwithcheck, + RelationGetDescr(pg_policy_rel), + &attr_isnull); + if (!attr_isnull) + { + char *with_check_value; + ParseState *with_check_pstate; + + /* parsestate is built just to build the range table */ + with_check_pstate = make_parsestate(NULL); + + with_check_value = TextDatumGetCString(value_datum); + with_check_qual = stringToNode(with_check_value); + + /* Add this rel to the parsestate's rangetable, for dependencies */ + (void) addRangeTableEntryForRelation(with_check_pstate, + target_table, + AccessShareLock, + NULL, false, false); + + with_check_parse_rtable = with_check_pstate->p_rtable; + free_parsestate(with_check_pstate); + } + } + + new_tuple = heap_modify_tuple(policy_tuple, + RelationGetDescr(pg_policy_rel), + values, isnull, replaces); + CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple); + + /* Update Dependencies. */ + deleteDependencyRecordsFor(PolicyRelationId, policy_id, false); + + /* Record Dependencies */ + target.classId = RelationRelationId; + target.objectId = table_id; + target.objectSubId = 0; + + myself.classId = PolicyRelationId; + myself.objectId = policy_id; + myself.objectSubId = 0; + + recordDependencyOn(&myself, &target, DEPENDENCY_AUTO); + + recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL); + + recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable, + DEPENDENCY_NORMAL); + + /* Register role dependencies */ + deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0); + target.classId = AuthIdRelationId; + target.objectSubId = 0; + for (i = 0; i < nitems; i++) + { + target.objectId = DatumGetObjectId(role_oids[i]); + /* no dependency if public */ + if (target.objectId != ACL_ID_PUBLIC) + recordSharedDependencyOn(&myself, &target, + SHARED_DEPENDENCY_POLICY); + } + + InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0); + + heap_freetuple(new_tuple); + + /* Invalidate Relation Cache */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + systable_endscan(sscan); + relation_close(target_table, NoLock); + table_close(pg_policy_rel, RowExclusiveLock); + + return myself; +} + +/* + * rename_policy - + * change the name of a policy on a relation + */ +ObjectAddress +rename_policy(RenameStmt *stmt) +{ + Relation pg_policy_rel; + Relation target_table; + Oid table_id; + Oid opoloid; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple policy_tuple; + ObjectAddress address; + + /* Get id of table. Also handles permissions checks. */ + table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, + 0, + RangeVarCallbackForPolicy, + (void *) stmt); + + target_table = relation_open(table_id, NoLock); + + pg_policy_rel = table_open(PolicyRelationId, RowExclusiveLock); + + /* First pass -- check for conflict */ + + /* Add key - policy's relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Add key - policy's name. */ + ScanKeyInit(&skey[1], + Anum_pg_policy_polname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->newname)); + + sscan = systable_beginscan(pg_policy_rel, + PolicyPolrelidPolnameIndexId, true, NULL, 2, + skey); + + if (HeapTupleIsValid(systable_getnext(sscan))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("policy \"%s\" for table \"%s\" already exists", + stmt->newname, RelationGetRelationName(target_table)))); + + systable_endscan(sscan); + + /* Second pass -- find existing policy and update */ + /* Add key - policy's relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(table_id)); + + /* Add key - policy's name. */ + ScanKeyInit(&skey[1], + Anum_pg_policy_polname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(stmt->subname)); + + sscan = systable_beginscan(pg_policy_rel, + PolicyPolrelidPolnameIndexId, true, NULL, 2, + skey); + + policy_tuple = systable_getnext(sscan); + + /* Complain if we did not find the policy */ + if (!HeapTupleIsValid(policy_tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("policy \"%s\" for table \"%s\" does not exist", + stmt->subname, RelationGetRelationName(target_table)))); + + opoloid = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; + + policy_tuple = heap_copytuple(policy_tuple); + + namestrcpy(&((Form_pg_policy) GETSTRUCT(policy_tuple))->polname, + stmt->newname); + + CatalogTupleUpdate(pg_policy_rel, &policy_tuple->t_self, policy_tuple); + + InvokeObjectPostAlterHook(PolicyRelationId, opoloid, 0); + + ObjectAddressSet(address, PolicyRelationId, opoloid); + + /* + * Invalidate relation's relcache entry so that other backends (and this + * one too!) are sent SI message to make them rebuild relcache entries. + * (Ideally this should happen automatically...) + */ + CacheInvalidateRelcache(target_table); + + /* Clean up. */ + systable_endscan(sscan); + table_close(pg_policy_rel, RowExclusiveLock); + relation_close(target_table, NoLock); + + return address; +} + +/* + * get_relation_policy_oid - Look up a policy by name to find its OID + * + * If missing_ok is false, throw an error if policy not found. If + * true, just return InvalidOid. + */ +Oid +get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok) +{ + Relation pg_policy_rel; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple policy_tuple; + Oid policy_oid; + + pg_policy_rel = table_open(PolicyRelationId, AccessShareLock); + + /* Add key - policy's relation id. */ + ScanKeyInit(&skey[0], + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + /* Add key - policy's name. */ + ScanKeyInit(&skey[1], + Anum_pg_policy_polname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(policy_name)); + + sscan = systable_beginscan(pg_policy_rel, + PolicyPolrelidPolnameIndexId, true, NULL, 2, + skey); + + policy_tuple = systable_getnext(sscan); + + if (!HeapTupleIsValid(policy_tuple)) + { + if (!missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("policy \"%s\" for table \"%s\" does not exist", + policy_name, get_rel_name(relid)))); + + policy_oid = InvalidOid; + } + else + policy_oid = ((Form_pg_policy) GETSTRUCT(policy_tuple))->oid; + + /* Clean up. */ + systable_endscan(sscan); + table_close(pg_policy_rel, AccessShareLock); + + return policy_oid; +} + +/* + * relation_has_policies - Determine if relation has any policies + */ +bool +relation_has_policies(Relation rel) +{ + Relation catalog; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple policy_tuple; + bool ret = false; + + catalog = table_open(PolicyRelationId, AccessShareLock); + ScanKeyInit(&skey, + Anum_pg_policy_polrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true, + NULL, 1, &skey); + policy_tuple = systable_getnext(sscan); + if (HeapTupleIsValid(policy_tuple)) + ret = true; + + systable_endscan(sscan); + table_close(catalog, AccessShareLock); + + return ret; +} |