heap_close(pg_policy_rel, RowExclusiveLock);
}
+/*
+ * RemoveRoleFromObjectPolicy -
+ * remove a role from a policy by its OID. If the role is not a member of
+ * the policy then an error is raised. False is returned to indicate that
+ * the role could not be removed due to being the only role on the policy
+ * and therefore the entire policy should be removed.
+ *
+ * Note that a warning will be thrown and true will be returned on a
+ * permission error, as the policy should not be removed in that case.
+ *
+ * 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;
+ Relation rel;
+ ArrayType *policy_roles;
+ int num_roles;
+ Datum roles_datum;
+ bool attr_isnull;
+ bool noperm = true;
+
+ Assert(classid == PolicyRelationId);
+
+ pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
+
+ /*
+ * Find the policy to update.
+ */
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ 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);
+
+ /*
+ * Open and exclusive-lock the relation the policy belongs to.
+ */
+ relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ if (rel->rd_rel->relkind != RELKIND_RELATION)
+ 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))));
+
+ /* 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);
+
+ /* We should be removing exactly one entry from the roles array */
+ num_roles = ARR_DIMS(policy_roles)[0] - 1;
+
+ Assert(num_roles >= 0);
+
+ /* Must own relation. */
+ if (pg_class_ownercheck(relid, GetUserId()))
+ noperm = false; /* user is allowed to modify this policy */
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
+ errmsg("role \"%s\" could not be removed from policy \"%s\" on \"%s\"",
+ GetUserNameFromId(roleid, false),
+ NameStr(((Form_pg_policy) GETSTRUCT(tuple))->polname),
+ RelationGetRelationName(rel))));
+
+ /*
+ * If multiple roles exist on this policy, then remove the one we were
+ * asked to and leave the rest.
+ */
+ if (!noperm && num_roles > 0)
+ {
+ int i, j;
+ Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
+ Datum *role_oids;
+ char *qual_value;
+ Node *qual_expr;
+ List *qual_parse_rtable = NIL;
+ char *with_check_value;
+ Node *with_check_qual;
+ List *with_check_parse_rtable = NIL;
+ Datum values[Natts_pg_policy];
+ bool isnull[Natts_pg_policy];
+ bool replaces[Natts_pg_policy];
+ Datum value_datum;
+ ArrayType *role_ids;
+ HeapTuple new_tuple;
+ ObjectAddress target;
+ ObjectAddress myself;
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(replaces, 0, sizeof(replaces));
+ memset(isnull, 0, sizeof(isnull));
+
+ /*
+ * All of the dependencies will be removed from the policy and then
+ * re-added. In order to get them correct, we need to extract out
+ * the expressions in the policy and construct a parsestate just
+ * enough to build the range table(s) to then pass to
+ * recordDependencyOnExpr().
+ */
+
+ /* Get policy qual, to update dependencies */
+ value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
+ RelationGetDescr(pg_policy_rel), &attr_isnull);
+ if (!attr_isnull)
+ {
+ ParseState *qual_pstate;
+
+ /* parsestate is built just to build the range table */
+ qual_pstate = make_parsestate(NULL);
+
+ qual_value = TextDatumGetCString(value_datum);
+ qual_expr = stringToNode(qual_value);
+
+ /* Add this rel to the parsestate's rangetable, for dependencies */
+ addRangeTableEntryForRelation(qual_pstate, rel, NULL, false, false);
+
+ qual_parse_rtable = qual_pstate->p_rtable;
+ free_parsestate(qual_pstate);
+ }
+ else
+ qual_expr = NULL;
+
+ /* Get WITH CHECK qual, to update dependencies */
+ value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
+ RelationGetDescr(pg_policy_rel), &attr_isnull);
+ if (!attr_isnull)
+ {
+ 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 */
+ addRangeTableEntryForRelation(with_check_pstate, rel, NULL, false,
+ false);
+
+ with_check_parse_rtable = with_check_pstate->p_rtable;
+ free_parsestate(with_check_pstate);
+ }
+ else
+ with_check_qual = NULL;
+
+ /* Rebuild the roles array to then update the pg_policy tuple with */
+ role_oids = (Datum *) palloc(num_roles * sizeof(Datum));
+ for (i = 0, j = 0; i < ARR_DIMS(policy_roles)[0]; i++)
+ /* Copy over all of the roles which are not the one being removed */
+ if (roles[i] != roleid)
+ role_oids[j++] = ObjectIdGetDatum(roles[i]);
+
+ /* We should have only removed the one role */
+ Assert(j == num_roles);
+
+ /* This is the array for the new tuple */
+ role_ids = construct_array(role_oids, num_roles, OIDOID,
+ sizeof(Oid), true, 'i');
+
+ 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);
+ simple_heap_update(pg_policy_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Catalog Indexes */
+ CatalogUpdateIndexes(pg_policy_rel, new_tuple);
+
+ /* Remove all old dependencies. */
+ deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
+
+ /* Record the new set of dependencies */
+ target.classId = RelationRelationId;
+ target.objectId = relid;
+ target.objectSubId = 0;
+
+ myself.classId = PolicyRelationId;
+ myself.objectId = policy_id;
+ myself.objectSubId = 0;
+
+ recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+ if (qual_expr)
+ recordDependencyOnExpr(&myself, qual_expr, qual_parse_rtable,
+ DEPENDENCY_NORMAL);
+
+ if (with_check_qual)
+ recordDependencyOnExpr(&myself, with_check_qual,
+ with_check_parse_rtable,
+ DEPENDENCY_NORMAL);
+
+ /* Remove all the old shared dependencies (roles) */
+ deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0);
+
+ /* Record the new shared dependencies (roles) */
+ 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);
+
+ /* Invalidate Relation Cache */
+ CacheInvalidateRelcache(rel);
+ }
+
+ /* Clean up. */
+ systable_endscan(sscan);
+ relation_close(rel, AccessExclusiveLock);
+ heap_close(pg_policy_rel, RowExclusiveLock);
+
+ return(noperm || num_roles > 0);
+}
+
/*
* CreatePolicy -
* handles the execution of the CREATE POLICY command.