mirror of
https://github.com/postgres/postgres.git
synced 2025-04-20 00:42:27 +03:00
1390 lines
38 KiB
C
1390 lines
38 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* policy.c
|
|
* Commands for manipulating policies.
|
|
*
|
|
* Portions Copyright (c) 1996-2018, 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, 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.
|
|
*/
|
|
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;
|
|
|
|
MemoryContextCopyAndSetIdentifier(rscxt,
|
|
RelationGetRelationName(relation));
|
|
|
|
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 &&
|
|
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))));
|
|
|
|
/* 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,
|
|
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 */
|
|
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,
|
|
0,
|
|
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,
|
|
0,
|
|
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;
|
|
}
|