citus/src/backend/distributed/commands/grant.c

291 lines
7.6 KiB
C

/*-------------------------------------------------------------------------
*
* grant.c
* Commands for granting access to distributed tables.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "utils/lsyscache.h"
#include "distributed/citus_ruleutils.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_cache.h"
#include "distributed/version_compat.h"
/* Local functions forward declarations for helper functions */
static List * CollectGrantTableIdList(GrantStmt *grantStmt);
/*
* PreprocessGrantStmt determines whether a given GRANT/REVOKE statement involves
* a distributed table. If so, it creates DDLJobs to encapsulate information
* needed during the worker node portion of DDL execution before returning the
* DDLJobs in a List. If no distributed table is involved, this returns NIL.
*
*/
List *
PreprocessGrantStmt(Node *node, const char *queryString,
ProcessUtilityContext processUtilityContext)
{
GrantStmt *grantStmt = castNode(GrantStmt, node);
StringInfoData privsString;
StringInfoData granteesString;
StringInfoData targetString;
StringInfoData ddlString;
ListCell *granteeCell = NULL;
ListCell *tableListCell = NULL;
bool isFirst = true;
List *ddlJobs = NIL;
initStringInfo(&privsString);
initStringInfo(&granteesString);
initStringInfo(&targetString);
initStringInfo(&ddlString);
/*
* So far only table level grants are supported. Most other types of
* grants aren't interesting anyway.
*/
if (grantStmt->objtype != OBJECT_TABLE)
{
return NIL;
}
List *tableIdList = CollectGrantTableIdList(grantStmt);
/* nothing to do if there is no distributed table in the grant list */
if (tableIdList == NIL)
{
return NIL;
}
EnsureCoordinator();
/* deparse the privileges */
if (grantStmt->privileges == NIL)
{
/* this is used for table level only */
appendStringInfo(&privsString, "ALL");
}
else
{
ListCell *privilegeCell = NULL;
isFirst = true;
foreach(privilegeCell, grantStmt->privileges)
{
AccessPriv *priv = lfirst(privilegeCell);
if (!isFirst)
{
appendStringInfoString(&privsString, ", ");
}
if (priv->priv_name)
{
appendStringInfo(&privsString, "%s", priv->priv_name);
}
/*
* ALL can only be set alone.
* And ALL is not added as a keyword in priv_name by parser, but
* because there are column(s) defined, a grantStmt->privileges is
* defined. So we need to handle this special case here (see if
* condition above).
*/
else if (isFirst)
{
/* this is used for column level only */
appendStringInfo(&privsString, "ALL");
}
/*
* Instead of relying only on the syntax check done by Postgres and
* adding an assert here, add a default ERROR if ALL is not first
* and no priv_name is defined.
*/
else
{
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Cannot parse GRANT/REVOKE privileges")));
}
isFirst = false;
if (priv->cols != NIL)
{
StringInfoData colsString;
initStringInfo(&colsString);
AppendColumnNameList(&colsString, priv->cols);
appendStringInfo(&privsString, "%s", colsString.data);
}
}
}
/* deparse the grantees */
isFirst = true;
foreach(granteeCell, grantStmt->grantees)
{
RoleSpec *spec = lfirst(granteeCell);
if (!isFirst)
{
appendStringInfoString(&granteesString, ", ");
}
isFirst = false;
appendStringInfoString(&granteesString, RoleSpecString(spec, true));
}
/*
* Deparse the target objects, and issue the deparsed statements to
* workers, if applicable. That's so we easily can replicate statements
* only to distributed relations.
*/
isFirst = true;
foreach(tableListCell, tableIdList)
{
Oid relationId = lfirst_oid(tableListCell);
const char *grantOption = "";
resetStringInfo(&targetString);
appendStringInfo(&targetString, "%s", generate_relation_name(relationId, NIL));
if (grantStmt->is_grant)
{
if (grantStmt->grant_option)
{
grantOption = " WITH GRANT OPTION";
}
appendStringInfo(&ddlString, "GRANT %s ON %s TO %s%s",
privsString.data, targetString.data, granteesString.data,
grantOption);
}
else
{
if (grantStmt->grant_option)
{
grantOption = "GRANT OPTION FOR ";
}
appendStringInfo(&ddlString, "REVOKE %s%s ON %s FROM %s",
grantOption, privsString.data, targetString.data,
granteesString.data);
if (grantStmt->behavior == DROP_CASCADE)
{
appendStringInfoString(&ddlString, " CASCADE");
}
else
{
appendStringInfoString(&ddlString, " RESTRICT");
}
}
DDLJob *ddlJob = palloc0(sizeof(DDLJob));
ObjectAddressSet(ddlJob->targetObjectAddress, RelationRelationId, relationId);
ddlJob->metadataSyncCommand = pstrdup(ddlString.data);
ddlJob->taskList = NIL;
if (IsCitusTable(relationId))
{
ddlJob->taskList = DDLTaskList(relationId, ddlString.data);
}
ddlJobs = lappend(ddlJobs, ddlJob);
resetStringInfo(&ddlString);
}
return ddlJobs;
}
/*
* CollectGrantTableIdList determines and returns a list of distributed table
* Oids from grant statement.
* Grant statement may appear in two forms
* 1 - grant on table:
* each distributed table oid in grant object list is added to returned list.
* 2 - grant all tables in schema:
* Collect namespace oid list from grant statement
* Add each distributed table oid in the target namespace list to the returned list.
*/
static List *
CollectGrantTableIdList(GrantStmt *grantStmt)
{
List *grantTableList = NIL;
bool grantOnTableCommand = (grantStmt->targtype == ACL_TARGET_OBJECT &&
grantStmt->objtype == OBJECT_TABLE);
bool grantAllTablesOnSchemaCommand = (grantStmt->targtype ==
ACL_TARGET_ALL_IN_SCHEMA &&
grantStmt->objtype == OBJECT_TABLE);
/* we are only interested in table level grants */
if (!grantOnTableCommand && !grantAllTablesOnSchemaCommand)
{
return NIL;
}
if (grantAllTablesOnSchemaCommand)
{
List *citusTableIdList = CitusTableTypeIdList(ANY_CITUS_TABLE_TYPE);
ListCell *citusTableIdCell = NULL;
List *namespaceOidList = NIL;
ListCell *objectCell = NULL;
foreach(objectCell, grantStmt->objects)
{
char *nspname = strVal(lfirst(objectCell));
bool missing_ok = false;
Oid namespaceOid = get_namespace_oid(nspname, missing_ok);
Assert(namespaceOid != InvalidOid);
namespaceOidList = list_append_unique_oid(namespaceOidList, namespaceOid);
}
foreach(citusTableIdCell, citusTableIdList)
{
Oid relationId = lfirst_oid(citusTableIdCell);
Oid namespaceOid = get_rel_namespace(relationId);
if (list_member_oid(namespaceOidList, namespaceOid))
{
grantTableList = lappend_oid(grantTableList, relationId);
}
}
}
else
{
ListCell *objectCell = NULL;
foreach(objectCell, grantStmt->objects)
{
RangeVar *relvar = (RangeVar *) lfirst(objectCell);
Oid relationId = RangeVarGetRelid(relvar, NoLock, false);
if (IsCitusTable(relationId))
{
grantTableList = lappend_oid(grantTableList, relationId);
continue;
}
/* check for distributed sequences included in GRANT ON TABLE statement */
ObjectAddress *sequenceAddress = palloc0(sizeof(ObjectAddress));
ObjectAddressSet(*sequenceAddress, RelationRelationId, relationId);
if (IsAnyObjectDistributed(list_make1(sequenceAddress)))
{
grantTableList = lappend_oid(grantTableList, relationId);
}
}
}
return grantTableList;
}