2587 lines
78 KiB
C
2587 lines
78 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* user.c
|
|
* Commands for manipulating roles (formerly called users).
|
|
*
|
|
* Portions Copyright (c) 1996-2023, 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/timestamp.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 = "";
|
|
bool createrole_self_grant_enabled = false;
|
|
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 %s.",
|
|
"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 %s.",
|
|
"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 %s.",
|
|
"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 %s.",
|
|
"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 %s.",
|
|
"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 user 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 %s.",
|
|
"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_addresses = 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;
|
|
ObjectAddress *role_address;
|
|
|
|
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 %s.",
|
|
"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. */
|
|
role_address = palloc(sizeof(ObjectAddress));
|
|
role_address->classId = AuthIdRelationId;
|
|
role_address->objectId = roleid;
|
|
role_address->objectSubId = 0;
|
|
role_addresses = lappend(role_addresses, role_address);
|
|
}
|
|
|
|
/*
|
|
* Second pass over the roles to be removed.
|
|
*/
|
|
foreach(item, role_addresses)
|
|
{
|
|
ObjectAddress *role_address = lfirst(item);
|
|
Oid roleid = role_address->objectId;
|
|
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 %s.",
|
|
"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,
|
|
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, 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,
|
|
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 %s.",
|
|
"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 %s.",
|
|
"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;
|
|
}
|