1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-22 23:02:54 +03:00
Tom Lane ab02896510 Provide CatalogTupleDelete() as a wrapper around simple_heap_delete().
This extends the work done in commit 2f5c9d9c9 to provide a more nearly
complete abstraction layer hiding the details of index updating for catalog
changes.  That commit only invented abstractions for catalog inserts and
updates, leaving nearby code for catalog deletes still calling the
heap-level routines directly.  That seems rather ugly from here, and it
does little to help if we ever want to shift to a storage system in which
indexing work is needed at delete time.

Hence, create a wrapper function CatalogTupleDelete(), and replace calls
of simple_heap_delete() on catalog tuples with it.  There are now very
few direct calls of [simple_]heap_delete remaining in the tree.

Discussion: https://postgr.es/m/462.1485902736@sss.pgh.pa.us
2017-02-01 16:13:30 -05:00

1386 lines
38 KiB
C

/*-------------------------------------------------------------------------
*
* policy.c
* Commands for manipulating policies.
*
* Portions Copyright (c) 1996-2017, 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/heapam.h"
#include "access/htup.h"
#include "access/htup_details.h"
#include "access/sysattr.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, ACL_KIND_CLASS, 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.
*/
void
RelationBuildRowSecurity(Relation relation)
{
MemoryContext rscxt;
MemoryContext oldcxt = CurrentMemoryContext;
RowSecurityDesc *volatile rsdesc = NULL;
/*
* 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.
*/
rscxt = AllocSetContextCreate(CacheMemoryContext,
"row security descriptor",
ALLOCSET_SMALL_SIZES);
/*
* Since rscxt lives under CacheMemoryContext, it is long-lived. Use a
* PG_TRY block to ensure it'll get freed if we fail partway through.
*/
PG_TRY();
{
Relation catalog;
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple tuple;
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
rsdesc->rscxt = rscxt;
catalog = heap_open(PolicyRelationId, AccessShareLock);
ScanKeyInit(&skey,
Anum_pg_policy_polrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationGetRelid(relation)));
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
NULL, 1, &skey);
/*
* Loop through the row level security policies for this relation, if
* any.
*/
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
{
Datum value_datum;
char cmd_value;
bool permissive_value;
Datum roles_datum;
char *qual_value;
Expr *qual_expr;
char *with_check_value;
Expr *with_check_qual;
char *policy_name_value;
bool isnull;
RowSecurityPolicy *policy;
/*
* Note: all the pass-by-reference data we collect here is either
* still stored in the tuple, or constructed in the caller's
* short-lived memory context. We must copy it into rscxt
* explicitly below.
*/
/* Get policy command */
value_datum = heap_getattr(tuple, Anum_pg_policy_polcmd,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
cmd_value = DatumGetChar(value_datum);
/* Get policy permissive or restrictive */
value_datum = heap_getattr(tuple, Anum_pg_policy_polpermissive,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
permissive_value = DatumGetBool(value_datum);
/* Get policy name */
value_datum = heap_getattr(tuple, Anum_pg_policy_polname,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
policy_name_value = NameStr(*(DatumGetName(value_datum)));
/* Get policy roles */
roles_datum = heap_getattr(tuple, Anum_pg_policy_polroles,
RelationGetDescr(catalog), &isnull);
/* shouldn't be null, but initdb doesn't mark it so, so check */
if (isnull)
elog(ERROR, "unexpected null value in pg_policy.polroles");
/* Get policy qual */
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
RelationGetDescr(catalog), &isnull);
if (!isnull)
{
qual_value = TextDatumGetCString(value_datum);
qual_expr = (Expr *) stringToNode(qual_value);
}
else
qual_expr = NULL;
/* Get WITH CHECK qual */
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
RelationGetDescr(catalog), &isnull);
if (!isnull)
{
with_check_value = TextDatumGetCString(value_datum);
with_check_qual = (Expr *) stringToNode(with_check_value);
}
else
with_check_qual = NULL;
/* Now copy everything into the cache context */
MemoryContextSwitchTo(rscxt);
policy = palloc0(sizeof(RowSecurityPolicy));
policy->policy_name = pstrdup(policy_name_value);
policy->polcmd = cmd_value;
policy->permissive = permissive_value;
policy->roles = DatumGetArrayTypePCopy(roles_datum);
policy->qual = copyObject(qual_expr);
policy->with_check_qual = copyObject(with_check_qual);
policy->hassublinks = checkExprHasSubLink((Node *) qual_expr) ||
checkExprHasSubLink((Node *) with_check_qual);
rsdesc->policies = lcons(policy, rsdesc->policies);
MemoryContextSwitchTo(oldcxt);
/* clean up some (not all) of the junk ... */
if (qual_expr != NULL)
pfree(qual_expr);
if (with_check_qual != NULL)
pfree(with_check_qual);
}
systable_endscan(sscan);
heap_close(catalog, AccessShareLock);
}
PG_CATCH();
{
/* Delete rscxt, first making sure it isn't active */
MemoryContextSwitchTo(oldcxt);
MemoryContextDelete(rscxt);
PG_RE_THROW();
}
PG_END_TRY();
/* Success --- attach the policy descriptor to the relcache entry */
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 = heap_open(PolicyRelationId, RowExclusiveLock);
/*
* Find the policy to delete.
*/
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);
/* 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 = heap_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);
heap_close(rel, NoLock);
/* Clean up */
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);
CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, 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, NoLock);
heap_close(pg_policy_rel, RowExclusiveLock);
return (noperm || num_roles > 0);
}
/*
* 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;
RangeTblEntry *rte;
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, 'i');
/* 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,
false, false,
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 */
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
NULL, false, false);
addRTEtoQuery(qual_pstate, rte, false, true, true);
/* Add for the with-check quals */
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
NULL, false, false);
addRTEtoQuery(with_check_pstate, rte, false, true, true);
qual = transformWhereClause(qual_pstate,
copyObject(stmt->qual),
EXPR_KIND_POLICY,
"POLICY");
with_check_qual = transformWhereClause(with_check_pstate,
copyObject(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 = heap_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))));
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);
policy_id = 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);
heap_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, 'i');
}
/* Get id of table. Also handles permissions checks. */
table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
false, false,
RangeVarCallbackForPolicy,
(void *) stmt);
target_table = relation_open(table_id, NoLock);
/* Parse the using policy clause */
if (stmt->qual)
{
RangeTblEntry *rte;
ParseState *qual_pstate = make_parsestate(NULL);
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
NULL, false, false);
addRTEtoQuery(qual_pstate, rte, false, true, true);
qual = transformWhereClause(qual_pstate, copyObject(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)
{
RangeTblEntry *rte;
ParseState *with_check_pstate = make_parsestate(NULL);
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
NULL, false, false);
addRTEtoQuery(with_check_pstate, rte, false, true, true);
with_check_qual = transformWhereClause(with_check_pstate,
copyObject(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 = heap_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 = HeapTupleGetOid(policy_tuple);
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 */
addRangeTableEntryForRelation(qual_pstate, target_table, 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 */
addRangeTableEntryForRelation(with_check_pstate, target_table, 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);
heap_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,
false, false,
RangeVarCallbackForPolicy,
(void *) stmt);
target_table = relation_open(table_id, NoLock);
pg_policy_rel = heap_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 = HeapTupleGetOid(policy_tuple);
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,
HeapTupleGetOid(policy_tuple), 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);
heap_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 = heap_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 = HeapTupleGetOid(policy_tuple);
/* Clean up. */
systable_endscan(sscan);
heap_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 = heap_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);
heap_close(catalog, AccessShareLock);
return ret;
}