aboutsummaryrefslogtreecommitdiffstats
path: root/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c
diff options
context:
space:
mode:
authorvvvv <vvvv@yandex-team.com>2024-11-07 12:29:36 +0300
committervvvv <vvvv@yandex-team.com>2024-11-07 13:49:47 +0300
commitd4c258e9431675bab6745c8638df6e3dfd4dca6b (patch)
treeb5efcfa11351152a4c872fccaea35749141c0b11 /yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c
parent13a4f274caef5cfdaf0263b24e4d6bdd5521472b (diff)
downloadydb-d4c258e9431675bab6745c8638df6e3dfd4dca6b.tar.gz
Moved other yql/essentials libs YQL-19206
init commit_hash:7d4c435602078407bbf20dd3c32f9c90d2bbcbc0
Diffstat (limited to 'yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c')
-rw-r--r--yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c2580
1 files changed, 2580 insertions, 0 deletions
diff --git a/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c
new file mode 100644
index 00000000000..a42c46a775b
--- /dev/null
+++ b/yql/essentials/parser/pg_wrapper/postgresql/src/backend/commands/user.c
@@ -0,0 +1,2580 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 */
+__thread 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 */
+__thread int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+__thread char *createrole_self_grant = "";
+__thread bool createrole_self_grant_enabled = false;
+__thread GrantRoleOptions createrole_self_grant_options;
+
+/* Hook to check passwords in CreateRole() and AlterRole() */
+__thread 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 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 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,
+ 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 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;
+}