blob: f8fc301f60ec08d66b2bef047f7ce6dd8f8436a5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* user.c
* Commands for manipulating roles (formerly called users).
*
* Portions Copyright (c) 2005-2010, Greenplum inc
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.174.2.1 2010/03/25 14:45:21 alvherre Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/xact.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/indexing.h"
#include "catalog/pg_auth_time_constraint.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_resqueue.h"
#include "commands/comment.h"
#include "commands/user.h"
#include "libpq/auth.h"
#include "libpq/password_hash.h"
#include "libpq/md5.h"
#include "libpq/pg_sha2.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "executor/execdesc.h"
#include "utils/resscheduler.h"
#include "utils/syscache.h"
#include "cdb/cdbdisp.h"
#include "cdb/cdbsrlz.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbcat.h"
#include "cdb/dispatcher.h"
#include "resourcemanager/communication/rmcomm_QD2RM.h"
#include "resourcemanager/errorcode.h"
typedef struct genericPair
{
char* key1;
char* val1;
char* key2;
char* val2;
} genericPair;
typedef struct extAuthPair
{
char* protocol;
char* type;
} extAuthPair;
extern bool Password_encryption;
static List *roleNamesToIds(List *memberNames);
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
Oid grantorId, bool admin_opt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
bool admin_opt);
static void TransformExttabAuthClause(DefElem *defel,
extAuthPair *extauth);
static void SetCreateExtTableForRole(List* allow,
List* disallow, bool* createrextgpfd,
bool* createrexthttp, bool* createwextgpfd,
bool* createrexthdfs, bool* createwexthdfs);
static char *daysofweek[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
static int16 ExtractAuthInterpretDay(Value * day);
static void ExtractAuthIntervalClause(DefElem *defel,
authInterval *authInterval);
static void AddRoleDenials(const char *rolename, Oid roleid,
List *addintervals);
static void DelRoleDenials(const char *rolename, Oid roleid,
List *dropintervals);
static bool resourceQueueIsBranch(Oid queueid);
/* Check if current user has createrole privileges */
static bool
have_createrole_privilege(void)
{
bool result = false;
cqContext *pcqCtx, cqc;
HeapTuple utup;
/* Superusers can always do everything */
if (superuser())
return true;
pcqCtx =
caql_beginscan(
cqclr(&cqc),
cql("SELECT * FROM pg_authid "
" WHERE oid = :1 ",
ObjectIdGetDatum(GetUserId())));
if (HeapTupleIsValid(utup = caql_getnext(pcqCtx)))
{
result = ((Form_pg_authid) GETSTRUCT(utup))->rolcreaterole;
}
caql_endscan(pcqCtx);
return result;
}
/*
* If a resource queue(oid) is a branch
*/
static bool resourceQueueIsBranch(Oid queueid)
{
HeapTuple tuple = NULL;
cqContext cqc;
cqContext *pcqCtx;
Relation pg_resqueue_rel;
bool res = false;
Datum status;
bool isNull = false;
Assert(queueid != InvalidOid);
pg_resqueue_rel = heap_open(ResQueueRelationId, RowExclusiveLock);
pcqCtx = caql_addrel(cqclr(&cqc), pg_resqueue_rel);
tuple = caql_getfirst(pcqCtx,
cql("SELECT * FROM pg_resqueue WHERE oid = :1",
ObjectIdGetDatum(queueid)));
Assert(tuple != NULL);
status = heap_getattr(tuple, Anum_pg_resqueue_status, RelationGetDescr(pg_resqueue_rel), &isNull);
if (!isNull && strncmp(TextDatumGetCString(status), "branch", strlen("branch")) == 0)
res = true;
heap_close(pg_resqueue_rel, NoLock);
return res;
}
/*
* CREATE ROLE
*/
void
CreateRole(CreateRoleStmt *stmt)
{
Relation pg_authid_rel;
HeapTuple tuple;
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
Oid roleid;
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
bool encrypt_password = Password_encryption; /* encrypt password? */
char encrypted_password[MAX_PASSWD_HASH_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
bool createdb = false; /* Can the user create databases? */
bool canlogin = false; /* Can this user login? */
bool createrextgpfd = false; /* Can create readable gpfdist exttab? */
bool createrexthttp = false; /* Can create readable http exttab? */
bool createwextgpfd = false; /* Can create writable gpfdist exttab? */
bool createrexthdfs = false; /* Can create readable gphdfs exttab? */
bool createwexthdfs = false; /* Can create writable gphdfs exttab? */
int connlimit = -1; /* maximum connections allowed */
List *addroleto = NIL; /* roles to make this a member of */
List *rolemembers = NIL; /* roles to be members of this role */
List *adminmembers = NIL; /* roles to be admins of this role */
List *exttabcreate = NIL; /* external table create privileges being added */
List *exttabnocreate = NIL; /* external table create privileges being removed */
char *validUntil = NULL; /* time the login is valid until */
char *resqueue = NULL; /* resource queue for this role */
List *addintervals = NIL; /* list of time intervals for which login should be denied */
DefElem *dpassword = NULL;
DefElem *dresqueue = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *dconnlimit = NULL;
DefElem *daddroleto = NULL;
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
cqContext cqc;
cqContext cqc2;
cqContext *pcqCtx;
Oid queueid = InvalidOid;
int res = FUNC_RETURN_OK;
static char errorbuf[1024] = "";
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
{
case ROLESTMT_ROLE:
break;
case ROLESTMT_USER:
canlogin = true;
/* may eventually want inherit to default to false here */
break;
case ROLESTMT_GROUP:
break;
}
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
if (Gp_role != GP_ROLE_EXECUTE)
ereport(NOTICE,
(errmsg("SYSID can no longer be specified")));
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcanlogin = defel;
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dconnlimit = defel;
}
else if (strcmp(defel->defname, "addroleto") == 0)
{
if (daddroleto)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
daddroleto = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0)
{
if (drolemembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
drolemembers = defel;
}
else if (strcmp(defel->defname, "adminmembers") == 0)
{
if (dadminmembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dadminmembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "resourceQueue") == 0)
{
if (dresqueue)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dresqueue = defel;
}
else if (strcmp(defel->defname, "exttabauth") == 0)
{
extAuthPair *extauth = (extAuthPair *) palloc0 (2 * sizeof(char *));
TransformExttabAuthClause(defel, extauth);
/* now actually append our transformed key value pairs to the list */
exttabcreate = lappend(exttabcreate, extauth);
}
else if (strcmp(defel->defname, "exttabnoauth") == 0)
{
extAuthPair *extauth = (extAuthPair *) palloc0 (2 * sizeof(char *));
TransformExttabAuthClause(defel, extauth);
/* now actually append our transformed key value pairs to the list */
exttabnocreate = lappend(exttabnocreate, extauth);
}
else if (strcmp(defel->defname, "deny") == 0)
{
authInterval *interval = (authInterval *) palloc0(sizeof(authInterval));
ExtractAuthIntervalClause(defel, interval);
addintervals = lappend(addintervals, interval);
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
inherit = intVal(dinherit->arg) != 0;
if (dcreaterole)
createrole = intVal(dcreaterole->arg) != 0;
if (dcreatedb)
createdb = intVal(dcreatedb->arg) != 0;
if (dcanlogin)
canlogin = intVal(dcanlogin->arg) != 0;
if (dconnlimit)
connlimit = intVal(dconnlimit->arg);
if (daddroleto)
addroleto = (List *) daddroleto->arg;
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
if (dadminmembers)
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dresqueue)
resqueue = strVal(linitial((List *) dresqueue->arg));
else
{
/* MPP-6926: resource queue required -- use default queue */
if (Gp_role == GP_ROLE_DISPATCH)
{
/* MPP-7587: don't complain if you CREATE a superuser,
* who doesn't use the queue
*/
if (!issuper)
ereport(NOTICE,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("resource queue required -- "
"using default resource queue \"%s\"",
GP_DEFAULT_RESOURCE_QUEUE_NAME)));
}
resqueue = pstrdup(GP_DEFAULT_RESOURCE_QUEUE_NAME);
}
/* Check some permissions first */
if (issuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create superusers")));
}
else
{
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role")));
}
if (strcmp(stmt->role, "public") == 0 ||
strcmp(stmt->role, "none") == 0)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role)));
/* Create a new resource context to manipulate role in resource manager. */
int resourceid = 0;
res = createNewResourceContext(&resourceid);
if (res != FUNC_RETURN_OK) {
Assert( res == COMM2RM_CLIENT_FULL_RESOURCECONTEXT );
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Can not apply CREATE ROLE. "
"Because too many resource contexts were created.")));
}
/* Here, using user oid is more convenient. */
res = registerConnectionInRMByOID(resourceid,
GetUserId(),
errorbuf,
sizeof(errorbuf));
if (res != FUNC_RETURN_OK)
{
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("%s", errorbuf)));
}
/*
* Check the pg_authid relation to be certain the role doesn't already
* exist.
*/
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pcqCtx =
caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authid_rel),
cql("INSERT INTO pg_authid ",
NULL));
if (caql_getcount(
caql_addrel(cqclr(&cqc2), pg_authid_rel),
cql("SELECT COUNT(*) FROM pg_authid "
" WHERE rolname = :1 ",
PointerGetDatum(stmt->role)))) {
unregisterConnectionInRMWithErrorReport(resourceid);
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists",
stmt->role)));
}
/*
* Build a tuple to insert
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
new_record[Anum_pg_authid_rolname - 1] =
DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
/* superuser gets catupdate right by default */
new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
/* Set the CREATE EXTERNAL TABLE permissions for this role */
if (exttabcreate || exttabnocreate)
SetCreateExtTableForRole(exttabcreate, exttabnocreate, &createrextgpfd,
&createrexthttp, &createwextgpfd,
&createrexthdfs, &createwexthdfs);
new_record[Anum_pg_authid_rolcreaterextgpfd - 1] = BoolGetDatum(createrextgpfd);
new_record[Anum_pg_authid_rolcreaterexthttp - 1] = BoolGetDatum(createrexthttp);
new_record[Anum_pg_authid_rolcreatewextgpfd - 1] = BoolGetDatum(createwextgpfd);
new_record[Anum_pg_authid_rolcreaterexthdfs - 1] = BoolGetDatum(createrexthdfs);
new_record[Anum_pg_authid_rolcreatewexthdfs - 1] = BoolGetDatum(createwexthdfs);
if (password)
{
if (!encrypt_password || isHashedPasswd(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
{
if (!hash_password(password, stmt->role, strlen(stmt->role),
encrypted_password))
{
elog(ERROR, "password encryption failed");
}
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(encrypted_password);
}
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
if (validUntil)
new_record[Anum_pg_authid_rolvaliduntil - 1] =
DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
else
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = true;
if (resqueue)
{
if (strcmp(resqueue, "none") == 0)
{
unregisterConnectionInRMWithErrorReport(resourceid);
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("resource queue name \"%s\" is reserved",
resqueue), errOmitLocation(true)));
}
queueid = GetResQueueIdForName(resqueue);
if (queueid == InvalidOid)
{
unregisterConnectionInRMWithErrorReport(resourceid);
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("resource queue \"%s\" does not exist",
resqueue), errOmitLocation(true)));
}
if(resourceQueueIsBranch(queueid))
{
unregisterConnectionInRMWithErrorReport(resourceid);
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("cannot assign non-leaf resource queue \"%s\" to role",
resqueue), errOmitLocation(true)));
}
new_record[Anum_pg_authid_rolresqueue - 1] =
ObjectIdGetDatum(queueid);
}
else
new_record_nulls[Anum_pg_authid_rolresqueue - 1] = true;
new_record_nulls[Anum_pg_authid_rolconfig - 1] = true;
tuple = caql_form_tuple(pcqCtx, new_record, new_record_nulls);
if (stmt->roleOid != InvalidOid)
/* force tuple to have the desired OID */
HeapTupleSetOid(tuple, stmt->roleOid);
/*
* Insert new record in the pg_authid table
*/
roleid = caql_insert(pcqCtx, tuple); /* implicit update of index as well */
stmt->roleOid = roleid;
/*
* send RPC: notify RM to update
*/
if (resqueue && queueid != InvalidOid)
{
res = manipulateRoleForResourceQueue(resourceid,
roleid,
queueid,
MANIPULATE_ROLE_RESQUEUE_CREATE,
issuper,
stmt->role,
errorbuf,
sizeof(errorbuf));
}
/* We always unregister connection. */
unregisterConnectionInRMWithErrorReport(resourceid);
/* We always release resource context. */
releaseResourceContextWithErrorReport(resourceid);
if (resqueue && queueid != InvalidOid)
{
if ( res != FUNC_RETURN_OK )
{
ereport(ERROR,
(errcode(IS_TO_RM_RPC_ERROR(res) ?
ERRCODE_INTERNAL_ERROR :
ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot apply CREATE ROLE because of %s", errorbuf)));
}
}
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
/*
* Add the new role to the specified existing roles.
*/
foreach(item, addroleto)
{
char *oldrolename = strVal(lfirst(item));
Oid oldroleid = get_roleid_checked(oldrolename);
AddRoleMems(oldrolename, oldroleid,
list_make1(makeString(stmt->role)),
list_make1_oid(roleid),
GetUserId(), false);
}
/*
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
AddRoleMems(stmt->role, roleid,
adminmembers, roleNamesToIds(adminmembers),
GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
GetUserId(), false);
/*
* Populate pg_auth_time_constraint with intervals for which this
* particular role should be denied access.
*/
if (addintervals)
{
if (issuper)
ereport(ERROR,
(errmsg("cannot create superuser with DENY rules")));
AddRoleDenials(stmt->role, roleid, addintervals);
}
/*
* Close pg_authid, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
caql_endscan(pcqCtx);
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
if (Gp_role == GP_ROLE_DISPATCH)
{
Assert(stmt->type == T_CreateRoleStmt);
Assert(stmt->type < 1000);
/* GPSQL: no dispatch to segments */
/* CdbDispatchUtilityStatement((Node *) stmt, "CreateRole"); */
/* MPP-6929: metadata tracking */
MetaTrackAddObject(AuthIdRelationId,
roleid,
GetUserId(),
"CREATE", "ROLE"
);
}
}
/*
* ALTER ROLE
*
* Note: the rolemembers option accepted here is intended to support the
* backwards-compatible ALTER GROUP syntax. Although it will work to say
* "ALTER ROLE role ROLE rolenames", we don't document it.
*/
void
AlterRole(AlterRoleStmt *stmt)
{
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
bool new_record_repl[Natts_pg_authid];
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
new_tuple;
ListCell *option;
char *password = NULL; /* user password */
bool encrypt_password
= Password_encryption; /* encrypt password? */
char encrypted_password[MAX_PASSWD_HASH_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
int createdb = -1; /* Can the user create databases? */
int canlogin = -1; /* Can this user login? */
int connlimit = -1; /* maximum connections allowed */
char *resqueue = NULL; /* resource queue for this role */
List *rolemembers = NIL; /* roles to be added/removed */
List *exttabcreate = NIL; /* external table create privileges being added */
List *exttabnocreate = NIL; /* external table create privileges being removed */
char *validUntil = NULL; /* time the login is valid until */
DefElem *dpassword = NULL;
DefElem *dresqueue = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *dconnlimit = NULL;
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
Oid roleid;
bool bWas_super = false; /* Was the user a superuser? */
int numopts = 0;
char *alter_subtype = ""; /* metadata tracking: kind of
redundant to say "role" */
bool createrextgpfd;
bool createrexthttp;
bool createwextgpfd;
bool createrexthdfs;
bool createwexthdfs;
List *addintervals = NIL; /* list of time intervals for which login should be denied */
List *dropintervals = NIL; /* list of time intervals for which matching rules should be dropped */
cqContext cqc;
cqContext *pcqCtx;
Oid queueid = InvalidOid;
int res = FUNC_RETURN_OK;
static char errorbuf[1024] = "";
numopts = list_length(stmt->options);
if (numopts > 1)
{
char allopts[NAMEDATALEN];
sprintf(allopts, "%d OPTIONS", numopts);
alter_subtype = pstrdup(allopts);
}
else if (0 == numopts)
{
alter_subtype = "0 OPTIONS";
}
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
if (1 == numopts) alter_subtype = "PASSWORD";
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dissuper = defel;
if (1 == numopts) alter_subtype = "SUPERUSER";
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dinherit = defel;
if (1 == numopts) alter_subtype = "INHERIT";
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dcreaterole = defel;
if (1 == numopts) alter_subtype = "CREATEROLE";
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dcreatedb = defel;
if (1 == numopts) alter_subtype = "CREATEDB";
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dcanlogin = defel;
if (1 == numopts) alter_subtype = "LOGIN";
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dconnlimit = defel;
if (1 == numopts) alter_subtype = "CONNECTION LIMIT";
}
else if (strcmp(defel->defname, "rolemembers") == 0 &&
stmt->action != 0)
{
if (drolemembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
drolemembers = defel;
if (1 == numopts) alter_subtype = "ROLE";
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dvalidUntil = defel;
if (1 == numopts) alter_subtype = "VALID UNTIL";
}
else if (strcmp(defel->defname, "resourceQueue") == 0)
{
if (dresqueue)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errOmitLocation(true)));
dresqueue = defel;
if (1 == numopts) alter_subtype = "RESOURCE QUEUE";
}
else if (strcmp(defel->defname, "exttabauth") == 0)
{
extAuthPair *extauth = (extAuthPair *) palloc0 (2 * sizeof(char *));
TransformExttabAuthClause(defel, extauth);
/* now actually append our transformed key value pairs to the list */
exttabcreate = lappend(exttabcreate, extauth);
if (1 == numopts) alter_subtype = "CREATEEXTTABLE";
}
else if (strcmp(defel->defname, "exttabnoauth") == 0)
{
extAuthPair *extauth = (extAuthPair *) palloc0 (2 * sizeof(char *));
TransformExttabAuthClause(defel, extauth);
/* now actually append our transformed key value pairs to the list */
exttabnocreate = lappend(exttabnocreate, extauth);
if (1 == numopts) alter_subtype = "NO CREATEEXTTABLE";
}
else if (strcmp(defel->defname, "deny") == 0)
{
authInterval *interval = (authInterval *) palloc0(sizeof(authInterval));
ExtractAuthIntervalClause(defel, interval);
addintervals = lappend(addintervals, interval);
}
else if (strcmp(defel->defname, "drop_deny") == 0)
{
authInterval *interval = (authInterval *) palloc0(sizeof(authInterval));
ExtractAuthIntervalClause(defel, interval);
dropintervals = lappend(dropintervals, interval);
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
inherit = intVal(dinherit->arg);
if (dcreaterole)
createrole = intVal(dcreaterole->arg);
if (dcreatedb)
createdb = intVal(dcreatedb->arg);
if (dcanlogin)
canlogin = intVal(dcanlogin->arg);
if (dconnlimit)
connlimit = intVal(dconnlimit->arg);
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dresqueue)
resqueue = strVal(linitial((List *) dresqueue->arg));
/*
* create a new context
*/
int resourceid = 0;
res = createNewResourceContext(&resourceid);
if ( res != FUNC_RETURN_OK )
{
Assert( res == COMM2RM_CLIENT_FULL_RESOURCECONTEXT );
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("too many existing resource context.")));
}
/* Here, using user oid is more convenient. */
res = registerConnectionInRMByOID(resourceid,
GetUserId(),
errorbuf,
sizeof(errorbuf));
if (res != FUNC_RETURN_OK)
{
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("%s", errorbuf)));
}
/*
* Scan the pg_authid relation to be certain the user exists.
*/
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authid_rel),
cql("SELECT * FROM pg_authid "
" WHERE rolname = :1 "
" FOR UPDATE ",
PointerGetDatum(stmt->role)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple)) {
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", stmt->role)));
}
roleid = HeapTupleGetOid(tuple);
/*
* To mess with a superuser you gotta be superuser; else you need
* createrole, or just want to change your own password
*/
bWas_super = ((Form_pg_authid) GETSTRUCT(tuple))->rolsuper;
if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper || issuper >= 0)
{
if (!superuser()) {
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superusers")));
}
}
else if (!have_createrole_privilege())
{
bool is_createrol_required = !(inherit < 0 &&
createrole < 0 &&
createdb < 0 &&
canlogin < 0 &&
!dconnlimit &&
!rolemembers &&
!validUntil &&
dpassword &&
!exttabcreate &&
!exttabnocreate &&
roleid == GetUserId());
if (is_createrol_required) {
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied"),
errOmitLocation(true)));
}
}
/*
* Build an updated tuple, perusing the information just obtained
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
/*
* issuper/createrole/catupdate/etc
*
* XXX It's rather unclear how to handle catupdate. It's probably best to
* keep it equal to the superuser status, otherwise you could end up with
* a situation where no existing superuser can alter the catalogs,
* including pg_authid!
*/
if (issuper >= 0)
{
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0);
new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true;
bWas_super = (issuper > 0); /* get current superuser status */
}
if (inherit >= 0)
{
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
}
if (createrole >= 0)
{
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);
new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
}
if (createdb >= 0)
{
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0);
new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
}
if (canlogin >= 0)
{
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0);
new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
}
if (dconnlimit)
{
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
new_record_repl[Anum_pg_authid_rolconnlimit - 1] = true;
}
/* password */
if (password)
{
if (!encrypt_password || isHashedPasswd(password))
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(password);
else
{
if (!hash_password(password, stmt->role, strlen(stmt->role),
encrypted_password))
elog(ERROR, "password encryption failed");
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(encrypted_password);
}
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
/* valid until */
if (validUntil)
{
new_record[Anum_pg_authid_rolvaliduntil - 1] =
DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
}
/* Set the CREATE EXTERNAL TABLE permissions for this role, if specified in ALTER */
if (exttabcreate || exttabnocreate)
{
bool isnull;
Datum dcreaterextgpfd;
Datum dcreaterexthttp;
Datum dcreatewextgpfd;
Datum dcreaterexthdfs;
Datum dcreatewexthdfs;
/*
* get bool values from catalog. we don't ever expect a NULL value, but just
* in case it is there (perhaps after an upgrade) we treat it as 'false'.
*/
dcreaterextgpfd = heap_getattr(tuple, Anum_pg_authid_rolcreaterextgpfd, pg_authid_dsc, &isnull);
createrextgpfd = (isnull ? false : DatumGetBool(dcreaterextgpfd));
dcreaterexthttp = heap_getattr(tuple, Anum_pg_authid_rolcreaterexthttp, pg_authid_dsc, &isnull);
createrexthttp = (isnull ? false : DatumGetBool(dcreaterexthttp));
dcreatewextgpfd = heap_getattr(tuple, Anum_pg_authid_rolcreatewextgpfd, pg_authid_dsc, &isnull);
createwextgpfd = (isnull ? false : DatumGetBool(dcreatewextgpfd));
dcreaterexthdfs = heap_getattr(tuple, Anum_pg_authid_rolcreaterexthdfs, pg_authid_dsc, &isnull);
createrexthdfs = (isnull ? false : DatumGetBool(dcreaterexthdfs));
dcreatewexthdfs = heap_getattr(tuple, Anum_pg_authid_rolcreatewexthdfs, pg_authid_dsc, &isnull);
createwexthdfs = (isnull ? false : DatumGetBool(dcreatewexthdfs));
SetCreateExtTableForRole(exttabcreate, exttabnocreate, &createrextgpfd,
&createrexthttp, &createwextgpfd,
&createrexthdfs, &createwexthdfs);
new_record[Anum_pg_authid_rolcreaterextgpfd - 1] = BoolGetDatum(createrextgpfd);
new_record_repl[Anum_pg_authid_rolcreaterextgpfd - 1] = true;
new_record[Anum_pg_authid_rolcreaterexthttp - 1] = BoolGetDatum(createrexthttp);
new_record_repl[Anum_pg_authid_rolcreaterexthttp - 1] = true;
new_record[Anum_pg_authid_rolcreatewextgpfd - 1] = BoolGetDatum(createwextgpfd);
new_record_repl[Anum_pg_authid_rolcreatewextgpfd - 1] = true;
new_record[Anum_pg_authid_rolcreaterexthdfs - 1] = BoolGetDatum(createrexthdfs);
new_record_repl[Anum_pg_authid_rolcreaterexthdfs - 1] = true;
new_record[Anum_pg_authid_rolcreatewexthdfs - 1] = BoolGetDatum(createwexthdfs);
new_record_repl[Anum_pg_authid_rolcreatewexthdfs - 1] = true;
}
/* resource queue */
if (resqueue)
{
/* MPP-6926: NONE not supported -- use default queue */
if (
/* ( 0 == pg_strcasecmp(resqueue,"none"))) */
( 0 == strcmp(resqueue,"none")))
{
/* MPP-7587: don't complain if you ALTER a superuser,
* who doesn't use the queue
*/
if (Gp_role == GP_ROLE_DISPATCH)
{
if (!bWas_super)
ereport(NOTICE,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("resource queue required -- "
"using default resource queue \"%s\"",
GP_DEFAULT_RESOURCE_QUEUE_NAME)));
}
resqueue = pstrdup(GP_DEFAULT_RESOURCE_QUEUE_NAME);
}
if ( strcmp(resqueue, "none") == 0)
{
new_record_nulls[Anum_pg_authid_rolresqueue - 1] = true;
}
else
{
queueid = GetResQueueIdForName(resqueue);
if (queueid == InvalidOid) {
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("resource queue \"%s\" does not exist",
resqueue),
errOmitLocation(true)));
}
if(resourceQueueIsBranch(queueid)) {
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("cannot assign non-leaf resource queue \"%s\" to role",
resqueue),
errOmitLocation(true)));
}
new_record[Anum_pg_authid_rolresqueue - 1] =
ObjectIdGetDatum(GetResQueueIdForName(resqueue));
}
new_record_repl[Anum_pg_authid_rolresqueue - 1] = true;
}
new_tuple = caql_modify_current(pcqCtx, new_record,
new_record_nulls, new_record_repl);
caql_update_current(pcqCtx, new_tuple);
/* and Update indexes (implicit) */
caql_endscan(pcqCtx);
heap_freetuple(new_tuple);
/*
* send RPC: notify RM to update
*/
if (resqueue && queueid != InvalidOid)
{
res = manipulateRoleForResourceQueue (resourceid,
roleid,
queueid,
MANIPULATE_ROLE_RESQUEUE_ALTER,
issuper,
stmt->role,
errorbuf,
sizeof(errorbuf));
}
/* We always unregister connection. */
unregisterConnectionInRMWithErrorReport(resourceid);
/* We always release resource context. */
releaseResourceContextWithErrorReport(resourceid);
if (resqueue && queueid != InvalidOid)
{
if ( res != FUNC_RETURN_OK )
{
ereport(ERROR,
(errcode(IS_TO_RM_RPC_ERROR(res) ?
ERRCODE_INTERNAL_ERROR :
ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot apply ALTER ROLE because of %s", errorbuf)));
}
}
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (rolemembers)
CommandCounterIncrement();
if (stmt->action == +1) /* add members to role */
{
if (rolemembers)
alter_subtype = "ADD USER";
AddRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
GetUserId(), false);
}
else if (stmt->action == -1) /* drop members from role */
{
if (rolemembers)
alter_subtype = "DROP USER";
DelRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
false);
}
if (bWas_super)
{
if (addintervals)
ereport(ERROR,
(errmsg("cannot alter superuser with DENY rules")));
else
DelRoleDenials(stmt->role, roleid, NIL); /* drop all preexisting constraints, if any. */
}
/*
* Disallow the use of DENY and DROP DENY fragments in the same query.
*
* We do this to prevent commands with unusual behavior.
* e.g. consider "ALTER ROLE foo DENY DAY 0 DROP DENY FOR DAY 1 DENY DAY 1 DENY DAY 2"
* In the manner that this is currently coded, because all DENY fragments are interpreted
* first, this actually becomes equivalent to you "ALTER ROLE foo DENY DAY 0 DENY DAY 2".
*
* Instead, we could honor the order in which the fragments are presented, but still that
* allows users to contradict themselves, as in the example given.
*/
if (addintervals && dropintervals)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"),
errhint("DENY and DROP DENY cannot be used in the same ALTER ROLE statement.")));
/*
* Populate pg_auth_time_constraint with the new intervals for which this
* particular role should be denied access.
*/
if (addintervals)
AddRoleDenials(stmt->role, roleid, addintervals);
/*
* Remove pg_auth_time_constraint entries that overlap with the
* intervals given by the user.
*/
if (dropintervals)
DelRoleDenials(stmt->role, roleid, dropintervals);
/* MPP-6929: metadata tracking */
if (Gp_role == GP_ROLE_DISPATCH)
MetaTrackUpdObject(AuthIdRelationId,
roleid,
GetUserId(),
"ALTER", alter_subtype
);
/*
* Close pg_authid, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
if (Gp_role == GP_ROLE_DISPATCH)
{
/* GPSQL: no dispatch to segments */
/*CdbDispatchUtilityStatement((Node *) stmt, "AlterRole");*/
}
}
/*
* ALTER ROLE ... SET
*/
void
AlterRoleSet(AlterRoleSetStmt *stmt)
{
char *valuestr;
HeapTuple oldtuple,
newtuple;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
char *alter_subtype = "SET"; /* metadata tracking */
cqContext *pcqCtx;
valuestr = flatten_set_variable_args(stmt->variable, stmt->value);
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_authid "
" WHERE rolname = :1 "
" FOR UPDATE ",
PointerGetDatum(stmt->role)));
oldtuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(oldtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", stmt->role),
errOmitLocation(true)));
/*
* To mess with a superuser you gotta be superuser; else you need
* createrole, or just want to change your own settings
*/
if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superusers"),
errOmitLocation(true)));
}
else
{
if (!have_createrole_privilege() &&
HeapTupleGetOid(oldtuple) != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied"),
errOmitLocation(true)));
}
memset(repl_repl, false, sizeof(repl_repl));
repl_repl[Anum_pg_authid_rolconfig - 1] = true;
if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
{
alter_subtype = "RESET ALL";
ArrayType *new = NULL;
Datum datum;
bool isnull;
/*
* in RESET ALL, request GUC to reset the settings array; if none
* left, we can set rolconfig to null; otherwise use the returned
* array
*/
datum = caql_getattr(pcqCtx,
Anum_pg_authid_rolconfig, &isnull);
if (!isnull)
new = GUCArrayReset(DatumGetArrayTypeP(datum));
if (new)
{
repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(new);
repl_null[Anum_pg_authid_rolconfig - 1] = false;
}
else
{
repl_null[Anum_pg_authid_rolconfig - 1] = true;
repl_val[Anum_pg_authid_rolconfig - 1] = (Datum) 0;
}
}
else
{
Datum datum;
bool isnull;
ArrayType *array;
repl_null[Anum_pg_authid_rolconfig - 1] = false;
/* Extract old value of rolconfig */
datum = caql_getattr(pcqCtx,
Anum_pg_authid_rolconfig, &isnull);
array = isnull ? NULL : DatumGetArrayTypeP(datum);
/* Update (valuestr is NULL in RESET cases) */
if (valuestr)
array = GUCArrayAdd(array, stmt->variable, valuestr);
else
{
alter_subtype = "RESET";
array = GUCArrayDelete(array, stmt->variable);
}
if (array)
repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(array);
else
repl_null[Anum_pg_authid_rolconfig - 1] = true;
}
newtuple = caql_modify_current(pcqCtx,
repl_val, repl_null, repl_repl);
caql_update_current(pcqCtx, newtuple);
/* and Update indexes (implicit) */
if (Gp_role == GP_ROLE_DISPATCH)
/* MPP-6929: metadata tracking */
MetaTrackUpdObject(AuthIdRelationId,
HeapTupleGetOid(oldtuple),
GetUserId(),
"ALTER", alter_subtype
);
caql_endscan(pcqCtx);
/* needn't keep lock since we won't be updating the flat file */
}
/*
* DROP ROLE
*/
void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
pg_auth_members_rel;
ListCell *item;
int res = FUNC_RETURN_OK;
static char errorbuf[1024] = "";
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop role"),
errOmitLocation(true)));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
* deleted.
*/
pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
foreach(item, stmt->roles)
{
const char *role = strVal(lfirst(item));
HeapTuple tuple;
char *detail;
Oid roleid;
cqContext cqc;
cqContext *pcqCtx;
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authid_rel),
cql("SELECT * FROM pg_authid "
" WHERE rolname = :1 "
" FOR UPDATE ",
PointerGetDatum((char *) role)));
tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(tuple))
{
if (!stmt->missing_ok)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", role),
errOmitLocation(true)));
}
else
{
ereport(NOTICE,
(errmsg("role \"%s\" does not exist, skipping",
role),
errOmitLocation(true)));
}
continue;
}
roleid = HeapTupleGetOid(tuple);
if (roleid == GetUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current user cannot be dropped"),
errOmitLocation(true)));
if (roleid == GetOuterUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current user cannot be dropped"),
errOmitLocation(true)));
if (roleid == GetSessionUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("session user cannot be dropped"),
errOmitLocation(true)));
/*
* For safety's sake, we allow createrole holders to drop ordinary
* roles but not superuser roles. This is mainly to avoid the
* scenario where you accidentally drop the last superuser.
*/
if (((Form_pg_authid) GETSTRUCT(tuple))->rolsuper &&
!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to drop superusers")));
/*
* Lock the role, so nobody can add dependencies to her while we drop
* her. We keep the lock until the end of transaction.
*/
LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock);
/* Check for pg_shdepend entries depending on this role */
if ((detail = checkSharedDependencies(AuthIdRelationId, roleid)) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("role \"%s\" cannot be dropped because some objects depend on it",
role),
errdetail("%s", detail),
errOmitLocation(true)));
/*
* Remove the role from the pg_authid table
*/
caql_delete_current(pcqCtx);
caql_endscan(pcqCtx);
/* Create a new resource context to manipulate role in resource manager. */
int resourceid = 0;
res = createNewResourceContext(&resourceid);
if ( res != FUNC_RETURN_OK ) {
Assert( res == COMM2RM_CLIENT_FULL_RESOURCECONTEXT );
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("cannot apply DROP ROLE. "
"Because too many resource contexts were created.")));
}
/* Here, using user oid is more convenient. */
res = registerConnectionInRMByOID(resourceid,
GetUserId(),
errorbuf,
sizeof(errorbuf));
if (res != FUNC_RETURN_OK)
{
releaseResourceContextWithErrorReport(resourceid);
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("%s", errorbuf)));
}
/* Notify resource manager to drop role. */
res = manipulateRoleForResourceQueue (resourceid,
roleid,
0, // not used when drop role
MANIPULATE_ROLE_RESQUEUE_DROP,
0, // not used when drop role
(char*)role,
errorbuf,
sizeof(errorbuf));
/* We always unregister connection. */
unregisterConnectionInRMWithErrorReport(resourceid);
/* We always release resource context. */
releaseResourceContextWithErrorReport(resourceid);
if ( res != FUNC_RETURN_OK )
{
ereport(ERROR,
(errcode(IS_TO_RM_RPC_ERROR(res) ?
ERRCODE_INTERNAL_ERROR :
ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot apply DROP ROLE because of %s", errorbuf)));
}
/*
* Remove role from the pg_auth_members table. We have to remove all
* tuples that show it as either a role or a member.
*
* XXX what about grantor entries? Maybe we should do one heap scan.
*/
{
int numDel;
cqContext cqc2;
numDel =
caql_getcount(
caql_addrel(cqclr(&cqc2), pg_auth_members_rel),
cql("DELETE FROM pg_auth_members "
" WHERE roleid = :1 ",
ObjectIdGetDatum(roleid)));
numDel =
caql_getcount(
caql_addrel(cqclr(&cqc2), pg_auth_members_rel),
cql("DELETE FROM pg_auth_members "
" WHERE member = :1 ",
ObjectIdGetDatum(roleid)));
}
/*
* Remove any time constraints on this role.
*/
DelRoleDenials(role, roleid, NIL);
/*
* Remove any comments on this role.
*/
DeleteSharedComments(roleid, AuthIdRelationId);
/* MPP-6929: metadata tracking */
if (Gp_role == GP_ROLE_DISPATCH)
MetaTrackDropObject(AuthIdRelationId,
roleid);
/*
* Advance command counter so that later iterations of this loop will
* see the changes already made. This is essential if, for example,
* we are trying to drop both a role and one of its direct members ---
* we'll get an error if we try to delete the linking pg_auth_members
* tuple twice. (We do not need a CCI between the two delete loops
* above, because it's not allowed for a role to directly contain
* itself.)
*/
CommandCounterIncrement();
}
/*
* Now we can clean up; but keep locks until commit (to avoid possible
* deadlock failure while updating flat file)
*/
heap_close(pg_auth_members_rel, NoLock);
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
if (Gp_role == GP_ROLE_DISPATCH)
{
/* GPSQL: no dispatch to segments */
/* CdbDispatchUtilityStatement((Node *) stmt, "DropRole"); */
}
}
/*
* Rename role
*/
void
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
newtuple;
TupleDesc dsc;
Relation rel;
Datum datum;
bool isnull;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
int i;
Oid roleid;
cqContext cqc;
cqContext cqc2;
cqContext *pcqCtx;
rel = heap_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), rel),
cql("SELECT * FROM pg_authid "
" WHERE rolname = :1 "
" FOR UPDATE ",
CStringGetDatum((char *) oldname)));
oldtuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(oldtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", oldname),
errOmitLocation(true)));
/*
* XXX Client applications probably store the session user somewhere, so
* renaming it could cause confusion. On the other hand, there may not be
* an actual problem besides a little confusion, so think about this and
* decide. Same for SET ROLE ... we don't restrict renaming the current
* effective userid, though.
*/
roleid = HeapTupleGetOid(oldtuple);
if (roleid == GetSessionUserId())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("session user cannot be renamed")));
if (roleid == GetOuterUserId())
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("current user cannot be renamed")));
/* make sure the new name doesn't exist */
if (caql_getcount(
caql_addrel(cqclr(&cqc2), rel),
cql("SELECT COUNT(*) FROM pg_authid "
" WHERE rolname = :1 ",
CStringGetDatum((char *) newname))))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists", newname)));
if (strcmp(newname, "public") == 0 ||
strcmp(newname, "none") == 0)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
newname)));
/*
* createrole is enough privilege unless you want to mess with a superuser
*/
if (((Form_pg_authid) GETSTRUCT(oldtuple))->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to rename superusers")));
}
else
{
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to rename role")));
}
/* OK, construct the modified tuple */
for (i = 0; i < Natts_pg_authid; i++)
repl_repl[i] = false;
repl_repl[Anum_pg_authid_rolname - 1] = true;
repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein,
CStringGetDatum((char *) newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
if (!isnull && isMD5(TextDatumGetCString(datum)))
{
/* MD5 uses the username as salt, so just clear it on a rename */
repl_repl[Anum_pg_authid_rolpassword - 1] = true;
repl_null[Anum_pg_authid_rolpassword - 1] = true;
if (Gp_role != GP_ROLE_EXECUTE)
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
newtuple = caql_modify_current(pcqCtx, repl_val, repl_null, repl_repl);
caql_update_current(pcqCtx, newtuple);
/* and Update indexes (implicit) */
caql_endscan(pcqCtx);
/*
* Close pg_authid, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
/* MPP-6929: metadata tracking */
if (Gp_role == GP_ROLE_DISPATCH)
MetaTrackUpdObject(AuthIdRelationId,
roleid,
GetUserId(),
"ALTER", "RENAME"
);
}
/*
* GrantRoleStmt
*
* Grant/Revoke roles to/from roles
*/
void
GrantRole(GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
if (stmt->grantor)
grantor = get_roleid_checked(stmt->grantor);
else
grantor = GetUserId();
grantee_ids = roleNamesToIds(stmt->grantee_roles);
/* AccessShareLock is enough since we aren't modifying pg_authid */
pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock);
/*
* Step through all of the granted roles and add/remove entries for the
* grantees, or, if admin_opt is set, then just add/remove the admin
* option.
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
foreach(item, stmt->granted_roles)
{
char *rolename = strVal(lfirst(item));
Oid roleid = get_roleid_checked(rolename);
if (stmt->is_grant)
{
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
grantor, stmt->admin_opt);
}
else
{
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
stmt->admin_opt);
}
/* MPP-6929: metadata tracking */
if (Gp_role == GP_ROLE_DISPATCH)
MetaTrackUpdObject(AuthIdRelationId,
roleid,
GetUserId(),
"PRIVILEGE",
(stmt->is_grant) ? "GRANT" : "REVOKE"
);
}
/*
* Close pg_authid, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
}
/*
* DropOwnedObjects
*
* Drop the objects owned by a given list of roles.
*/
void
DropOwnedObjects(DropOwnedStmt *stmt)
{
List *role_ids = roleNamesToIds(stmt->roles);
ListCell *cell;
/* Check privileges */
foreach(cell, role_ids)
{
Oid roleid = lfirst_oid(cell);
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop objects"),
errOmitLocation(true)));
}
/*
if (Gp_role == GP_ROLE_DISPATCH)
{
CdbDispatchUtilityStatement((Node *) stmt, "DropOwnedObjects");
}
*/
/* Ok, do it */
shdepDropOwned(role_ids, stmt->behavior);
}
/*
* ReassignOwnedObjects
*
* Give the objects owned by a given list of roles away to another user.
*/
void
ReassignOwnedObjects(ReassignOwnedStmt *stmt)
{
List *role_ids = roleNamesToIds(stmt->roles);
ListCell *cell;
Oid newrole;
/* Check privileges */
foreach(cell, role_ids)
{
Oid roleid = lfirst_oid(cell);
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to reassign objects"),
errOmitLocation(true)));
}
/* Must have privileges on the receiving side too */
newrole = get_roleid_checked(stmt->newrole);
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to reassign objects"),
errOmitLocation(true)));
/*
if (Gp_role == GP_ROLE_DISPATCH)
{
CdbDispatchUtilityStatement((Node *) stmt, "ReassignOwnedObjects");
}
*/
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
}
/*
* roleNamesToIds
*
* Given a list of role names (as String nodes), generate a list of role OIDs
* in the same order.
*/
static List *
roleNamesToIds(List *memberNames)
{
List *result = NIL;
ListCell *l;
foreach(l, memberNames)
{
char *rolename = strVal(lfirst(l));
Oid roleid = get_roleid_checked(rolename);
result = lappend_oid(result, roleid);
}
return result;
}
/*
* AddRoleMems -- Add given members to the specified role
*
* rolename: name of role to add to (used only for error messages)
* roleid: OID of role to add to
* memberNames: list of names of roles to add (used only for error messages)
* memberIds: OIDs of roles to add
* grantorId: who is granting the membership
* admin_opt: granting admin option?
*
* Note: caller is responsible for calling auth_file_update_needed().
*/
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
Oid grantorId, bool admin_opt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
ListCell *nameitem;
ListCell *iditem;
Assert(list_length(memberNames) == list_length(memberIds));
/* Skip permission check if nothing to do */
if (!memberIds)
return;
/*
* Check permissions: must have createrole or admin option on the role to
* be changed. To mess with a superuser role, you gotta be superuser.
*/
if (superuser_arg(roleid))
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superusers")));
}
else
{
if (!have_createrole_privilege() &&
!is_admin_of_role(grantorId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must have admin option on role \"%s\"",
rolename)));
}
/* XXX not sure about this check */
if (grantorId != GetUserId() && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to set grantor")));
pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
forboth(nameitem, memberNames, iditem, memberIds)
{
const char *membername = strVal(lfirst(nameitem));
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
bool new_record_repl[Natts_pg_auth_members];
cqContext cqc;
cqContext *pcqCtx;
/*
* Refuse creation of membership loops, including the trivial case
* where a role is made a member of itself. We do this by checking to
* see if the target role is already a member of the proposed member
* role. We have to ignore possible superuserness, however, else we
* could never grant membership in a superuser-privileged role.
*/
if (is_member_of_role_nosuper(roleid, memberid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
(errmsg("role \"%s\" is a member of role \"%s\"",
rolename, membername))));
/*
* Check if entry for this role/member already exists; if so, give
* warning unless we are adding admin option.
*/
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authmem_rel),
cql("SELECT * FROM pg_auth_members "
" WHERE roleid = :1 "
" AND member = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid)));
authmem_tuple = caql_getnext(pcqCtx);
if (HeapTupleIsValid(authmem_tuple) &&
(!admin_opt ||
((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
{
if (Gp_role != GP_ROLE_EXECUTE)
ereport(NOTICE,
(errmsg("role \"%s\" is already a member of role \"%s\"",
membername, rolename)));
caql_endscan(pcqCtx);
continue;
}
/* Build a tuple to insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
if (HeapTupleIsValid(authmem_tuple))
{
new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
tuple = caql_modify_current(pcqCtx,
new_record,
new_record_nulls, new_record_repl);
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
caql_endscan(pcqCtx);
}
else
{
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authmem_rel),
cql("INSERT INTO pg_auth_members ",
NULL));
tuple = caql_form_tuple(pcqCtx, new_record, new_record_nulls);
/* Insert tuple into the relation */
caql_insert(pcqCtx, tuple); /* implicit update of index as well */
caql_endscan(pcqCtx);
}
/* CCI after each change, in case there are duplicates in list */
CommandCounterIncrement();
}
/*
* Close pg_authmem, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authmem_rel, NoLock);
}
/*
* CheckKeywordIsValid
*
* check that string in 'keyword' is included in set of strings in 'arr'
*/
static void CheckKeywordIsValid(char *keyword, const char **arr, const int arrsize)
{
int i = 0;
bool ok = false;
for(i = 0 ; i < arrsize ; i++)
{
if(strcasecmp(keyword, arr[i]) == 0)
ok = true;
}
if(!ok)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid [NO]CREATEEXTTABLE option \"%s\"", keyword)));
}
/*
* CheckValueBelongsToKey
*
* check that value (e.g 'gpfdist') belogs to the key it was defined for (e.g 'protocol').
* error out otherwise (for example, [protocol='writable'] includes valid keywords, but makes
* no sense.
*/
static void CheckValueBelongsToKey(char *key, char *val, const char **keys, const char **vals)
{
if(strcasecmp(key, keys[0]) == 0)
{
if(strcasecmp(val, vals[0]) != 0 &&
strcasecmp(val, vals[1]) != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid %s value \"%s\"", key, val)));
}
else /* keys[1] */
{
if (strcasecmp(val, "gphdfs") == 0 && Gp_role == GP_ROLE_DISPATCH)
ereport(WARNING,
(errmsg("GRANT/REVOKE on gphdfs is deprecated"),
errhint("Issue the GRANT or REVOKE on the protocol itself"),
errOmitLocation(true)));
if(strcasecmp(val, "gpfdist") != 0 &&
strcasecmp(val, "gpfdists") != 0 &&
strcasecmp(val, "http") != 0 &&
strcasecmp(val, "gphdfs") != 0&&
strcasecmp(val, "hdfs") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid %s value \"%s\"", key, val)));
}
}
/*
* TransformExttabAuthClause
*
* Given a set of key value pairs, take them apart, fill in any default
* values, and validate that pairs are legal and make sense.
*
* defaults are:
* - 'readable' when no type defined,
* - 'gpfdist' when no protocol defined,
* - 'readable' + ' gpfdist' if both type and protocol aren't defined.
*
*/
static void TransformExttabAuthClause(DefElem *defel, extAuthPair *extauth)
{
ListCell *lc;
List *l = (List *) defel->arg;
DefElem *d1 = NULL,
*d2 = NULL;
genericPair *genpair = (genericPair *) palloc0 (4 * sizeof(char *));
const int numkeys = 2;
const int numvals = 6;
const char *keys[] = { "type", "protocol"}; /* order matters for validation. don't change! */
const char *vals[] = { /* types */ "readable", "writable",
/* protocols */ "gpfdist", "gpfdists" , "http", "gphdfs", "hdfs"};
if(list_length(l) > 2)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid [NO]CREATEEXTTABLE specification. too many values")));
if(list_length(l) == 2)
{
/* both a protocol and type specification */
lc = list_head(l);
d1 = (DefElem *) lfirst(lc);
genpair->key1 = pstrdup(d1->defname);
genpair->val1 = pstrdup(strVal(d1->arg));
lc = lnext(lc);
d2 = (DefElem *) lfirst(lc);
genpair->key2 = pstrdup(d2->defname);
genpair->val2 = pstrdup(strVal(d2->arg));
}
else if(list_length(l) == 1)
{
/* either a protocol or type specification */
lc = list_head(l);
d1 = (DefElem *) lfirst(lc);
genpair->key1 = pstrdup(d1->defname);
genpair->val1 = pstrdup(strVal(d1->arg));
if(strcasecmp(genpair->key1, "type") == 0)
{
/* default value for missing protocol */
genpair->key2 = pstrdup("protocol");
genpair->val2 = pstrdup("gpfdist");
}
else
{
/* default value for missing type */
genpair->key2 = pstrdup("type");
genpair->val2 = pstrdup("readable");
}
}
else
{
/* none specified. use global default */
genpair->key1 = pstrdup("protocol");
genpair->val1 = pstrdup("gpfdist");
genpair->key2 = pstrdup("type");
genpair->val2 = pstrdup("readable");
}
/* check all keys and values are legal */
CheckKeywordIsValid(genpair->key1, keys, numkeys);
CheckKeywordIsValid(genpair->key2, keys, numkeys);
CheckKeywordIsValid(genpair->val1, vals, numvals);
CheckKeywordIsValid(genpair->val2, vals, numvals);
/* check all values are of the proper key */
CheckValueBelongsToKey(genpair->key1, genpair->val1, keys, vals);
CheckValueBelongsToKey(genpair->key2, genpair->val2, keys, vals);
if(strcasecmp(genpair->key1, genpair->key2) == 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("redundant option for \"%s\"", genpair->key1)));
/* now set values in extauth, which is the result returned */
if(strcasecmp(genpair->key1, "protocol") == 0)
{
extauth->protocol = pstrdup(genpair->val1);
extauth->type = pstrdup(genpair->val2);
}
else
{
extauth->protocol = pstrdup(genpair->val2);
extauth->type = pstrdup(genpair->val1);
}
pfree(genpair->key1);
pfree(genpair->key2);
pfree(genpair->val1);
pfree(genpair->val2);
pfree(genpair);
}
/*
* SetCreateExtTableForRole
*
* Given the allow list (permissions to add) and disallow (permissions
* to take away) consolidate this information into the 3 catalog
* boolean columns that will need to get updated. While at it we check
* that all the options are valid and don't conflict with each other.
*
*/
static void SetCreateExtTableForRole(List* allow,
List* disallow,
bool* createrextgpfd,
bool* createrexthttp,
bool* createwextgpfd,
bool* createrexthdfs,
bool* createwexthdfs)
{
ListCell* lc;
bool createrextgpfd_specified = false;
bool createwextgpfd_specified = false;
bool createrexthttp_specified = false;
bool createrexthdfs_specified = false;
bool createwexthdfs_specified = false;
if(list_length(allow) > 0)
{
/* examine key value pairs */
foreach(lc, allow)
{
extAuthPair* extauth = (extAuthPair*) lfirst(lc);
/* we use the same privilege for gpfdist and gpfdists */
if ((strcasecmp(extauth->protocol, "gpfdist") == 0) ||
(strcasecmp(extauth->protocol, "gpfdists") == 0))
{
if(strcasecmp(extauth->type, "readable") == 0)
{
*createrextgpfd = true;
createrextgpfd_specified = true;
}
else
{
*createwextgpfd = true;
createwextgpfd_specified = true;
}
}
else if(strcasecmp(extauth->protocol, "gphdfs") == 0)
{
if(strcasecmp(extauth->type, "readable") == 0)
{
*createrexthdfs = true;
createrexthdfs_specified = true;
}
else
{
*createwexthdfs = true;
createwexthdfs_specified = true;
}
}
else if(strcasecmp(extauth->protocol, "hdfs") == 0)
{
if(strcasecmp(extauth->type, "readable") == 0)
{
*createrexthdfs = true;
createrexthdfs_specified = true;
}
else
{
*createwexthdfs = true;
createwexthdfs_specified = true;
}
}
else /* http */
{
if(strcasecmp(extauth->type, "readable") == 0)
{
*createrexthttp = true;
createrexthttp_specified = true;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid CREATEEXTTABLE specification. writable http external tables do not exist")));
}
}
}
}
/*
* go over the disallow list.
* if we're in CREATE ROLE, check that we don't negate something from the
* allow list. error out with conflicting options if we do.
* if we're in ALTER ROLE, just set the flags accordingly.
*/
if(list_length(disallow) > 0)
{
bool conflict = false;
/* examine key value pairs */
foreach(lc, disallow)
{
extAuthPair* extauth = (extAuthPair*) lfirst(lc);
/* we use the same privilege for gpfdist and gpfdists */
if ((strcasecmp(extauth->protocol, "gpfdist") == 0) ||
(strcasecmp(extauth->protocol, "gpfdists") == 0))
{
if(strcasecmp(extauth->type, "readable") == 0)
{
if(createrextgpfd_specified)
conflict = true;
*createrextgpfd = false;
}
else
{
if(createwextgpfd_specified)
conflict = true;
*createwextgpfd = false;
}
}
else if(strcasecmp(extauth->protocol, "gphdfs") == 0)
{
if(strcasecmp(extauth->type, "readable") == 0)
{
if(createrexthdfs_specified)
conflict = true;
*createrexthdfs = false;
}
else
{
if(createwexthdfs_specified)
conflict = true;
*createwexthdfs = false;
}
}
else if(strcasecmp(extauth->protocol, "hdfs") == 0)
{
if(strcasecmp(extauth->type, "readable") == 0)
{
if(createrexthdfs_specified)
conflict = true;
*createrexthdfs = false;
}
else
{
if(createwexthdfs_specified)
conflict = true;
*createwexthdfs = false;
}
}
else /* http */
{
if(strcasecmp(extauth->type, "readable") == 0)
{
if(createrexthttp_specified)
conflict = true;
*createrexthttp = false;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid NOCREATEEXTTABLE specification. writable http external tables do not exist")));
}
}
}
if(conflict)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting specifications in CREATEEXTTABLE and NOCREATEEXTTABLE")));
}
}
/*
* DelRoleMems -- Remove given members from the specified role
*
* rolename: name of role to del from (used only for error messages)
* roleid: OID of role to del from
* memberNames: list of names of roles to del (used only for error messages)
* memberIds: OIDs of roles to del
* admin_opt: remove admin option only?
*
* Note: caller is responsible for calling auth_file_update_needed().
*/
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
bool admin_opt)
{
Relation pg_authmem_rel;
ListCell *nameitem;
ListCell *iditem;
Assert(list_length(memberNames) == list_length(memberIds));
/* Skip permission check if nothing to do */
if (!memberIds)
return;
/*
* Check permissions: must have createrole or admin option on the role to
* be changed. To mess with a superuser role, you gotta be superuser.
*/
if (superuser_arg(roleid))
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superusers")));
}
else
{
if (!have_createrole_privilege() &&
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must have admin option on role \"%s\"",
rolename)));
}
pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock);
forboth(nameitem, memberNames, iditem, memberIds)
{
const char *membername = strVal(lfirst(nameitem));
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
cqContext cqc;
cqContext *pcqCtx;
/*
* Find entry for this role/member
*/
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_authmem_rel),
cql("SELECT * FROM pg_auth_members "
" WHERE roleid = :1 "
" AND member = :2 "
" FOR UPDATE ",
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid)));
authmem_tuple = caql_getnext(pcqCtx);
if (!HeapTupleIsValid(authmem_tuple))
{
ereport(WARNING,
(errmsg("role \"%s\" is not a member of role \"%s\"",
membername, rolename)));
continue;
}
if (!admin_opt)
{
/* Remove the entry altogether */
caql_delete_current(pcqCtx);
}
else
{
/* Just turn off the admin option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
bool new_record_repl[Natts_pg_auth_members];
/* Build a tuple to update with */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
tuple = caql_modify_current(pcqCtx,
new_record,
new_record_nulls, new_record_repl);
caql_update_current(pcqCtx, tuple);
/* and Update indexes (implicit) */
}
caql_endscan(pcqCtx);
/* CCI after each change, in case there are duplicates in list */
CommandCounterIncrement();
}
/*
* Close pg_authmem, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_authmem_rel, NoLock);
}
/*
* ExtractAuthIntervalClause
*
* Build an authInterval struct (defined above) from given input
*/
static void
ExtractAuthIntervalClause(DefElem *defel, authInterval *interval)
{
DenyLoginPoint *start = NULL, *end = NULL;
char *temp;
if (IsA(defel->arg, DenyLoginInterval))
{
DenyLoginInterval *span = (DenyLoginInterval *)defel->arg;
start = span->start;
end = span->end;
}
else
{
Assert(IsA(defel->arg, DenyLoginPoint));
start = (DenyLoginPoint *)defel->arg;
end = start;
}
interval->start.day = ExtractAuthInterpretDay(start->day);
temp = start->time != NULL ? strVal(start->time) : "00:00:00";
interval->start.time = DatumGetTimeADT(DirectFunctionCall1(time_in, CStringGetDatum(temp)));
interval->end.day = ExtractAuthInterpretDay(end->day);
temp = end->time != NULL ? strVal(end->time) : "24:00:00";
interval->end.time = DatumGetTimeADT(DirectFunctionCall1(time_in, CStringGetDatum(temp)));
if (point_cmp(&interval->start, &interval->end) > 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("time interval must not wrap around")));
}
/*
* TransferAuthInterpretDay -- Interpret day of week from parse node
*
* day: node which dictates a day of week;
* may be either an integer in [0, 6]
* or a string giving name of day in English
*/
static int16
ExtractAuthInterpretDay(Value * day)
{
int16 ret;
if (day->type == T_Integer)
{
ret = intVal(day);
if (ret < 0 || ret > 6)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("numeric day of week must be between 0 and 6")));
}
else
{
int16 elems = 7;
char *target = strVal(day);
for (ret = 0; ret < elems; ret++)
if (strcasecmp(target, daysofweek[ret]) == 0)
break;
if (ret == elems)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid weekday name \"%s\"", target),
errhint("Day of week must be one of 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'.")));
}
return ret;
}
/*
* AddRoleDenials -- Populate pg_auth_time_constraint
*
* rolename: name of role to add to (used only for error messages)
* roleid: OID of role to add to
* addintervals: list of authInterval structs dictating when
* this particular role should be denied access
*
* Note: caller is reponsible for checking permissions to edit the given role.
*/
static void
AddRoleDenials(const char *rolename, Oid roleid, List *addintervals) {
Relation pg_auth_time_rel;
ListCell *intervalitem;
cqContext cqc;
cqContext *pcqCtx;
pg_auth_time_rel = heap_open(AuthTimeConstraintRelationId, RowExclusiveLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), pg_auth_time_rel),
cql("INSERT INTO pg_auth_time_constraint ",
NULL));
foreach(intervalitem, addintervals)
{
authInterval *interval = (authInterval *)lfirst(intervalitem);
HeapTuple tuple;
Datum new_record[Natts_pg_auth_time_constraint];
bool new_record_nulls[Natts_pg_auth_time_constraint];
/* Build a tuple to insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
new_record[Anum_pg_auth_time_constraint_authid - 1] = ObjectIdGetDatum(roleid);
new_record[Anum_pg_auth_time_constraint_start_day - 1] = Int16GetDatum(interval->start.day);
new_record[Anum_pg_auth_time_constraint_start_time - 1] = TimeADTGetDatum(interval->start.time);
new_record[Anum_pg_auth_time_constraint_end_day - 1] = Int16GetDatum(interval->end.day);
new_record[Anum_pg_auth_time_constraint_end_time - 1] = TimeADTGetDatum(interval->end.time);
tuple = caql_form_tuple(pcqCtx, new_record, new_record_nulls);
/* Insert tuple into the relation */
caql_insert(pcqCtx, tuple); /* implicit update of index as well */
}
CommandCounterIncrement();
/*
* Close pg_auth_time_constraint, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
caql_endscan(pcqCtx);
heap_close(pg_auth_time_rel, NoLock);
/*
* Set flag to update flat auth time constraint file at commit.
*/
auth_time_file_update_needed();
}
/*
* DelRoleDenials -- Trim pg_auth_time_constraint
*
* rolename: name of role to edit (used only for error messages)
* roleid: OID of role to edit
* dropintervals: list of authInterval structs dictating which
* existing rules should be dropped. Here, NIL will mean
* remove all constraints for the given role.
*
* Note: caller is reponsible for checking permissions to edit the given role.
*/
static void
DelRoleDenials(const char *rolename, Oid roleid, List *dropintervals)
{
Relation pg_auth_time_rel;
ListCell *intervalitem;
bool dropped_matching_interval = false;
HeapTuple tmp_tuple;
cqContext cqc;
cqContext *pcqCtx;
pg_auth_time_rel = heap_open(AuthTimeConstraintRelationId, RowExclusiveLock);
pcqCtx =
caql_beginscan(
caql_addrel(cqclr(&cqc), pg_auth_time_rel),
cql("SELECT * FROM pg_auth_time_constraint "
" WHERE authid = :1 "
" FOR UPDATE ",
ObjectIdGetDatum(roleid)));
while (HeapTupleIsValid(tmp_tuple = caql_getnext(pcqCtx)))
{
if (dropintervals != NIL)
{
Form_pg_auth_time_constraint obj = (Form_pg_auth_time_constraint) GETSTRUCT(tmp_tuple);
authInterval *interval, *existing = (authInterval *) palloc0(sizeof(authInterval));
existing->start.day = obj->start_day;
existing->start.time = obj->start_time;
existing->end.day = obj->end_day;
existing->end.time = obj->end_time;
foreach(intervalitem, dropintervals)
{
interval = (authInterval *)lfirst(intervalitem);
if (interval_overlap(existing, interval))
{
if (Gp_role == GP_ROLE_DISPATCH)
ereport(NOTICE,
(errmsg("dropping DENY rule for \"%s\" between %s %s and %s %s",
rolename,
daysofweek[existing->start.day],
DatumGetCString(DirectFunctionCall1(time_out, TimeADTGetDatum(existing->start.time))),
daysofweek[existing->end.day],
DatumGetCString(DirectFunctionCall1(time_out, TimeADTGetDatum(existing->end.time))))));
caql_delete_current(pcqCtx);
dropped_matching_interval = true;
break;
}
}
}
else
caql_delete_current(pcqCtx);
}
/* if intervals were specified and none was found, raise error */
if (dropintervals && !dropped_matching_interval)
ereport(ERROR,
(errmsg("cannot find matching DENY rules for \"%s\"", rolename)));
caql_endscan(pcqCtx);
/*
* Close pg_auth_time_constraint, but keep lock till commit (this is important to
* prevent any risk of deadlock failure while updating flat file)
*/
heap_close(pg_auth_time_rel, NoLock);
/*
* Set flag to update flat auth time constraint file at commit.
*/
auth_time_file_update_needed();
}