Michael Paquier b81a71aa05 Assign error codes where missing for user-facing failures
All the errors triggered in the code paths patched here would cause the
backend to issue an internal_error errcode, which is a state that should
be used only for "can't happen" situations.  However, these code paths
are reachable by the regression tests, and could be seen by users in
valid cases.  Some regression tests expect internal errcodes as they
manipulate the backend state to cause corruption (like checksums), or
use elog() because it is more convenient (like injection points), these
have no need to change.

This reduces the number of internal failures triggered in a check-world
by more than half, while providing correct errcodes for these valid
cases.

Reviewed-by: Robert Haas
Discussion: https://postgr.es/m/Zic_GNgos5sMxKoa@paquier.xyz
2024-07-04 09:48:40 +09:00

2582 lines
78 KiB
C

/*-------------------------------------------------------------------------
*
* user.c
* Commands for manipulating roles (formerly called users).
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/backend/commands/user.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/user.h"
#include "libpq/crypt.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
/*
* Removing a role grant - or the admin option on it - might recurse to
* dependent grants. We use these values to reason about what would need to
* be done in such cases.
*
* RRG_NOOP indicates a grant that would not need to be altered by the
* operation.
*
* RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have
* admin_option set to false by the operation.
*
* Similarly, RRG_REMOVE_INHERIT_OPTION and RRG_REMOVE_SET_OPTION indicate
* grants that would need to have the corresponding options set to false.
*
* RRG_DELETE_GRANT indicates a grant that would need to be removed entirely
* by the operation.
*/
typedef enum
{
RRG_NOOP,
RRG_REMOVE_ADMIN_OPTION,
RRG_REMOVE_INHERIT_OPTION,
RRG_REMOVE_SET_OPTION,
RRG_DELETE_GRANT,
} RevokeRoleGrantAction;
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
typedef struct
{
unsigned specified;
bool admin;
bool inherit;
bool set;
} GrantRoleOptions;
#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
#define GRANT_ROLE_SPECIFIED_SET 0x0004
/* GUC parameters */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
char *createrole_self_grant = "";
static bool createrole_self_grant_enabled = false;
static GrantRoleOptions createrole_self_grant_options;
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
Oid grantorId, GrantRoleOptions *popt,
DropBehavior behavior);
static void check_role_membership_authorization(Oid currentUserId, Oid roleid,
bool is_grant);
static Oid check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId,
bool is_grant);
static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist);
static bool plan_single_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions,
Oid member, Oid grantor,
GrantRoleOptions *popt,
DropBehavior behavior);
static void plan_member_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions, Oid member);
static void plan_recursive_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions,
int index,
bool revoke_admin_option_only,
DropBehavior behavior);
static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
static bool
have_createrole_privilege(void)
{
return has_createrole_privilege(GetUserId());
}
/*
* CREATE ROLE
*/
Oid
CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
{
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple;
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
Oid currentUserId = GetUserId();
Oid roleid;
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
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 isreplication = false; /* Is this a replication role? */
bool bypassrls = false; /* Is this a row security enabled role? */
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 */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *disreplication = NULL;
DefElem *dconnlimit = NULL;
DefElem *daddroleto = NULL;
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
GrantRoleOptions popt;
/* 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)
{
if (dpassword)
errorConflictingDefElem(defel, pstate);
dpassword = defel;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
ereport(NOTICE,
(errmsg("SYSID can no longer be specified")));
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
errorConflictingDefElem(defel, pstate);
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
errorConflictingDefElem(defel, pstate);
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
errorConflictingDefElem(defel, pstate);
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
errorConflictingDefElem(defel, pstate);
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
errorConflictingDefElem(defel, pstate);
dcanlogin = defel;
}
else if (strcmp(defel->defname, "isreplication") == 0)
{
if (disreplication)
errorConflictingDefElem(defel, pstate);
disreplication = defel;
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
errorConflictingDefElem(defel, pstate);
dconnlimit = defel;
}
else if (strcmp(defel->defname, "addroleto") == 0)
{
if (daddroleto)
errorConflictingDefElem(defel, pstate);
daddroleto = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0)
{
if (drolemembers)
errorConflictingDefElem(defel, pstate);
drolemembers = defel;
}
else if (strcmp(defel->defname, "adminmembers") == 0)
{
if (dadminmembers)
errorConflictingDefElem(defel, pstate);
dadminmembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
errorConflictingDefElem(defel, pstate);
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dissuper)
issuper = boolVal(dissuper->arg);
if (dinherit)
inherit = boolVal(dinherit->arg);
if (dcreaterole)
createrole = boolVal(dcreaterole->arg);
if (dcreatedb)
createdb = boolVal(dcreatedb->arg);
if (dcanlogin)
canlogin = boolVal(dcanlogin->arg);
if (disreplication)
isreplication = boolVal(disreplication->arg);
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
if (connlimit < -1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid connection limit: %d", connlimit)));
}
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 (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
/* Check some permissions first */
if (!superuser_arg(currentUserId))
{
if (!has_createrole_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role"),
errdetail("Only roles with the %s attribute may create roles.",
"CREATEROLE")));
if (issuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role"),
errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
if (createdb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role"),
errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
"CREATEDB", "CREATEDB")));
if (isreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role"),
errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
"REPLICATION", "REPLICATION")));
if (bypassrls && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create role"),
errdetail("Only roles with the %s attribute may create roles with the %s attribute.",
"BYPASSRLS", "BYPASSRLS")));
}
/*
* Check that the user is not trying to create a role in the reserved
* "pg_" namespace.
*/
if (IsReservedName(stmt->role))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role),
errdetail("Role names starting with \"pg_\" are reserved.")));
/*
* If built with appropriate switch, whine when regression-testing
* conventions for role names are violated.
*/
#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
if (strncmp(stmt->role, "regress_", 8) != 0)
elog(WARNING, "roles created by regression test cases should have names starting with \"regress_\"");
#endif
/*
* Check the pg_authid relation to be certain the role doesn't already
* exist.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
if (OidIsValid(get_role_oid(stmt->role, true)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists",
stmt->role)));
/* Convert validuntil to internal form */
if (validUntil)
{
validUntil_datum = DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
validUntil_null = false;
}
else
{
validUntil_datum = (Datum) 0;
validUntil_null = true;
}
/*
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
(*check_password_hook) (stmt->role,
password,
get_password_type(password),
validUntil_datum,
validUntil_null);
/*
* Build a tuple to insert
*/
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);
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
if (password)
{
char *shadow_pass;
const char *logdetail = NULL;
/*
* Don't allow an empty password. Libpq treats an empty password the
* same as no password at all, and won't even try to authenticate. But
* other clients might, so allowing it would be confusing. By clearing
* the password when an empty string is specified, the account is
* consistently locked for all clients.
*
* Note that this only covers passwords stored in the database itself.
* There are also checks in the authentication code, to forbid an
* empty password from being used with authentication methods that
* fetch the password from an external system, like LDAP or PAM.
*/
if (password[0] == '\0' ||
plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, stmt->role,
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
/*
* pg_largeobject_metadata contains pg_authid.oid's, so we use the
* binary-upgrade override.
*/
if (IsBinaryUpgrade)
{
if (!OidIsValid(binary_upgrade_next_pg_authid_oid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("pg_authid OID value not set when in binary upgrade mode")));
roleid = binary_upgrade_next_pg_authid_oid;
binary_upgrade_next_pg_authid_oid = InvalidOid;
}
else
{
roleid = GetNewOidWithIndex(pg_authid_rel, AuthIdOidIndexId,
Anum_pg_authid_oid);
}
new_record[Anum_pg_authid_oid - 1] = ObjectIdGetDatum(roleid);
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
* Insert new record in the pg_authid table
*/
CatalogTupleInsert(pg_authid_rel, tuple);
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
/* Default grant. */
InitGrantRoleOptions(&popt);
/*
* Add the new role to the specified existing roles.
*/
if (addroleto)
{
RoleSpec *thisrole = makeNode(RoleSpec);
List *thisrole_list = list_make1(thisrole);
List *thisrole_oidlist = list_make1_oid(roleid);
thisrole->roletype = ROLESPEC_CSTRING;
thisrole->rolename = stmt->role;
thisrole->location = -1;
foreach(item, addroleto)
{
RoleSpec *oldrole = lfirst(item);
HeapTuple oldroletup = get_rolespec_tuple(oldrole);
Form_pg_authid oldroleform = (Form_pg_authid) GETSTRUCT(oldroletup);
Oid oldroleid = oldroleform->oid;
char *oldrolename = NameStr(oldroleform->rolname);
/* can only add this role to roles for which you have rights */
check_role_membership_authorization(currentUserId, oldroleid, true);
AddRoleMems(currentUserId, oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
InvalidOid, &popt);
ReleaseSysCache(oldroletup);
}
}
/*
* If the current user isn't a superuser, make them an admin of the new
* role so that they can administer the new object they just created.
* Superusers will be able to do that anyway.
*
* The grantor of record for this implicit grant is the bootstrap
* superuser, which means that the CREATEROLE user cannot revoke the
* grant. They can however grant the created role back to themselves with
* different options, since they enjoy ADMIN OPTION on it.
*/
if (!superuser())
{
RoleSpec *current_role = makeNode(RoleSpec);
GrantRoleOptions poptself;
List *memberSpecs;
List *memberIds = list_make1_oid(currentUserId);
current_role->roletype = ROLESPEC_CURRENT_ROLE;
current_role->location = -1;
memberSpecs = list_make1(current_role);
poptself.specified = GRANT_ROLE_SPECIFIED_ADMIN
| GRANT_ROLE_SPECIFIED_INHERIT
| GRANT_ROLE_SPECIFIED_SET;
poptself.admin = true;
poptself.inherit = false;
poptself.set = false;
AddRoleMems(BOOTSTRAP_SUPERUSERID, stmt->role, roleid,
memberSpecs, memberIds,
BOOTSTRAP_SUPERUSERID, &poptself);
/*
* We must make the implicit grant visible to the code below, else the
* additional grants will fail.
*/
CommandCounterIncrement();
/*
* Because of the implicit grant above, a CREATEROLE user who creates
* a role has the ability to grant that role back to themselves with
* the INHERIT or SET options, if they wish to inherit the role's
* privileges or be able to SET ROLE to it. The createrole_self_grant
* GUC can be used to make this happen automatically. This has no
* security implications since the same user is able to make the same
* grant using an explicit GRANT statement; it's just convenient.
*/
if (createrole_self_grant_enabled)
AddRoleMems(currentUserId, stmt->role, roleid,
memberSpecs, memberIds,
currentUserId, &createrole_self_grant_options);
}
/*
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*
* NB: No permissions check is required here. If you have enough rights to
* create a role, you can add any members you like.
*/
AddRoleMems(currentUserId, stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
InvalidOid, &popt);
popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
popt.admin = true;
AddRoleMems(currentUserId, stmt->role, roleid,
adminmembers, roleSpecsToIds(adminmembers),
InvalidOid, &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
return roleid;
}
/*
* 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.
*/
Oid
AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
bool new_record_repl[Natts_pg_authid] = {0};
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
new_tuple;
Form_pg_authid authform;
ListCell *option;
char *rolename;
char *password = NULL; /* user password */
int connlimit = -1; /* maximum connections allowed */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
DefElem *disreplication = NULL;
DefElem *dconnlimit = NULL;
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
Oid currentUserId = GetUserId();
GrantRoleOptions popt;
check_rolespec_name(stmt->role,
_("Cannot alter reserved roles."));
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0)
{
if (dpassword)
errorConflictingDefElem(defel, pstate);
dpassword = defel;
}
else if (strcmp(defel->defname, "superuser") == 0)
{
if (dissuper)
errorConflictingDefElem(defel, pstate);
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
errorConflictingDefElem(defel, pstate);
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
errorConflictingDefElem(defel, pstate);
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
errorConflictingDefElem(defel, pstate);
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
errorConflictingDefElem(defel, pstate);
dcanlogin = defel;
}
else if (strcmp(defel->defname, "isreplication") == 0)
{
if (disreplication)
errorConflictingDefElem(defel, pstate);
disreplication = defel;
}
else if (strcmp(defel->defname, "connectionlimit") == 0)
{
if (dconnlimit)
errorConflictingDefElem(defel, pstate);
dconnlimit = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0 &&
stmt->action != 0)
{
if (drolemembers)
errorConflictingDefElem(defel, pstate);
drolemembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
errorConflictingDefElem(defel, pstate);
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "bypassrls") == 0)
{
if (dbypassRLS)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dpassword && dpassword->arg)
password = strVal(dpassword->arg);
if (dconnlimit)
{
connlimit = intVal(dconnlimit->arg);
if (connlimit < -1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid connection limit: %d", connlimit)));
}
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
tuple = get_rolespec_tuple(stmt->role);
authform = (Form_pg_authid) GETSTRUCT(tuple);
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid;
/* To mess with a superuser in any way you gotta be superuser. */
if (!superuser() && authform->rolsuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may alter roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
if (!superuser() && dissuper)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may change the %s attribute.",
"SUPERUSER", "SUPERUSER")));
/*
* Most changes to a role require that you both have CREATEROLE privileges
* and also ADMIN OPTION on the role.
*/
if (!have_createrole_privilege() ||
!is_admin_of_role(GetUserId(), roleid))
{
/* things an unprivileged user certainly can't do */
if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
dvalidUntil || disreplication || dbypassRLS)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
"CREATEROLE", "ADMIN", rolename)));
/* an unprivileged user can change their own password */
if (dpassword && roleid != currentUserId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("To change another role's password, the current user must have the %s attribute and the %s option on the role.",
"CREATEROLE", "ADMIN")));
}
else if (!superuser())
{
/*
* Even if you have both CREATEROLE and ADMIN OPTION on a role, you
* can only change the CREATEDB, REPLICATION, or BYPASSRLS attributes
* if they are set for your own role (or you are the superuser).
*/
if (dcreatedb && !have_createdb_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may change the %s attribute.",
"CREATEDB", "CREATEDB")));
if (disreplication && !has_rolreplication(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may change the %s attribute.",
"REPLICATION", "REPLICATION")));
if (dbypassRLS && !has_bypassrls_privilege(currentUserId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may change the %s attribute.",
"BYPASSRLS", "BYPASSRLS")));
}
/* To add members to a role, you need ADMIN OPTION. */
if (drolemembers && !is_admin_of_role(currentUserId, roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s option on role \"%s\" may add members.",
"ADMIN", rolename)));
/* Convert validuntil to internal form */
if (dvalidUntil)
{
validUntil_datum = DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
validUntil_null = false;
}
else
{
/* fetch existing setting in case hook needs it */
validUntil_datum = SysCacheGetAttr(AUTHNAME, tuple,
Anum_pg_authid_rolvaliduntil,
&validUntil_null);
}
/*
* Call the password checking hook if there is one defined
*/
if (check_password_hook && password)
(*check_password_hook) (rolename,
password,
get_password_type(password),
validUntil_datum,
validUntil_null);
/*
* Build an updated tuple, perusing the information just obtained
*/
/*
* issuper/createrole/etc
*/
if (dissuper)
{
bool should_be_super = boolVal(dissuper->arg);
if (!should_be_super && roleid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("permission denied to alter role"),
errdetail("The bootstrap superuser must have the %s attribute.",
"SUPERUSER")));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(should_be_super);
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
}
if (dinherit)
{
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(boolVal(dinherit->arg));
new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
}
if (dcreaterole)
{
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(boolVal(dcreaterole->arg));
new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
}
if (dcreatedb)
{
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(boolVal(dcreatedb->arg));
new_record_repl[Anum_pg_authid_rolcreatedb - 1] = true;
}
if (dcanlogin)
{
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(boolVal(dcanlogin->arg));
new_record_repl[Anum_pg_authid_rolcanlogin - 1] = true;
}
if (disreplication)
{
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(boolVal(disreplication->arg));
new_record_repl[Anum_pg_authid_rolreplication - 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)
{
char *shadow_pass;
const char *logdetail = NULL;
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
plain_crypt_verify(rolename, password, "", &logdetail) == STATUS_OK)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, rolename,
password);
new_record[Anum_pg_authid_rolpassword - 1] =
CStringGetTextDatum(shadow_pass);
}
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 */
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
if (dbypassRLS)
{
new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(boolVal(dbypassRLS->arg));
new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
}
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
InitGrantRoleOptions(&popt);
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
*/
if (drolemembers)
{
List *rolemembers = (List *) drolemembers->arg;
CommandCounterIncrement();
if (stmt->action == +1) /* add members to role */
AddRoleMems(currentUserId, rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
InvalidOid, &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(currentUserId, rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
InvalidOid, &popt, DROP_RESTRICT);
}
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
return roleid;
}
/*
* ALTER ROLE ... SET
*/
Oid
AlterRoleSet(AlterRoleSetStmt *stmt)
{
HeapTuple roletuple;
Form_pg_authid roleform;
Oid databaseid = InvalidOid;
Oid roleid = InvalidOid;
if (stmt->role)
{
check_rolespec_name(stmt->role,
_("Cannot alter reserved roles."));
roletuple = get_rolespec_tuple(stmt->role);
roleform = (Form_pg_authid) GETSTRUCT(roletuple);
roleid = roleform->oid;
/*
* Obtain a lock on the role and make sure it didn't go away in the
* meantime.
*/
shdepLockAndCheckObject(AuthIdRelationId, roleid);
/*
* To mess with a superuser you gotta be superuser; otherwise you need
* CREATEROLE plus admin option on the target role; unless you're just
* trying to change your own settings
*/
if (roleform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute may alter roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
}
else
{
if ((!have_createrole_privilege() ||
!is_admin_of_role(GetUserId(), roleid))
&& roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter role"),
errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may alter this role.",
"CREATEROLE", "ADMIN", NameStr(roleform->rolname))));
}
ReleaseSysCache(roletuple);
}
/* look up and lock the database, if specified */
if (stmt->database != NULL)
{
databaseid = get_database_oid(stmt->database, false);
shdepLockAndCheckObject(DatabaseRelationId, databaseid);
if (!stmt->role)
{
/*
* If no role is specified, then this is effectively the same as
* ALTER DATABASE ... SET, so use the same permission check.
*/
if (!object_ownercheck(DatabaseRelationId, databaseid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
stmt->database);
}
}
if (!stmt->role && !stmt->database)
{
/* Must be superuser to alter settings globally. */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to alter setting"),
errdetail("Only roles with the %s attribute may alter settings globally.",
"SUPERUSER")));
}
AlterSetting(databaseid, roleid, stmt->setstmt);
return roleid;
}
/*
* DROP ROLE
*/
void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
pg_auth_members_rel;
ListCell *item;
List *role_oids = NIL;
if (!have_createrole_privilege())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop role"),
errdetail("Only roles with the %s attribute and the %s option on the target roles may drop roles.",
"CREATEROLE", "ADMIN")));
/*
* Scan the pg_authid relation to find the Oid of the role(s) to be
* deleted and perform preliminary permissions and sanity checks.
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
foreach(item, stmt->roles)
{
RoleSpec *rolspec = lfirst(item);
char *role;
HeapTuple tuple,
tmp_tuple;
Form_pg_authid roleform;
ScanKeyData scankey;
SysScanDesc sscan;
Oid roleid;
if (rolspec->roletype != ROLESPEC_CSTRING)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("cannot use special role specifier in DROP ROLE")));
role = rolspec->rolename;
tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(tuple))
{
if (!stmt->missing_ok)
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", role)));
}
else
{
ereport(NOTICE,
(errmsg("role \"%s\" does not exist, skipping",
role)));
}
continue;
}
roleform = (Form_pg_authid) GETSTRUCT(tuple);
roleid = roleform->oid;
if (roleid == GetUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current user cannot be dropped")));
if (roleid == GetOuterUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current user cannot be dropped")));
if (roleid == GetSessionUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("session user cannot be dropped")));
/*
* For safety's sake, we allow createrole holders to drop ordinary
* roles but not superuser roles, and only if they also have ADMIN
* OPTION.
*/
if (roleform->rolsuper && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop role"),
errdetail("Only roles with the %s attribute may drop roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
if (!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to drop role"),
errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may drop this role.",
"CREATEROLE", "ADMIN", NameStr(roleform->rolname))));
/* DROP hook for the role being removed */
InvokeObjectDropHook(AuthIdRelationId, roleid, 0);
/* Don't leak the syscache tuple */
ReleaseSysCache(tuple);
/*
* 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);
/*
* If there is a pg_auth_members entry that has one of the roles to be
* dropped as the roleid or member, it should be silently removed, but
* if there is a pg_auth_members entry that has one of the roles to be
* dropped as the grantor, the operation should fail.
*
* It's possible, however, that a single pg_auth_members entry could
* fall into multiple categories - e.g. the user could do "GRANT foo
* TO bar GRANTED BY baz" and then "DROP ROLE baz, bar". We want such
* an operation to succeed regardless of the order in which the
* to-be-dropped roles are passed to DROP ROLE.
*
* To make that work, we remove all pg_auth_members entries that can
* be silently removed in this loop, and then below we'll make a
* second pass over the list of roles to be removed and check for any
* remaining dependencies.
*/
ScanKeyInit(&scankey,
Anum_pg_auth_members_roleid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
sscan = systable_beginscan(pg_auth_members_rel, AuthMemRoleMemIndexId,
true, NULL, 1, &scankey);
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
{
Form_pg_auth_members authmem_form;
authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
deleteSharedDependencyRecordsFor(AuthMemRelationId,
authmem_form->oid, 0);
CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
}
systable_endscan(sscan);
ScanKeyInit(&scankey,
Anum_pg_auth_members_member,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(roleid));
sscan = systable_beginscan(pg_auth_members_rel, AuthMemMemRoleIndexId,
true, NULL, 1, &scankey);
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
{
Form_pg_auth_members authmem_form;
authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
deleteSharedDependencyRecordsFor(AuthMemRelationId,
authmem_form->oid, 0);
CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self);
}
systable_endscan(sscan);
/*
* 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();
/* Looks tentatively OK, add it to the list if not there yet. */
role_oids = list_append_unique_oid(role_oids, roleid);
}
/*
* Second pass over the roles to be removed.
*/
foreach(item, role_oids)
{
Oid roleid = lfirst_oid(item);
HeapTuple tuple;
Form_pg_authid roleform;
char *detail;
char *detail_log;
/*
* Re-find the pg_authid tuple.
*
* Since we've taken a lock on the role OID, it shouldn't be possible
* for the tuple to have been deleted -- or for that matter updated --
* unless the user is manually modifying the system catalogs.
*/
tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for role %u", roleid);
roleform = (Form_pg_authid) GETSTRUCT(tuple);
/*
* Check for pg_shdepend entries depending on this role.
*
* This needs to happen after we've completed removing any
* pg_auth_members entries that can be removed silently, in order to
* avoid spurious failures. See notes above for more details.
*/
if (checkSharedDependencies(AuthIdRelationId, roleid,
&detail, &detail_log))
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("role \"%s\" cannot be dropped because some objects depend on it",
NameStr(roleform->rolname)),
errdetail_internal("%s", detail),
errdetail_log("%s", detail_log)));
/*
* Remove the role from the pg_authid table
*/
CatalogTupleDelete(pg_authid_rel, &tuple->t_self);
ReleaseSysCache(tuple);
/*
* Remove any comments or security labels on this role.
*/
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
/*
* Remove settings for this role.
*/
DropSetting(InvalidOid, roleid);
}
/*
* Now we can clean up; but keep locks until commit.
*/
table_close(pg_auth_members_rel, NoLock);
table_close(pg_authid_rel, NoLock);
}
/*
* Rename role
*/
ObjectAddress
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;
ObjectAddress address;
Form_pg_authid authform;
rel = table_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
oldtuple = SearchSysCache1(AUTHNAME, CStringGetDatum(oldname));
if (!HeapTupleIsValid(oldtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", oldname)));
/*
* 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.
*/
authform = (Form_pg_authid) GETSTRUCT(oldtuple);
roleid = authform->oid;
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")));
/*
* Check that the user is not trying to rename a system role and not
* trying to rename a role into the reserved "pg_" namespace.
*/
if (IsReservedName(NameStr(authform->rolname)))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
NameStr(authform->rolname)),
errdetail("Role names starting with \"pg_\" are reserved.")));
if (IsReservedName(newname))
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
newname),
errdetail("Role names starting with \"pg_\" are reserved.")));
/*
* If built with appropriate switch, whine when regression-testing
* conventions for role names are violated.
*/
#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
if (strncmp(newname, "regress_", 8) != 0)
elog(WARNING, "roles created by regression test cases should have names starting with \"regress_\"");
#endif
/* make sure the new name doesn't exist */
if (SearchSysCacheExists1(AUTHNAME, CStringGetDatum(newname)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists", newname)));
/*
* Only superusers can mess with superusers. Otherwise, a user with
* CREATEROLE can rename a role for which they have ADMIN OPTION.
*/
if (authform->rolsuper)
{
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to rename role"),
errdetail("Only roles with the %s attribute may rename roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
}
else
{
if (!have_createrole_privilege() ||
!is_admin_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to rename role"),
errdetail("Only roles with the %s attribute and the %s option on role \"%s\" may rename this role.",
"CREATEROLE", "ADMIN", NameStr(authform->rolname))));
}
/* 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(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
/* 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;
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
ObjectAddressSet(address, AuthIdRelationId, roleid);
ReleaseSysCache(oldtuple);
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(rel, NoLock);
return address;
}
/*
* GrantRoleStmt
*
* Grant/Revoke roles to/from roles
*/
void
GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
GrantRoleOptions popt;
Oid currentUserId = GetUserId();
/* Parse options list. */
InitGrantRoleOptions(&popt);
foreach(item, stmt->opt)
{
DefElem *opt = (DefElem *) lfirst(item);
char *optval = defGetString(opt);
if (strcmp(opt->defname, "admin") == 0)
{
popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
if (parse_bool(optval, &popt.admin))
continue;
}
else if (strcmp(opt->defname, "inherit") == 0)
{
popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
if (parse_bool(optval, &popt.inherit))
continue;
}
else if (strcmp(opt->defname, "set") == 0)
{
popt.specified |= GRANT_ROLE_SPECIFIED_SET;
if (parse_bool(optval, &popt.set))
continue;
}
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized role option \"%s\"", opt->defname),
parser_errposition(pstate, opt->location));
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized value for role option \"%s\": \"%s\"",
opt->defname, optval),
parser_errposition(pstate, opt->location)));
}
/* Lookup OID of grantor, if specified. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
grantor = InvalidOid;
grantee_ids = roleSpecsToIds(stmt->grantee_roles);
/* AccessShareLock is enough since we aren't modifying pg_authid */
pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
/*
* Step through all of the granted roles and add, update, or remove
* entries in pg_auth_members as appropriate. If stmt->is_grant is true,
* we are adding new grants or, if they already exist, updating options on
* those grants. If stmt->is_grant is false, we are revoking grants or
* removing options from them.
*/
foreach(item, stmt->granted_roles)
{
AccessPriv *priv = (AccessPriv *) lfirst(item);
char *rolename = priv->priv_name;
Oid roleid;
/* Must reject priv(columns) and ALL PRIVILEGES(columns) */
if (rolename == NULL || priv->cols != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("column names cannot be included in GRANT/REVOKE ROLE")));
roleid = get_role_oid(rolename, false);
check_role_membership_authorization(currentUserId,
roleid, stmt->is_grant);
if (stmt->is_grant)
AddRoleMems(currentUserId, rolename, roleid,
stmt->grantee_roles, grantee_ids,
grantor, &popt);
else
DelRoleMems(currentUserId, rolename, roleid,
stmt->grantee_roles, grantee_ids,
grantor, &popt, stmt->behavior);
}
/*
* Close pg_authid, but keep lock till commit.
*/
table_close(pg_authid_rel, NoLock);
}
/*
* DropOwnedObjects
*
* Drop the objects owned by a given list of roles.
*/
void
DropOwnedObjects(DropOwnedStmt *stmt)
{
List *role_ids = roleSpecsToIds(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"),
errdetail("Only roles with privileges of role \"%s\" may drop objects owned by it.",
GetUserNameFromId(roleid, false))));
}
/* 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 = roleSpecsToIds(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"),
errdetail("Only roles with privileges of role \"%s\" may reassign objects owned by it.",
GetUserNameFromId(roleid, false))));
}
/* Must have privileges on the receiving side too */
newrole = get_rolespec_oid(stmt->newrole, false);
if (!has_privs_of_role(GetUserId(), newrole))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to reassign objects"),
errdetail("Only roles with privileges of role \"%s\" may reassign objects to it.",
GetUserNameFromId(newrole, false))));
/* Ok, do it */
shdepReassignOwned(role_ids, newrole);
}
/*
* roleSpecsToIds
*
* Given a list of RoleSpecs, generate a list of role OIDs in the same order.
*
* ROLESPEC_PUBLIC is not allowed.
*/
List *
roleSpecsToIds(List *memberNames)
{
List *result = NIL;
ListCell *l;
foreach(l, memberNames)
{
RoleSpec *rolespec = lfirst_node(RoleSpec, l);
Oid roleid;
roleid = get_rolespec_oid(rolespec, false);
result = lappend_oid(result, roleid);
}
return result;
}
/*
* AddRoleMems -- Add given members to the specified role
*
* currentUserId: OID of role performing the operation
* rolename: name of role to add to (used only for error messages)
* roleid: OID of role to add to
* memberSpecs: list of RoleSpec of roles to add (used only for error messages)
* memberIds: OIDs of roles to add
* grantorId: OID that should be recorded as having granted the membership
* (InvalidOid if not set explicitly)
* popt: information about grant options
*/
static void
AddRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
ListCell *specitem;
ListCell *iditem;
Assert(list_length(memberSpecs) == list_length(memberIds));
/* Validate grantor (and resolve implicit grantor if not specified). */
grantorId = check_role_grantor(currentUserId, roleid, grantorId, true);
pg_authmem_rel = table_open(AuthMemRelationId, RowExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
/*
* Only allow changes to this role by one backend at a time, so that we
* can check integrity constraints like the lack of circular ADMIN OPTION
* grants without fear of race conditions.
*/
LockSharedObject(AuthIdRelationId, roleid, 0,
ShareUpdateExclusiveLock);
/* Preliminary sanity checks. */
forboth(specitem, memberSpecs, iditem, memberIds)
{
RoleSpec *memberRole = lfirst_node(RoleSpec, specitem);
Oid memberid = lfirst_oid(iditem);
/*
* pg_database_owner is never a role member. Lifting this restriction
* would require a policy decision about membership loops. One could
* prevent loops, which would include making "ALTER DATABASE x OWNER
* TO proposed_datdba" fail if is_member_of_role(pg_database_owner,
* proposed_datdba). Hence, gaining a membership could reduce what a
* role could do. Alternately, one could allow these memberships to
* complete loops. A role could then have actual WITH ADMIN OPTION on
* itself, prompting a decision about is_admin_of_role() treatment of
* the case.
*
* Lifting this restriction also has policy implications for ownership
* of shared objects (databases and tablespaces). We allow such
* ownership, but we might find cause to ban it in the future.
* Designing such a ban would more troublesome if the design had to
* address pg_database_owner being a member of role FOO that owns a
* shared object. (The effect of such ownership is that any owner of
* another database can act as the owner of affected shared objects.)
*/
if (memberid == ROLE_PG_DATABASE_OWNER)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("role \"%s\" cannot be a member of any role",
get_rolespec_name(memberRole)));
/*
* 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, get_rolespec_name(memberRole))));
}
/*
* Disallow attempts to grant ADMIN OPTION back to a user who granted it
* to you, similar to what check_circularity does for ACLs. We want the
* chains of grants to remain acyclic, so that it's always possible to use
* REVOKE .. CASCADE to clean up all grants that depend on the one being
* revoked.
*
* NB: This check might look redundant with the check for membership loops
* above, but it isn't. That's checking for role-member loop (e.g. A is a
* member of B and B is a member of A) while this is checking for a
* member-grantor loop (e.g. A gave ADMIN OPTION on X to B and now B, who
* has no other source of ADMIN OPTION on X, tries to give ADMIN OPTION on
* X back to A).
*/
if (popt->admin && grantorId != BOOTSTRAP_SUPERUSERID)
{
CatCList *memlist;
RevokeRoleGrantAction *actions;
int i;
/* Get the list of members for this role. */
memlist = SearchSysCacheList1(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid));
/*
* Figure out what would happen if we removed all existing grants to
* every role to which we've been asked to make a new grant.
*/
actions = initialize_revoke_actions(memlist);
foreach(iditem, memberIds)
{
Oid memberid = lfirst_oid(iditem);
if (memberid == BOOTSTRAP_SUPERUSERID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("%s option cannot be granted back to your own grantor",
"ADMIN")));
plan_member_revoke(memlist, actions, memberid);
}
/*
* If the result would be that the grantor role would no longer have
* the ability to perform the grant, then the proposed grant would
* create a circularity.
*/
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple authmem_tuple;
Form_pg_auth_members authmem_form;
authmem_tuple = &memlist->members[i]->tuple;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
if (actions[i] == RRG_NOOP &&
authmem_form->member == grantorId &&
authmem_form->admin_option)
break;
}
if (i >= memlist->n_members)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("%s option cannot be granted back to your own grantor",
"ADMIN")));
ReleaseSysCacheList(memlist);
}
/* Now perform the catalog updates. */
forboth(specitem, memberSpecs, iditem, memberIds)
{
RoleSpec *memberRole = lfirst_node(RoleSpec, specitem);
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
/* Common initialization for possible insert or update */
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);
/* Find any existing tuple */
authmem_tuple = SearchSysCache3(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid),
ObjectIdGetDatum(grantorId));
/*
* If we found a tuple, update it with new option values, unless there
* are no changes, in which case issue a WARNING.
*
* If we didn't find a tuple, just insert one.
*/
if (HeapTupleIsValid(authmem_tuple))
{
Form_pg_auth_members authmem_form;
bool at_least_one_change = false;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
&& authmem_form->admin_option != popt->admin)
{
new_record[Anum_pg_auth_members_admin_option - 1] =
BoolGetDatum(popt->admin);
new_record_repl[Anum_pg_auth_members_admin_option - 1] =
true;
at_least_one_change = true;
}
if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
&& authmem_form->inherit_option != popt->inherit)
{
new_record[Anum_pg_auth_members_inherit_option - 1] =
BoolGetDatum(popt->inherit);
new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
true;
at_least_one_change = true;
}
if ((popt->specified & GRANT_ROLE_SPECIFIED_SET) != 0
&& authmem_form->set_option != popt->set)
{
new_record[Anum_pg_auth_members_set_option - 1] =
BoolGetDatum(popt->set);
new_record_repl[Anum_pg_auth_members_set_option - 1] =
true;
at_least_one_change = true;
}
if (!at_least_one_change)
{
ereport(NOTICE,
(errmsg("role \"%s\" has already been granted membership in role \"%s\" by role \"%s\"",
get_rolespec_name(memberRole), rolename,
GetUserNameFromId(grantorId, false))));
ReleaseSysCache(authmem_tuple);
continue;
}
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple);
ReleaseSysCache(authmem_tuple);
}
else
{
Oid objectId;
Oid *newmembers = palloc(sizeof(Oid));
/*
* The values for these options can be taken directly from 'popt'.
* Either they were specified, or the defaults as set by
* InitGrantRoleOptions are correct.
*/
new_record[Anum_pg_auth_members_admin_option - 1] =
BoolGetDatum(popt->admin);
new_record[Anum_pg_auth_members_set_option - 1] =
BoolGetDatum(popt->set);
/*
* If the user specified a value for the inherit option, use
* whatever was specified. Otherwise, set the default value based
* on the role-level property.
*/
if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
new_record[Anum_pg_auth_members_inherit_option - 1] =
popt->inherit;
else
{
HeapTuple mrtup;
Form_pg_authid mrform;
mrtup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(memberid));
if (!HeapTupleIsValid(mrtup))
elog(ERROR, "cache lookup failed for role %u", memberid);
mrform = (Form_pg_authid) GETSTRUCT(mrtup);
new_record[Anum_pg_auth_members_inherit_option - 1] =
mrform->rolinherit;
ReleaseSysCache(mrtup);
}
/* get an OID for the new row and insert it */
objectId = GetNewOidWithIndex(pg_authmem_rel, AuthMemOidIndexId,
Anum_pg_auth_members_oid);
new_record[Anum_pg_auth_members_oid - 1] = objectId;
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
/* updateAclDependencies wants to pfree array inputs */
newmembers[0] = grantorId;
updateAclDependencies(AuthMemRelationId, objectId,
0, InvalidOid,
0, NULL,
1, newmembers);
}
/* CCI after each change, in case there are duplicates in list */
CommandCounterIncrement();
}
/*
* Close pg_authmem, but keep lock till commit.
*/
table_close(pg_authmem_rel, NoLock);
}
/*
* 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
* memberSpecs: list of RoleSpec of roles to del (used only for error messages)
* memberIds: OIDs of roles to del
* grantorId: who is revoking the membership
* popt: information about grant options
* behavior: RESTRICT or CASCADE behavior for recursive removal
*/
static void
DelRoleMems(Oid currentUserId, const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
Oid grantorId, GrantRoleOptions *popt, DropBehavior behavior)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
ListCell *specitem;
ListCell *iditem;
CatCList *memlist;
RevokeRoleGrantAction *actions;
int i;
Assert(list_length(memberSpecs) == list_length(memberIds));
/* Validate grantor (and resolve implicit grantor if not specified). */
grantorId = check_role_grantor(currentUserId, roleid, grantorId, false);
pg_authmem_rel = table_open(AuthMemRelationId, RowExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
/*
* Only allow changes to this role by one backend at a time, so that we
* can check for things like dependent privileges without fear of race
* conditions.
*/
LockSharedObject(AuthIdRelationId, roleid, 0,
ShareUpdateExclusiveLock);
memlist = SearchSysCacheList1(AUTHMEMROLEMEM, ObjectIdGetDatum(roleid));
actions = initialize_revoke_actions(memlist);
/*
* We may need to recurse to dependent privileges if DROP_CASCADE was
* specified, or refuse to perform the operation if dependent privileges
* exist and DROP_RESTRICT was specified. plan_single_revoke() will figure
* out what to do with each catalog tuple.
*/
forboth(specitem, memberSpecs, iditem, memberIds)
{
RoleSpec *memberRole = lfirst(specitem);
Oid memberid = lfirst_oid(iditem);
if (!plan_single_revoke(memlist, actions, memberid, grantorId,
popt, behavior))
{
ereport(WARNING,
(errmsg("role \"%s\" has not been granted membership in role \"%s\" by role \"%s\"",
get_rolespec_name(memberRole), rolename,
GetUserNameFromId(grantorId, false))));
continue;
}
}
/*
* We now know what to do with each catalog tuple: it should either be
* left alone, deleted, or just have the admin_option flag cleared.
* Perform the appropriate action in each case.
*/
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple authmem_tuple;
Form_pg_auth_members authmem_form;
if (actions[i] == RRG_NOOP)
continue;
authmem_tuple = &memlist->members[i]->tuple;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
if (actions[i] == RRG_DELETE_GRANT)
{
/*
* Remove the entry altogether, after first removing its
* dependencies
*/
deleteSharedDependencyRecordsFor(AuthMemRelationId,
authmem_form->oid, 0);
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
/* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
/* Build a tuple to update with */
if (actions[i] == RRG_REMOVE_ADMIN_OPTION)
{
new_record[Anum_pg_auth_members_admin_option - 1] =
BoolGetDatum(false);
new_record_repl[Anum_pg_auth_members_admin_option - 1] =
true;
}
else if (actions[i] == RRG_REMOVE_INHERIT_OPTION)
{
new_record[Anum_pg_auth_members_inherit_option - 1] =
BoolGetDatum(false);
new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
true;
}
else if (actions[i] == RRG_REMOVE_SET_OPTION)
{
new_record[Anum_pg_auth_members_set_option - 1] =
BoolGetDatum(false);
new_record_repl[Anum_pg_auth_members_set_option - 1] =
true;
}
else
elog(ERROR, "unknown role revoke action");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple);
}
}
ReleaseSysCacheList(memlist);
/*
* Close pg_authmem, but keep lock till commit.
*/
table_close(pg_authmem_rel, NoLock);
}
/*
* Check that currentUserId has permission to modify the membership list for
* roleid. Throw an error if not.
*/
static void
check_role_membership_authorization(Oid currentUserId, Oid roleid,
bool is_grant)
{
/*
* The charter of pg_database_owner is to have exactly one, implicit,
* situation-dependent member. There's no technical need for this
* restriction. (One could lift it and take the further step of making
* object_ownercheck(DatabaseRelationId, ...) equivalent to
* has_privs_of_role(roleid, ROLE_PG_DATABASE_OWNER), in which case
* explicit, situation-independent members could act as the owner of any
* database.)
*/
if (is_grant && roleid == ROLE_PG_DATABASE_OWNER)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("role \"%s\" cannot have explicit members",
GetUserNameFromId(roleid, false)));
/* To mess with a superuser role, you gotta be superuser. */
if (superuser_arg(roleid))
{
if (!superuser_arg(currentUserId))
{
if (is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant role \"%s\"",
GetUserNameFromId(roleid, false)),
errdetail("Only roles with the %s attribute may grant roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
else
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke role \"%s\"",
GetUserNameFromId(roleid, false)),
errdetail("Only roles with the %s attribute may revoke roles with the %s attribute.",
"SUPERUSER", "SUPERUSER")));
}
}
else
{
/*
* Otherwise, must have admin option on the role to be changed.
*/
if (!is_admin_of_role(currentUserId, roleid))
{
if (is_grant)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant role \"%s\"",
GetUserNameFromId(roleid, false)),
errdetail("Only roles with the %s option on role \"%s\" may grant this role.",
"ADMIN", GetUserNameFromId(roleid, false))));
else
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke role \"%s\"",
GetUserNameFromId(roleid, false)),
errdetail("Only roles with the %s option on role \"%s\" may revoke this role.",
"ADMIN", GetUserNameFromId(roleid, false))));
}
}
}
/*
* Sanity-check, or infer, the grantor for a GRANT or REVOKE statement
* targeting a role.
*
* The grantor must always be either a role with ADMIN OPTION on the role in
* which membership is being granted, or the bootstrap superuser. This is
* similar to the restriction enforced by select_best_grantor, except that
* roles don't have owners, so we regard the bootstrap superuser as the
* implicit owner.
*
* If the grantor was not explicitly specified by the user, grantorId should
* be passed as InvalidOid, and this function will infer the user to be
* recorded as the grantor. In many cases, this will be the current user, but
* things get more complicated when the current user doesn't possess ADMIN
* OPTION on the role but rather relies on having SUPERUSER privileges, or
* on inheriting the privileges of a role which does have ADMIN OPTION. See
* below for details.
*
* If the grantor was specified by the user, then it must be a user that
* can legally be recorded as the grantor, as per the rule stated above.
* This is an integrity constraint, not a permissions check, and thus even
* superusers are subject to this restriction. However, there is also a
* permissions check: to specify a role as the grantor, the current user
* must possess the privileges of that role. Superusers will always pass
* this check, but for non-superusers it may lead to an error.
*
* The return value is the OID to be regarded as the grantor when executing
* the operation.
*/
static Oid
check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant)
{
/* If the grantor ID was not specified, pick one to use. */
if (!OidIsValid(grantorId))
{
/*
* Grants where the grantor is recorded as the bootstrap superuser do
* not depend on any other existing grants, so always default to this
* interpretation when possible.
*/
if (superuser_arg(currentUserId))
return BOOTSTRAP_SUPERUSERID;
/*
* Otherwise, the grantor must either have ADMIN OPTION on the role or
* inherit the privileges of a role which does. In the former case,
* record the grantor as the current user; in the latter, pick one of
* the roles that is "most directly" inherited by the current role
* (i.e. fewest "hops").
*
* (We shouldn't fail to find a best grantor, because we've already
* established that the current user has permission to perform the
* operation.)
*/
grantorId = select_best_admin(currentUserId, roleid);
if (!OidIsValid(grantorId))
elog(ERROR, "no possible grantors");
return grantorId;
}
/*
* If an explicit grantor is specified, it must be a role whose privileges
* the current user possesses.
*
* It should also be a role that has ADMIN OPTION on the target role, but
* we check this condition only in case of GRANT. For REVOKE, no matching
* grant should exist anyway, but if it somehow does, let the user get rid
* of it.
*/
if (is_grant)
{
if (!has_privs_of_role(currentUserId, grantorId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
GetUserNameFromId(grantorId, false)),
errdetail("Only roles with privileges of role \"%s\" may grant privileges as this role.",
GetUserNameFromId(grantorId, false))));
if (grantorId != BOOTSTRAP_SUPERUSERID &&
select_best_admin(grantorId, roleid) != grantorId)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to grant privileges as role \"%s\"",
GetUserNameFromId(grantorId, false)),
errdetail("The grantor must have the %s option on role \"%s\".",
"ADMIN", GetUserNameFromId(roleid, false))));
}
else
{
if (!has_privs_of_role(currentUserId, grantorId))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to revoke privileges granted by role \"%s\"",
GetUserNameFromId(grantorId, false)),
errdetail("Only roles with privileges of role \"%s\" may revoke privileges granted by this role.",
GetUserNameFromId(grantorId, false))));
}
/*
* If a grantor was specified explicitly, always attribute the grant to
* that role (unless we error out above).
*/
return grantorId;
}
/*
* Initialize an array of RevokeRoleGrantAction objects.
*
* 'memlist' should be a list of all grants for the target role.
*
* This constructs an array indicating that no actions are to be performed;
* that is, every element is initially RRG_NOOP.
*/
static RevokeRoleGrantAction *
initialize_revoke_actions(CatCList *memlist)
{
RevokeRoleGrantAction *result;
int i;
if (memlist->n_members == 0)
return NULL;
result = palloc(sizeof(RevokeRoleGrantAction) * memlist->n_members);
for (i = 0; i < memlist->n_members; i++)
result[i] = RRG_NOOP;
return result;
}
/*
* Figure out what we would need to do in order to revoke a grant, or just the
* admin option on a grant, given that there might be dependent privileges.
*
* 'memlist' should be a list of all grants for the target role.
*
* Whatever actions prove to be necessary will be signalled by updating
* 'actions'.
*
* If behavior is DROP_RESTRICT, an error will occur if there are dependent
* role membership grants; if DROP_CASCADE, those grants will be scheduled
* for deletion.
*
* The return value is true if the matching grant was found in the list,
* and false if not.
*/
static bool
plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
Oid member, Oid grantor, GrantRoleOptions *popt,
DropBehavior behavior)
{
int i;
/*
* If popt.specified == 0, we're revoking the grant entirely; otherwise,
* we expect just one bit to be set, and we're revoking the corresponding
* option. As of this writing, there's no syntax that would allow for an
* attempt to revoke multiple options at once, and the logic below
* wouldn't work properly if such syntax were added, so assert that our
* caller isn't trying to do that.
*/
Assert(pg_popcount32(popt->specified) <= 1);
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple authmem_tuple;
Form_pg_auth_members authmem_form;
authmem_tuple = &memlist->members[i]->tuple;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
if (authmem_form->member == member &&
authmem_form->grantor == grantor)
{
if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
{
/*
* Revoking the INHERIT option doesn't change anything for
* dependent privileges, so we don't need to recurse.
*/
actions[i] = RRG_REMOVE_INHERIT_OPTION;
}
else if ((popt->specified & GRANT_ROLE_SPECIFIED_SET) != 0)
{
/* Here too, no need to recurse. */
actions[i] = RRG_REMOVE_SET_OPTION;
}
else
{
bool revoke_admin_option_only;
/*
* Revoking the grant entirely, or ADMIN option on a grant,
* implicates dependent privileges, so we may need to recurse.
*/
revoke_admin_option_only =
(popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0;
plan_recursive_revoke(memlist, actions, i,
revoke_admin_option_only, behavior);
}
return true;
}
}
return false;
}
/*
* Figure out what we would need to do in order to revoke all grants to
* a given member, given that there might be dependent privileges.
*
* 'memlist' should be a list of all grants for the target role.
*
* Whatever actions prove to be necessary will be signalled by updating
* 'actions'.
*/
static void
plan_member_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
Oid member)
{
int i;
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple authmem_tuple;
Form_pg_auth_members authmem_form;
authmem_tuple = &memlist->members[i]->tuple;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
if (authmem_form->member == member)
plan_recursive_revoke(memlist, actions, i, false, DROP_CASCADE);
}
}
/*
* Workhorse for figuring out recursive revocation of role grants.
*
* This is similar to what recursive_revoke() does for ACLs.
*/
static void
plan_recursive_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
int index,
bool revoke_admin_option_only, DropBehavior behavior)
{
bool would_still_have_admin_option = false;
HeapTuple authmem_tuple;
Form_pg_auth_members authmem_form;
int i;
/* If it's already been done, we can just return. */
if (actions[index] == RRG_DELETE_GRANT)
return;
if (actions[index] == RRG_REMOVE_ADMIN_OPTION &&
revoke_admin_option_only)
return;
/* Locate tuple data. */
authmem_tuple = &memlist->members[index]->tuple;
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
/*
* If the existing tuple does not have admin_option set, then we do not
* need to recurse. If we're just supposed to clear that bit we don't need
* to do anything at all; if we're supposed to remove the grant, we need
* to do something, but only to the tuple, and not any others.
*/
if (!revoke_admin_option_only)
{
actions[index] = RRG_DELETE_GRANT;
if (!authmem_form->admin_option)
return;
}
else
{
if (!authmem_form->admin_option)
return;
actions[index] = RRG_REMOVE_ADMIN_OPTION;
}
/* Determine whether the member would still have ADMIN OPTION. */
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple am_cascade_tuple;
Form_pg_auth_members am_cascade_form;
am_cascade_tuple = &memlist->members[i]->tuple;
am_cascade_form = (Form_pg_auth_members) GETSTRUCT(am_cascade_tuple);
if (am_cascade_form->member == authmem_form->member &&
am_cascade_form->admin_option && actions[i] == RRG_NOOP)
{
would_still_have_admin_option = true;
break;
}
}
/* If the member would still have ADMIN OPTION, we need not recurse. */
if (would_still_have_admin_option)
return;
/*
* Recurse to grants that are not yet slated for deletion which have this
* member as the grantor.
*/
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple am_cascade_tuple;
Form_pg_auth_members am_cascade_form;
am_cascade_tuple = &memlist->members[i]->tuple;
am_cascade_form = (Form_pg_auth_members) GETSTRUCT(am_cascade_tuple);
if (am_cascade_form->grantor == authmem_form->member &&
actions[i] != RRG_DELETE_GRANT)
{
if (behavior == DROP_RESTRICT)
ereport(ERROR,
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
errmsg("dependent privileges exist"),
errhint("Use CASCADE to revoke them too.")));
plan_recursive_revoke(memlist, actions, i, false, behavior);
}
}
}
/*
* Initialize a GrantRoleOptions object with default values.
*/
static void
InitGrantRoleOptions(GrantRoleOptions *popt)
{
popt->specified = 0;
popt->admin = false;
popt->inherit = false;
popt->set = true;
}
/*
* GUC check_hook for createrole_self_grant
*/
bool
check_createrole_self_grant(char **newval, void **extra, GucSource source)
{
char *rawstring;
List *elemlist;
ListCell *l;
unsigned options = 0;
unsigned *result;
/* Need a modifiable copy of string */
rawstring = pstrdup(*newval);
if (!SplitIdentifierString(rawstring, ',', &elemlist))
{
/* syntax error in list */
GUC_check_errdetail("List syntax is invalid.");
pfree(rawstring);
list_free(elemlist);
return false;
}
foreach(l, elemlist)
{
char *tok = (char *) lfirst(l);
if (pg_strcasecmp(tok, "SET") == 0)
options |= GRANT_ROLE_SPECIFIED_SET;
else if (pg_strcasecmp(tok, "INHERIT") == 0)
options |= GRANT_ROLE_SPECIFIED_INHERIT;
else
{
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
pfree(rawstring);
list_free(elemlist);
return false;
}
}
pfree(rawstring);
list_free(elemlist);
result = (unsigned *) guc_malloc(LOG, sizeof(unsigned));
*result = options;
*extra = result;
return true;
}
/*
* GUC assign_hook for createrole_self_grant
*/
void
assign_createrole_self_grant(const char *newval, void *extra)
{
unsigned options = *(unsigned *) extra;
createrole_self_grant_enabled = (options != 0);
createrole_self_grant_options.specified = GRANT_ROLE_SPECIFIED_ADMIN
| GRANT_ROLE_SPECIFIED_INHERIT
| GRANT_ROLE_SPECIFIED_SET;
createrole_self_grant_options.admin = false;
createrole_self_grant_options.inherit =
(options & GRANT_ROLE_SPECIFIED_INHERIT) != 0;
createrole_self_grant_options.set =
(options & GRANT_ROLE_SPECIFIED_SET) != 0;
}