diff options
Diffstat (limited to 'src/backend/commands/user.c')
-rw-r--r-- | src/backend/commands/user.c | 1335 |
1 files changed, 583 insertions, 752 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 3c70c579785..131f1896f92 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * * user.c - * Commands for manipulating users and groups. + * Commands for manipulating roles (formerly called users). * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.151 2005/04/14 20:03:24 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.152 2005/06/28 05:08:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -14,15 +14,14 @@ #include "access/heapam.h" #include "catalog/indexing.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" #include "catalog/pg_database.h" -#include "catalog/pg_group.h" -#include "catalog/pg_shadow.h" -#include "catalog/pg_type.h" #include "commands/user.h" #include "libpq/crypt.h" #include "miscadmin.h" -#include "utils/acl.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "utils/flatfiles.h" #include "utils/fmgroids.h" #include "utils/guc.h" @@ -32,46 +31,46 @@ extern bool Password_encryption; - -static void CheckPgUserAclNotNull(void); -static void UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, - List *members); -static IdList *IdListToArray(List *members); -static List *IdArrayToList(IdList *oldarray); +static List *roleNamesToIds(List *memberNames); +static void AddRoleMems(const char *rolename, Oid roleid, + List *memberNames, List *memberIds, + Oid grantorId, bool admin_opt); +static void DelRoleMems(const char *rolename, Oid roleid, + List *memberNames, List *memberIds, + bool admin_opt); /* - * CREATE USER + * CREATE ROLE */ void -CreateUser(CreateUserStmt *stmt) +CreateRole(CreateRoleStmt *stmt) { - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; - HeapScanDesc scan; + Relation pg_authid_rel; + TupleDesc pg_authid_dsc; HeapTuple tuple; - Datum new_record[Natts_pg_shadow]; - char new_record_nulls[Natts_pg_shadow]; - bool user_exists = false, - sysid_exists = false, - havesysid = false; - int max_id; + Datum new_record[Natts_pg_authid]; + char new_record_nulls[Natts_pg_authid]; + Oid roleid; ListCell *item; ListCell *option; - char *password = NULL; /* PostgreSQL user password */ + char *password = NULL; /* user password */ bool encrypt_password = Password_encryption; /* encrypt password? */ char encrypted_password[MD5_PASSWD_LEN + 1]; - int sysid = 0; /* PgSQL system id (valid if havesysid) */ + bool issuper = false; /* Make the user a superuser? */ + bool createrole = false; /* Can this user create roles? */ bool createdb = false; /* Can the user create databases? */ - bool createuser = false; /* Can this user create users? */ - List *groupElts = NIL; /* The groups the user is a member of */ + bool canlogin = false; /* Can this user login? */ + List *roleElts = NIL; /* roles the user is a member of */ + List *rolememElts = NIL; /* roles which will be members of this role */ char *validUntil = NULL; /* The time the login is valid * until */ DefElem *dpassword = NULL; - DefElem *dsysid = NULL; DefElem *dcreatedb = NULL; - DefElem *dcreateuser = NULL; - DefElem *dgroupElts = NULL; + DefElem *dcreaterole = NULL; + DefElem *dcanlogin = NULL; + DefElem *droleElts = NULL; + DefElem *drolememElts = NULL; DefElem *dvalidUntil = NULL; /* Extract options from the statement node tree */ @@ -95,11 +94,16 @@ CreateUser(CreateUserStmt *stmt) } else if (strcmp(defel->defname, "sysid") == 0) { - if (dsysid) + ereport(WARNING, + (errmsg("SYSID can no longer be specified"))); + } + else if (strcmp(defel->defname, "createrole") == 0) + { + if (dcreaterole) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - dsysid = defel; + dcreaterole = defel; } else if (strcmp(defel->defname, "createdb") == 0) { @@ -109,21 +113,29 @@ CreateUser(CreateUserStmt *stmt) errmsg("conflicting or redundant options"))); dcreatedb = defel; } - else if (strcmp(defel->defname, "createuser") == 0) + else if (strcmp(defel->defname, "canlogin") == 0) { - if (dcreateuser) + if (dcanlogin) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - dcreateuser = defel; + dcanlogin = defel; } - else if (strcmp(defel->defname, "groupElts") == 0) + else if (strcmp(defel->defname, "roleElts") == 0) { - if (dgroupElts) + if (droleElts) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - dgroupElts = defel; + droleElts = defel; + } + else if (strcmp(defel->defname, "rolememElts") == 0) + { + if (drolememElts) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + drolememElts = defel; } else if (strcmp(defel->defname, "validUntil") == 0) { @@ -140,83 +152,51 @@ CreateUser(CreateUserStmt *stmt) if (dcreatedb) createdb = intVal(dcreatedb->arg) != 0; - if (dcreateuser) - createuser = intVal(dcreateuser->arg) != 0; - if (dsysid) + if (dcreaterole) { - sysid = intVal(dsysid->arg); - if (sysid <= 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("user ID must be positive"))); - havesysid = true; + createrole = intVal(dcreaterole->arg) != 0; + /* XXX issuper is implied by createrole for now */ + issuper = createrole; } + if (dcanlogin) + canlogin = intVal(dcanlogin->arg) != 0; if (dvalidUntil) validUntil = strVal(dvalidUntil->arg); if (dpassword) password = strVal(dpassword->arg); - if (dgroupElts) - groupElts = (List *) dgroupElts->arg; + if (droleElts) + roleElts = (List *) droleElts->arg; + if (drolememElts) + rolememElts = (List *) drolememElts->arg; /* Check some permissions first */ - if (password) - CheckPgUserAclNotNull(); - if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to create users"))); + errmsg("must be superuser to create roles"))); - if (strcmp(stmt->user, "public") == 0) + if (strcmp(stmt->role, "public") == 0) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), - errmsg("user name \"%s\" is reserved", - stmt->user))); + errmsg("role name \"%s\" is reserved", + stmt->role))); /* - * Scan the pg_shadow relation to be certain the user or id doesn't - * already exist. Note we secure exclusive lock, because we also need - * to be sure of what the next usesysid should be, and we need to - * protect our eventual update of the flat password file. + * Check the pg_authid relation to be certain the role doesn't + * already exist. Note we secure exclusive lock because + * we need to protect our eventual update of the flat auth file. */ - pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); - - scan = heap_beginscan(pg_shadow_rel, SnapshotNow, 0, NULL); - max_id = 99; /* start auto-assigned ids at 100 */ - while (!user_exists && !sysid_exists && - (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - Form_pg_shadow shadow_form = (Form_pg_shadow) GETSTRUCT(tuple); - int32 this_sysid; - - user_exists = (strcmp(NameStr(shadow_form->usename), stmt->user) == 0); - - this_sysid = shadow_form->usesysid; - if (havesysid) /* customized id wanted */ - sysid_exists = (this_sysid == sysid); - else - { - /* pick 1 + max */ - if (this_sysid > max_id) - max_id = this_sysid; - } - } - heap_endscan(scan); + pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock); + pg_authid_dsc = RelationGetDescr(pg_authid_rel); - if (user_exists) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("user \"%s\" already exists", - stmt->user))); - if (sysid_exists) + tuple = SearchSysCache(AUTHNAME, + PointerGetDatum(stmt->role), + 0, 0, 0); + if (HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("user ID %d is already assigned", sysid))); - - /* If no sysid given, use max existing id + 1 */ - if (!havesysid) - sysid = max_id + 1; + errmsg("role \"%s\" already exists", + stmt->role))); /* * Build a tuple to insert @@ -224,105 +204,123 @@ CreateUser(CreateUserStmt *stmt) MemSet(new_record, 0, sizeof(new_record)); MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); - new_record[Anum_pg_shadow_usename - 1] = - DirectFunctionCall1(namein, CStringGetDatum(stmt->user)); - new_record[Anum_pg_shadow_usesysid - 1] = Int32GetDatum(sysid); - AssertState(BoolIsValid(createdb)); - new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb); - AssertState(BoolIsValid(createuser)); - new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser); - /* superuser gets catupd right by default */ - new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser); + 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_rolcreaterole - 1] = BoolGetDatum(createrole); + new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb); + /* superuser gets catupdate right by default */ + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper); + new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); if (password) { if (!encrypt_password || isMD5(password)) - new_record[Anum_pg_shadow_passwd - 1] = + new_record[Anum_pg_authid_rolpassword - 1] = DirectFunctionCall1(textin, CStringGetDatum(password)); else { - if (!EncryptMD5(password, stmt->user, strlen(stmt->user), + if (!EncryptMD5(password, stmt->role, strlen(stmt->role), encrypted_password)) elog(ERROR, "password encryption failed"); - new_record[Anum_pg_shadow_passwd - 1] = + new_record[Anum_pg_authid_rolpassword - 1] = DirectFunctionCall1(textin, CStringGetDatum(encrypted_password)); } } else - new_record_nulls[Anum_pg_shadow_passwd - 1] = 'n'; + new_record_nulls[Anum_pg_authid_rolpassword - 1] = 'n'; if (validUntil) - new_record[Anum_pg_shadow_valuntil - 1] = - DirectFunctionCall1(abstimein, CStringGetDatum(validUntil)); + new_record[Anum_pg_authid_rolvaliduntil - 1] = + DirectFunctionCall3(timestamptz_in, + CStringGetDatum(validUntil), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else - new_record_nulls[Anum_pg_shadow_valuntil - 1] = 'n'; + new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = 'n'; - new_record_nulls[Anum_pg_shadow_useconfig - 1] = 'n'; + new_record_nulls[Anum_pg_authid_rolconfig - 1] = 'n'; - tuple = heap_formtuple(pg_shadow_dsc, new_record, new_record_nulls); + tuple = heap_formtuple(pg_authid_dsc, new_record, new_record_nulls); /* - * Insert new record in the pg_shadow table + * Insert new record in the pg_authid table */ - simple_heap_insert(pg_shadow_rel, tuple); + roleid = simple_heap_insert(pg_authid_rel, tuple); + Assert(OidIsValid(roleid)); /* Update indexes */ - CatalogUpdateIndexes(pg_shadow_rel, tuple); + CatalogUpdateIndexes(pg_authid_rel, tuple); /* - * Add the user to the groups specified. We'll just call the below - * AlterGroup for this. + * Add the new role to the specified existing roles. */ - foreach(item, groupElts) + foreach(item, roleElts) { - AlterGroupStmt ags; + char *oldrolename = strVal(lfirst(item)); + Oid oldroleid = get_roleid_checked(oldrolename); - ags.name = strVal(lfirst(item)); /* the group name to add - * this in */ - ags.action = +1; - ags.listUsers = list_make1(makeInteger(sysid)); - AlterGroup(&ags, "CREATE USER"); + AddRoleMems(oldrolename, oldroleid, + list_make1(makeString(stmt->role)), + list_make1_oid(roleid), + GetUserId(), false); } /* + * Add the specified members to this new role. + */ + AddRoleMems(stmt->role, roleid, + rolememElts, roleNamesToIds(rolememElts), + GetUserId(), false); + + /* * Now we can clean up; but keep lock until commit (to avoid possible * deadlock when commit code tries to acquire lock). */ - heap_close(pg_shadow_rel, NoLock); + heap_close(pg_authid_rel, NoLock); /* - * Set flag to update flat password file at commit. + * Set flag to update flat auth file at commit. */ - user_file_update_needed(); + auth_file_update_needed(); } - /* - * ALTER USER + * ALTER ROLE */ void -AlterUser(AlterUserStmt *stmt) +AlterRole(AlterRoleStmt *stmt) { - Datum new_record[Natts_pg_shadow]; - char new_record_nulls[Natts_pg_shadow]; - char new_record_repl[Natts_pg_shadow]; - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; + Datum new_record[Natts_pg_authid]; + char new_record_nulls[Natts_pg_authid]; + char new_record_repl[Natts_pg_authid]; + Relation pg_authid_rel; + TupleDesc pg_authid_dsc; HeapTuple tuple, new_tuple; ListCell *option; - char *password = NULL; /* PostgreSQL user password */ + char *password = NULL; /* user password */ bool encrypt_password = Password_encryption; /* encrypt password? */ char encrypted_password[MD5_PASSWD_LEN + 1]; - int createdb = -1; /* Can the user create databases? */ - int createuser = -1; /* Can this user create users? */ + int issuper = -1; /* Make the user a superuser? */ + int createrole = -1; /* Can this user create roles? */ + int createdb = -1; /* Can the user create databases? */ + int canlogin = -1; /* Can this user login? */ + int adminopt = 0; /* Can this user grant this role to others? */ + List *rolememElts = NIL; /* The roles which will be added/removed to this role */ char *validUntil = NULL; /* The time the login is valid * until */ DefElem *dpassword = NULL; DefElem *dcreatedb = NULL; - DefElem *dcreateuser = NULL; + DefElem *dcreaterole = NULL; + DefElem *dcanlogin = NULL; + DefElem *dadminopt = NULL; DefElem *dvalidUntil = NULL; + DefElem *drolememElts = NULL; + Oid roleid; /* Extract options from the statement node tree */ foreach(option, stmt->options) @@ -351,13 +349,29 @@ AlterUser(AlterUserStmt *stmt) errmsg("conflicting or redundant options"))); dcreatedb = defel; } - else if (strcmp(defel->defname, "createuser") == 0) + else if (strcmp(defel->defname, "createrole") == 0) { - if (dcreateuser) + if (dcreaterole) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - dcreateuser = defel; + dcreaterole = defel; + } + else if (strcmp(defel->defname, "canlogin") == 0) + { + if (dcanlogin) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dcanlogin = defel; + } + else if (strcmp(defel->defname, "adminopt") == 0) + { + if (dadminopt) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dadminopt = defel; } else if (strcmp(defel->defname, "validUntil") == 0) { @@ -367,6 +381,14 @@ AlterUser(AlterUserStmt *stmt) errmsg("conflicting or redundant options"))); dvalidUntil = defel; } + else if (strcmp(defel->defname, "rolememElts") == 0 && stmt->action != 0) + { + if (drolememElts) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + drolememElts = defel; + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); @@ -374,42 +396,54 @@ AlterUser(AlterUserStmt *stmt) if (dcreatedb) createdb = intVal(dcreatedb->arg); - if (dcreateuser) - createuser = intVal(dcreateuser->arg); + if (dcreaterole) + { + createrole = intVal(dcreaterole->arg); + /* XXX createrole implies issuper for now */ + issuper = createrole; + } + if (dcanlogin) + canlogin = intVal(dcanlogin->arg); + if (dadminopt) + adminopt = intVal(dadminopt->arg); if (dvalidUntil) validUntil = strVal(dvalidUntil->arg); if (dpassword) password = strVal(dpassword->arg); - - if (password) - CheckPgUserAclNotNull(); + if (drolememElts) + rolememElts = (List *) drolememElts->arg; /* must be superuser or just want to change your own password */ if (!superuser() && - !(createdb < 0 && - createuser < 0 && + !(issuper < 0 && + createrole < 0 && + createdb < 0 && + canlogin < 0 && !validUntil && + !rolememElts && + !adminopt && password && - strcmp(GetUserNameFromId(GetUserId()), stmt->user) == 0)) + strcmp(GetUserNameFromId(GetUserId()), stmt->role) == 0)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied"))); /* - * Scan the pg_shadow relation to be certain the user exists. Note we - * secure exclusive lock to protect our update of the flat password - * file. + * Scan the pg_authid relation to be certain the user exists. Note we + * secure exclusive lock to protect our update of the flat auth file. */ - pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); + pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock); + pg_authid_dsc = RelationGetDescr(pg_authid_rel); - tuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(stmt->user), + tuple = SearchSysCache(AUTHNAME, + PointerGetDatum(stmt->role), 0, 0, 0); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("user \"%s\" does not exist", stmt->user))); + errmsg("role \"%s\" does not exist", stmt->role))); + + roleid = HeapTupleGetOid(tuple); /* * Build an updated tuple, perusing the information just obtained @@ -418,65 +452,79 @@ AlterUser(AlterUserStmt *stmt) MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); MemSet(new_record_repl, ' ', sizeof(new_record_repl)); - new_record[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein, - CStringGetDatum(stmt->user)); - new_record_repl[Anum_pg_shadow_usename - 1] = 'r'; - - /* createdb */ - if (createdb >= 0) - { - new_record[Anum_pg_shadow_usecreatedb - 1] = BoolGetDatum(createdb > 0); - new_record_repl[Anum_pg_shadow_usecreatedb - 1] = 'r'; - } + new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, + CStringGetDatum(stmt->role)); + new_record_repl[Anum_pg_authid_rolname - 1] = 'r'; /* - * createuser (superuser) and catupd + * issuper/createrole/catupdate/etc * - * XXX It's rather unclear how to handle catupd. It's probably best to + * XXX It's rather unclear how to handle catupdate. It's probably best to * keep it equal to the superuser status, otherwise you could end up * with a situation where no existing superuser can alter the - * catalogs, including pg_shadow! + * catalogs, including pg_authid! */ - if (createuser >= 0) + if (issuper >= 0) { - new_record[Anum_pg_shadow_usesuper - 1] = BoolGetDatum(createuser > 0); - new_record_repl[Anum_pg_shadow_usesuper - 1] = 'r'; + new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0); + new_record_repl[Anum_pg_authid_rolsuper - 1] = 'r'; - new_record[Anum_pg_shadow_usecatupd - 1] = BoolGetDatum(createuser > 0); - new_record_repl[Anum_pg_shadow_usecatupd - 1] = 'r'; + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0); + new_record_repl[Anum_pg_authid_rolcatupdate - 1] = 'r'; + } + + if (createrole >= 0) + { + new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0); + new_record_repl[Anum_pg_authid_rolcreaterole - 1] = 'r'; + } + + if (createdb >= 0) + { + new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb > 0); + new_record_repl[Anum_pg_authid_rolcreatedb - 1] = 'r'; + } + + if (canlogin >= 0) + { + new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin > 0); + new_record_repl[Anum_pg_authid_rolcanlogin - 1] = 'r'; } /* password */ if (password) { if (!encrypt_password || isMD5(password)) - new_record[Anum_pg_shadow_passwd - 1] = + new_record[Anum_pg_authid_rolpassword - 1] = DirectFunctionCall1(textin, CStringGetDatum(password)); else { - if (!EncryptMD5(password, stmt->user, strlen(stmt->user), + if (!EncryptMD5(password, stmt->role, strlen(stmt->role), encrypted_password)) elog(ERROR, "password encryption failed"); - new_record[Anum_pg_shadow_passwd - 1] = + new_record[Anum_pg_authid_rolpassword - 1] = DirectFunctionCall1(textin, CStringGetDatum(encrypted_password)); } - new_record_repl[Anum_pg_shadow_passwd - 1] = 'r'; + new_record_repl[Anum_pg_authid_rolpassword - 1] = 'r'; } /* valid until */ if (validUntil) { - new_record[Anum_pg_shadow_valuntil - 1] = - DirectFunctionCall1(abstimein, CStringGetDatum(validUntil)); - new_record_repl[Anum_pg_shadow_valuntil - 1] = 'r'; + new_record[Anum_pg_authid_rolvaliduntil - 1] = + DirectFunctionCall3(timestamptz_in, + CStringGetDatum(validUntil), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = 'r'; } - new_tuple = heap_modifytuple(tuple, pg_shadow_dsc, new_record, + new_tuple = heap_modifytuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl); - simple_heap_update(pg_shadow_rel, &tuple->t_self, new_tuple); + simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple); /* Update indexes */ - CatalogUpdateIndexes(pg_shadow_rel, new_tuple); + CatalogUpdateIndexes(pg_authid_rel, new_tuple); ReleaseSysCache(tuple); heap_freetuple(new_tuple); @@ -485,59 +533,68 @@ AlterUser(AlterUserStmt *stmt) * Now we can clean up; but keep lock until commit (to avoid possible * deadlock when commit code tries to acquire lock). */ - heap_close(pg_shadow_rel, NoLock); + heap_close(pg_authid_rel, NoLock); + + if (stmt->action == +1) /* add members to role */ + AddRoleMems(stmt->role, roleid, + rolememElts, roleNamesToIds(rolememElts), + GetUserId(), adminopt); + else if (stmt->action == -1) /* drop members from role */ + DelRoleMems(stmt->role, roleid, + rolememElts, roleNamesToIds(rolememElts), + adminopt); /* - * Set flag to update flat password file at commit. + * Set flag to update flat auth file at commit. */ - user_file_update_needed(); + auth_file_update_needed(); } /* - * ALTER USER ... SET + * ALTER ROLE ... SET */ void -AlterUserSet(AlterUserSetStmt *stmt) +AlterRoleSet(AlterRoleSetStmt *stmt) { char *valuestr; HeapTuple oldtuple, newtuple; Relation rel; - Datum repl_val[Natts_pg_shadow]; - char repl_null[Natts_pg_shadow]; - char repl_repl[Natts_pg_shadow]; + Datum repl_val[Natts_pg_authid]; + char repl_null[Natts_pg_authid]; + char repl_repl[Natts_pg_authid]; int i; valuestr = flatten_set_variable_args(stmt->variable, stmt->value); /* * RowExclusiveLock is sufficient, because we don't need to update the - * flat password file. + * flat auth file. */ - rel = heap_open(ShadowRelationId, RowExclusiveLock); - oldtuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(stmt->user), + rel = heap_open(AuthIdRelationId, RowExclusiveLock); + oldtuple = SearchSysCache(AUTHNAME, + PointerGetDatum(stmt->role), 0, 0, 0); if (!HeapTupleIsValid(oldtuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("user \"%s\" does not exist", stmt->user))); + errmsg("role \"%s\" does not exist", stmt->role))); if (!(superuser() || - ((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetUserId())) + (HeapTupleGetOid(oldtuple) == GetUserId()))) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied"))); - for (i = 0; i < Natts_pg_shadow; i++) + for (i = 0; i < Natts_pg_authid; i++) repl_repl[i] = ' '; - repl_repl[Anum_pg_shadow_useconfig - 1] = 'r'; + repl_repl[Anum_pg_authid_rolconfig - 1] = 'r'; if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL) { /* RESET ALL */ - repl_null[Anum_pg_shadow_useconfig - 1] = 'n'; + repl_null[Anum_pg_authid_rolconfig - 1] = 'n'; } else { @@ -545,10 +602,10 @@ AlterUserSet(AlterUserSetStmt *stmt) bool isnull; ArrayType *array; - repl_null[Anum_pg_shadow_useconfig - 1] = ' '; + repl_null[Anum_pg_authid_rolconfig - 1] = ' '; - datum = SysCacheGetAttr(SHADOWNAME, oldtuple, - Anum_pg_shadow_useconfig, &isnull); + datum = SysCacheGetAttr(AUTHNAME, oldtuple, + Anum_pg_authid_rolconfig, &isnull); array = isnull ? NULL : DatumGetArrayTypeP(datum); @@ -558,9 +615,9 @@ AlterUserSet(AlterUserSetStmt *stmt) array = GUCArrayDelete(array, stmt->variable); if (array) - repl_val[Anum_pg_shadow_useconfig - 1] = PointerGetDatum(array); + repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(array); else - repl_null[Anum_pg_shadow_useconfig - 1] = 'n'; + repl_null[Anum_pg_authid_rolconfig - 1] = 'n'; } newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); @@ -574,60 +631,61 @@ AlterUserSet(AlterUserSetStmt *stmt) /* - * DROP USER + * DROP ROLE */ void -DropUser(DropUserStmt *stmt) +DropRole(DropRoleStmt *stmt) { - Relation pg_shadow_rel; - TupleDesc pg_shadow_dsc; + Relation pg_authid_rel, pg_auth_members_rel; ListCell *item; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to drop users"))); + errmsg("must be superuser to drop roles"))); /* - * Scan the pg_shadow relation to find the usesysid of the user to be + * Scan the pg_authid relation to find the Oid of the role to be * deleted. Note we secure exclusive lock, because we need to protect - * our update of the flat password file. + * our update of the flat auth file. */ - pg_shadow_rel = heap_open(ShadowRelationId, ExclusiveLock); - pg_shadow_dsc = RelationGetDescr(pg_shadow_rel); + pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock); + pg_auth_members_rel = heap_open(AuthMemRelationId, ExclusiveLock); - foreach(item, stmt->users) + foreach(item, stmt->roles) { - const char *user = strVal(lfirst(item)); + const char *role = strVal(lfirst(item)); HeapTuple tuple, tmp_tuple; Relation pg_rel; TupleDesc pg_dsc; ScanKeyData scankey; HeapScanDesc scan; - AclId usesysid; + CatCList *auth_mem_list; + Oid roleid; + int i; - tuple = SearchSysCache(SHADOWNAME, - PointerGetDatum(user), + tuple = SearchSysCache(AUTHNAME, + PointerGetDatum(role), 0, 0, 0); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("user \"%s\" does not exist", user))); + errmsg("role \"%s\" does not exist", role))); - usesysid = ((Form_pg_shadow) GETSTRUCT(tuple))->usesysid; + roleid = HeapTupleGetOid(tuple); - if (usesysid == GetUserId()) + if (roleid == GetUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("current user cannot be dropped"))); - if (usesysid == GetSessionUserId()) + errmsg("current role cannot be dropped"))); + if (roleid == GetSessionUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("session user cannot be dropped"))); + errmsg("session role cannot be dropped"))); /* - * Check if user still owns a database. If so, error out. + * Check if role still owns a database. If so, error out. * * (It used to be that this function would drop the database * automatically. This is not only very dangerous for people that @@ -639,8 +697,8 @@ DropUser(DropUserStmt *stmt) ScanKeyInit(&scankey, Anum_pg_database_datdba, - BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(usesysid)); + BTEqualStrategyNumber, F_OIDEQ, + roleid); scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey); @@ -651,8 +709,8 @@ DropUser(DropUserStmt *stmt) dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname); ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("user \"%s\" cannot be dropped", user), - errdetail("The user owns database \"%s\".", dbname))); + errmsg("role \"%s\" cannot be dropped", role), + errdetail("The role owns database \"%s\".", dbname))); } heap_endscan(scan); @@ -660,66 +718,64 @@ DropUser(DropUserStmt *stmt) /* * Somehow we'd have to check for tables, views, etc. owned by the - * user as well, but those could be spread out over all sorts of + * role as well, but those could be spread out over all sorts of * databases which we don't have access to (easily). */ /* - * Remove the user from the pg_shadow table + * Remove the role from the pg_authid table */ - simple_heap_delete(pg_shadow_rel, &tuple->t_self); + simple_heap_delete(pg_authid_rel, &tuple->t_self); ReleaseSysCache(tuple); /* - * Remove user from groups + * Remove role from roles * - * try calling alter group drop user for every group + * scan pg_auth_members and remove tuples which have + * roleid == member or roleid == role */ - pg_rel = heap_open(GroupRelationId, ExclusiveLock); - pg_dsc = RelationGetDescr(pg_rel); - scan = heap_beginscan(pg_rel, SnapshotNow, 0, NULL); - while ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) - { - AlterGroupStmt ags; + auth_mem_list = SearchSysCacheList(AUTHMEMROLEMEM, 1, + ObjectIdGetDatum(roleid), + 0, 0, 0); - /* the group name from which to try to drop the user: */ - ags.name = pstrdup(NameStr(((Form_pg_group) GETSTRUCT(tmp_tuple))->groname)); - ags.action = -1; - ags.listUsers = list_make1(makeInteger(usesysid)); - AlterGroup(&ags, "DROP USER"); + for (i = 0; i < auth_mem_list->n_members; i++) + { + HeapTuple authmemtup = &auth_mem_list->members[i]->tuple; + simple_heap_delete(pg_auth_members_rel, &authmemtup->t_self); } - heap_endscan(scan); - heap_close(pg_rel, ExclusiveLock); + ReleaseSysCacheList(auth_mem_list); - /* - * 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 two users who are members of the - * same group --- the AlterGroup for the second user had better - * see the tuple updated from the first one. - */ - CommandCounterIncrement(); + auth_mem_list = SearchSysCacheList(AUTHMEMMEMROLE, 1, + ObjectIdGetDatum(roleid), + 0, 0, 0); + + for (i = 0; i < auth_mem_list->n_members; i++) + { + HeapTuple authmemtup = &auth_mem_list->members[i]->tuple; + simple_heap_delete(pg_auth_members_rel, &authmemtup->t_self); + } + ReleaseSysCacheList(auth_mem_list); } /* * Now we can clean up; but keep lock until commit (to avoid possible * deadlock when commit code tries to acquire lock). */ - heap_close(pg_shadow_rel, NoLock); + heap_close(pg_auth_members_rel, NoLock); + heap_close(pg_authid_rel, NoLock); /* - * Set flag to update flat password file at commit. + * Set flag to update flat auth file at commit. */ - user_file_update_needed(); + auth_file_update_needed(); } - /* - * Rename user + * Rename role */ void -RenameUser(const char *oldname, const char *newname) +RenameRole(const char *oldname, const char *newname) { HeapTuple oldtuple, newtuple; @@ -727,22 +783,23 @@ RenameUser(const char *oldname, const char *newname) Relation rel; Datum datum; bool isnull; - Datum repl_val[Natts_pg_shadow]; - char repl_null[Natts_pg_shadow]; - char repl_repl[Natts_pg_shadow]; + Datum repl_val[Natts_pg_authid]; + char repl_null[Natts_pg_authid]; + char repl_repl[Natts_pg_authid]; int i; + Oid roleid; /* ExclusiveLock because we need to update the password file */ - rel = heap_open(ShadowRelationId, ExclusiveLock); + rel = heap_open(AuthIdRelationId, ExclusiveLock); dsc = RelationGetDescr(rel); - oldtuple = SearchSysCache(SHADOWNAME, + oldtuple = SearchSysCache(AUTHNAME, CStringGetDatum(oldname), 0, 0, 0); if (!HeapTupleIsValid(oldtuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("user \"%s\" does not exist", oldname))); + errmsg("role \"%s\" does not exist", oldname))); /* * XXX Client applications probably store the session user somewhere, @@ -750,43 +807,46 @@ RenameUser(const char *oldname, const char *newname) * not be an actual problem besides a little confusion, so think about * this and decide. */ - if (((Form_pg_shadow) GETSTRUCT(oldtuple))->usesysid == GetSessionUserId()) + + roleid = HeapTupleGetOid(oldtuple); + + if (roleid == GetSessionUserId()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("session user may not be renamed"))); + errmsg("session role may not be renamed"))); /* make sure the new name doesn't exist */ - if (SearchSysCacheExists(SHADOWNAME, + if (SearchSysCacheExists(AUTHNAME, CStringGetDatum(newname), 0, 0, 0)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("user \"%s\" already exists", newname))); + errmsg("role \"%s\" already exists", newname))); /* must be superuser */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to rename users"))); + errmsg("must be superuser to rename roles"))); - for (i = 0; i < Natts_pg_shadow; i++) + for (i = 0; i < Natts_pg_authid; i++) repl_repl[i] = ' '; - repl_repl[Anum_pg_shadow_usename - 1] = 'r'; - repl_val[Anum_pg_shadow_usename - 1] = DirectFunctionCall1(namein, + repl_repl[Anum_pg_authid_rolname - 1] = 'r'; + repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(newname)); - repl_null[Anum_pg_shadow_usename - 1] = ' '; + repl_null[Anum_pg_authid_rolname - 1] = ' '; - datum = heap_getattr(oldtuple, Anum_pg_shadow_passwd, dsc, &isnull); + datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull); if (!isnull && isMD5(DatumGetCString(DirectFunctionCall1(textout, datum)))) { /* MD5 uses the username as salt, so just clear it on a rename */ - repl_repl[Anum_pg_shadow_passwd - 1] = 'r'; - repl_null[Anum_pg_shadow_passwd - 1] = 'n'; + repl_repl[Anum_pg_authid_rolpassword - 1] = 'r'; + repl_null[Anum_pg_authid_rolpassword - 1] = 'n'; ereport(NOTICE, - (errmsg("MD5 password cleared because of user rename"))); + (errmsg("MD5 password cleared because of role rename"))); } newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl); @@ -797,551 +857,322 @@ RenameUser(const char *oldname, const char *newname) ReleaseSysCache(oldtuple); heap_close(rel, NoLock); - user_file_update_needed(); + auth_file_update_needed(); } - /* - * CheckPgUserAclNotNull + * GrantRoleStmt * - * check to see if there is an ACL on pg_shadow - */ -static void -CheckPgUserAclNotNull(void) -{ - HeapTuple htup; - - htup = SearchSysCache(RELOID, - ObjectIdGetDatum(ShadowRelationId), - 0, 0, 0); - if (!HeapTupleIsValid(htup)) /* should not happen, we hope */ - elog(ERROR, "cache lookup failed for relation %u", ShadowRelationId); - - if (heap_attisnull(htup, Anum_pg_class_relacl)) - { - Form_pg_class classForm = (Form_pg_class) GETSTRUCT(htup); - - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("before using passwords you must revoke privileges on %s", - NameStr(classForm->relname)), - errdetail("This restriction is to prevent unprivileged users from reading the passwords."), - errhint("Try REVOKE ALL ON \"%s\" FROM PUBLIC.", - NameStr(classForm->relname)))); - } - - ReleaseSysCache(htup); -} - - -/* - * CREATE GROUP + * Grant/Revoke roles to/from roles */ void -CreateGroup(CreateGroupStmt *stmt) +GrantRole(GrantRoleStmt *stmt) { - Relation pg_group_rel; - HeapScanDesc scan; - HeapTuple tuple; - TupleDesc pg_group_dsc; - bool group_exists = false, - sysid_exists = false, - havesysid = false; - int max_id; - Datum new_record[Natts_pg_group]; - char new_record_nulls[Natts_pg_group]; + Oid grantor; + List *grantee_ids; ListCell *item; - ListCell *option; - List *newlist = NIL; - IdList *grolist; - int sysid = 0; - List *userElts = NIL; - DefElem *dsysid = NULL; - DefElem *duserElts = NULL; - - foreach(option, stmt->options) - { - DefElem *defel = (DefElem *) lfirst(option); - if (strcmp(defel->defname, "sysid") == 0) - { - if (dsysid) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("conflicting or redundant options"))); - dsysid = defel; - } - else if (strcmp(defel->defname, "userElts") == 0) - { - if (duserElts) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("conflicting or redundant options"))); - duserElts = defel; - } - else - elog(ERROR, "option \"%s\" not recognized", - defel->defname); - } - - if (dsysid) - { - sysid = intVal(dsysid->arg); - if (sysid <= 0) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("group ID must be positive"))); - havesysid = true; - } + if (stmt->grantor) + grantor = get_roleid_checked(stmt->grantor); + else + grantor = GetUserId(); - if (duserElts) - userElts = (List *) duserElts->arg; + grantee_ids = roleNamesToIds(stmt->grantee_roles); /* - * Make sure the user can do this. - */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to create groups"))); - - if (strcmp(stmt->name, "public") == 0) - ereport(ERROR, - (errcode(ERRCODE_RESERVED_NAME), - errmsg("group name \"%s\" is reserved", - stmt->name))); - - /* - * Scan the pg_group relation to be certain the group or id doesn't - * already exist. Note we secure exclusive lock, because we also need - * to be sure of what the next grosysid should be, and we need to - * protect our eventual update of the flat group file. + * Step through all of the granted roles and add/remove + * entries for the grantees, or, if admin_opt is set, then + * just add/remove the admin option. + * + * Note: Permissions checking is done by AddRoleMems/DelRoleMems */ - pg_group_rel = heap_open(GroupRelationId, ExclusiveLock); - pg_group_dsc = RelationGetDescr(pg_group_rel); - - scan = heap_beginscan(pg_group_rel, SnapshotNow, 0, NULL); - max_id = 99; /* start auto-assigned ids at 100 */ - while (!group_exists && !sysid_exists && - (tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + foreach(item, stmt->granted_roles) { - Form_pg_group group_form = (Form_pg_group) GETSTRUCT(tuple); - int32 this_sysid; + char *rolename = strVal(lfirst(item)); + Oid roleid = get_roleid_checked(rolename); - group_exists = (strcmp(NameStr(group_form->groname), stmt->name) == 0); - - this_sysid = group_form->grosysid; - if (havesysid) /* customized id wanted */ - sysid_exists = (this_sysid == sysid); + if (stmt->is_grant) + AddRoleMems(rolename, roleid, + stmt->grantee_roles, grantee_ids, + grantor, stmt->admin_opt); else - { - /* pick 1 + max */ - if (this_sysid > max_id) - max_id = this_sysid; - } + DelRoleMems(rolename, roleid, + stmt->grantee_roles, grantee_ids, + stmt->admin_opt); } - heap_endscan(scan); - - if (group_exists) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("group \"%s\" already exists", - stmt->name))); - if (sysid_exists) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("group ID %d is already assigned", sysid))); +} - /* If no sysid given, use max existing id + 1 */ - if (!havesysid) - sysid = max_id + 1; +/* + * roleNamesToIds + * + * Given a list of role names (as String nodes), generate a list of role OIDs + * in the same order. + */ +static List * +roleNamesToIds(List *memberNames) +{ + List *result = NIL; + ListCell *l; - /* - * Translate the given user names to ids - */ - foreach(item, userElts) + foreach(l, memberNames) { - const char *groupuser = strVal(lfirst(item)); - int32 userid = get_usesysid(groupuser); + char *rolename = strVal(lfirst(l)); + Oid roleid = get_roleid_checked(rolename); - if (!list_member_int(newlist, userid)) - newlist = lappend_int(newlist, userid); + result = lappend_oid(result, roleid); } - - /* build an array to insert */ - if (newlist) - grolist = IdListToArray(newlist); - else - grolist = NULL; - - /* - * Form a tuple to insert - */ - new_record[Anum_pg_group_groname - 1] = - DirectFunctionCall1(namein, CStringGetDatum(stmt->name)); - new_record[Anum_pg_group_grosysid - 1] = Int32GetDatum(sysid); - new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(grolist); - - new_record_nulls[Anum_pg_group_groname - 1] = ' '; - new_record_nulls[Anum_pg_group_grosysid - 1] = ' '; - new_record_nulls[Anum_pg_group_grolist - 1] = grolist ? ' ' : 'n'; - - tuple = heap_formtuple(pg_group_dsc, new_record, new_record_nulls); - - /* - * Insert a new record in the pg_group table - */ - simple_heap_insert(pg_group_rel, tuple); - - /* Update indexes */ - CatalogUpdateIndexes(pg_group_rel, tuple); - - /* - * Now we can clean up; but keep lock until commit (to avoid possible - * deadlock when commit code tries to acquire lock). - */ - heap_close(pg_group_rel, NoLock); - - /* - * Set flag to update flat group file at commit. - */ - group_file_update_needed(); + return result; } - /* - * ALTER GROUP + * AddRoleMems -- Add given members to the specified role + * + * rolename: name of role to add to (used only for error messages) + * roleid: OID of role to add to + * memberNames: list of names of roles to add (used only for error messages) + * memberIds: OIDs of roles to add + * grantorId: who is granting the membership + * admin_opt: granting admin option? */ -void -AlterGroup(AlterGroupStmt *stmt, const char *tag) +static void +AddRoleMems(const char *rolename, Oid roleid, + List *memberNames, List *memberIds, + Oid grantorId, bool admin_opt) { - Relation pg_group_rel; - TupleDesc pg_group_dsc; - HeapTuple group_tuple; - IdList *oldarray; - Datum datum; - bool null; - List *newlist; - ListCell *item; + Relation pg_authmem_rel; + TupleDesc pg_authmem_dsc; + ListCell *nameitem; + ListCell *iditem; - /* - * Make sure the user can do this. - */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to alter groups"))); + Assert(list_length(memberNames) == list_length(memberIds)); - /* - * Secure exclusive lock to protect our update of the flat group file. - */ - pg_group_rel = heap_open(GroupRelationId, ExclusiveLock); - pg_group_dsc = RelationGetDescr(pg_group_rel); + /* Skip permission check if nothing to do */ + if (!memberIds) + return; /* - * Fetch existing tuple for group. + * Check permissions: must be superuser or have admin option on the + * role to be changed. + * + * XXX: The admin option is not considered to be inherited through + * multiple roles, unlike normal 'is_member_of_role' privilege checks. */ - group_tuple = SearchSysCache(GRONAME, - PointerGetDatum(stmt->name), - 0, 0, 0); - if (!HeapTupleIsValid(group_tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("group \"%s\" does not exist", stmt->name))); + if (!superuser()) + { + HeapTuple authmem_chk_tuple; + Form_pg_auth_members authmem_chk; - /* Fetch old group membership. */ - datum = heap_getattr(group_tuple, Anum_pg_group_grolist, - pg_group_dsc, &null); - oldarray = null ? NULL : DatumGetIdListP(datum); + if (grantorId != GetUserId()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to set grantor ID"))); + + authmem_chk_tuple = SearchSysCache(AUTHMEMROLEMEM, + ObjectIdGetDatum(roleid), + ObjectIdGetDatum(grantorId), + 0, 0); + if (!HeapTupleIsValid(authmem_chk_tuple)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have admin option on role \"%s\"", + rolename))); - /* initialize list with old array contents */ - newlist = IdArrayToList(oldarray); + authmem_chk = (Form_pg_auth_members) GETSTRUCT(authmem_chk_tuple); + if (!authmem_chk->admin_option) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have admin option on role \"%s\"", + rolename))); + ReleaseSysCache(authmem_chk_tuple); + } /* - * Now decide what to do. + * Secure exclusive lock to protect our update of the flat auth file. */ - AssertState(stmt->action == +1 || stmt->action == -1); + pg_authmem_rel = heap_open(AuthMemRelationId, ExclusiveLock); + pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); - if (stmt->action == +1) /* add users, might also be invoked by - * create user */ + forboth(nameitem, memberNames, iditem, memberIds) { + const char *membername = strVal(lfirst(nameitem)); + Oid memberid = lfirst_oid(iditem); + HeapTuple authmem_tuple; + HeapTuple tuple; + Datum new_record[Natts_pg_auth_members]; + char new_record_nulls[Natts_pg_auth_members]; + char new_record_repl[Natts_pg_auth_members]; + /* - * convert the to be added usernames to sysids and add them to the - * list + * Check if entry for this role/member already exists; + * if so, give warning unless we are adding admin option. */ - foreach(item, stmt->listUsers) + authmem_tuple = SearchSysCache(AUTHMEMROLEMEM, + ObjectIdGetDatum(roleid), + ObjectIdGetDatum(memberid), + 0, 0); + if (HeapTupleIsValid(authmem_tuple) && !admin_opt) { - int32 sysid; - - if (strcmp(tag, "ALTER GROUP") == 0) - { - /* Get the uid of the proposed user to add. */ - sysid = get_usesysid(strVal(lfirst(item))); - } - else if (strcmp(tag, "CREATE USER") == 0) - { - /* - * in this case we already know the uid and it wouldn't be - * in the cache anyway yet - */ - sysid = intVal(lfirst(item)); - } - else - { - elog(ERROR, "unexpected tag: \"%s\"", tag); - sysid = 0; /* keep compiler quiet */ - } - - if (!list_member_int(newlist, sysid)) - newlist = lappend_int(newlist, sysid); + ereport(NOTICE, + (errmsg("role \"%s\" is already a member of role \"%s\"", + membername, rolename))); + ReleaseSysCache(authmem_tuple); + continue; } - /* Do the update */ - UpdateGroupMembership(pg_group_rel, group_tuple, newlist); - } /* endif alter group add user */ + /* Build a tuple to insert or update */ + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); + MemSet(new_record_repl, ' ', sizeof(new_record_repl)); - else if (stmt->action == -1) /* drop users from group */ - { - bool is_dropuser = strcmp(tag, "DROP USER") == 0; + new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); + new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid); + new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); + new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt); - if (newlist == NIL) + if (HeapTupleIsValid(authmem_tuple)) { - if (!is_dropuser) - ereport(WARNING, - (errcode(ERRCODE_WARNING), - errmsg("group \"%s\" does not have any members", - stmt->name))); + new_record_repl[Anum_pg_auth_members_grantor - 1] = 'r'; + new_record_repl[Anum_pg_auth_members_admin_option - 1] = 'r'; + tuple = heap_modifytuple(authmem_tuple, pg_authmem_dsc, + new_record, + new_record_nulls, new_record_repl); + simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + ReleaseSysCache(authmem_tuple); } else { - /* - * convert the to be dropped usernames to sysids and remove - * them from the list - */ - foreach(item, stmt->listUsers) - { - int32 sysid; - - if (!is_dropuser) - { - /* Get the uid of the proposed user to drop. */ - sysid = get_usesysid(strVal(lfirst(item))); - } - else - { - /* for dropuser we already know the uid */ - sysid = intVal(lfirst(item)); - } - if (list_member_int(newlist, sysid)) - newlist = list_delete_int(newlist, sysid); - else if (!is_dropuser) - ereport(WARNING, - (errcode(ERRCODE_WARNING), - errmsg("user \"%s\" is not in group \"%s\"", - strVal(lfirst(item)), stmt->name))); - } - - /* Do the update */ - UpdateGroupMembership(pg_group_rel, group_tuple, newlist); - } /* endif group not null */ - } /* endif alter group drop user */ - - ReleaseSysCache(group_tuple); + tuple = heap_formtuple(pg_authmem_dsc, + new_record, new_record_nulls); + simple_heap_insert(pg_authmem_rel, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + } + } /* * Now we can clean up; but keep lock until commit (to avoid possible * deadlock when commit code tries to acquire lock). */ - heap_close(pg_group_rel, NoLock); - - /* - * Set flag to update flat group file at commit. - */ - group_file_update_needed(); + heap_close(pg_authmem_rel, NoLock); } /* - * Subroutine for AlterGroup: given a pg_group tuple and a desired new - * membership (expressed as an integer list), form and write an updated tuple. - * The pg_group relation must be open and locked already. + * DelRoleMems -- Remove given members from the specified role + * + * rolename: name of role to del from (used only for error messages) + * roleid: OID of role to del from + * memberNames: list of names of roles to del (used only for error messages) + * memberIds: OIDs of roles to del + * admin_opt: remove admin option only? */ static void -UpdateGroupMembership(Relation group_rel, HeapTuple group_tuple, - List *members) +DelRoleMems(const char *rolename, Oid roleid, + List *memberNames, List *memberIds, + bool admin_opt) { - IdList *newarray; - Datum new_record[Natts_pg_group]; - char new_record_nulls[Natts_pg_group]; - char new_record_repl[Natts_pg_group]; - HeapTuple tuple; + Relation pg_authmem_rel; + TupleDesc pg_authmem_dsc; + ListCell *nameitem; + ListCell *iditem; - newarray = IdListToArray(members); + Assert(list_length(memberNames) == list_length(memberIds)); + + /* Skip permission check if nothing to do */ + if (!memberIds) + return; /* - * Form an updated tuple with the new array and write it back. + * Check permissions: must be superuser or have admin option on the + * role to be changed. + * + * XXX: The admin option is not considered to be inherited through + * multiple roles, unlike normal 'is_member_of_role' privilege checks. */ - MemSet(new_record, 0, sizeof(new_record)); - MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); - MemSet(new_record_repl, ' ', sizeof(new_record_repl)); - - new_record[Anum_pg_group_grolist - 1] = PointerGetDatum(newarray); - new_record_repl[Anum_pg_group_grolist - 1] = 'r'; - - tuple = heap_modifytuple(group_tuple, RelationGetDescr(group_rel), - new_record, new_record_nulls, new_record_repl); - - simple_heap_update(group_rel, &group_tuple->t_self, tuple); - - /* Update indexes */ - CatalogUpdateIndexes(group_rel, tuple); -} - - -/* - * Convert an integer list of sysids to an array. - */ -static IdList * -IdListToArray(List *members) -{ - int nmembers = list_length(members); - IdList *newarray; - ListCell *item; - int i; - - newarray = palloc(ARR_OVERHEAD(1) + nmembers * sizeof(int32)); - newarray->size = ARR_OVERHEAD(1) + nmembers * sizeof(int32); - newarray->flags = 0; - newarray->elemtype = INT4OID; - ARR_NDIM(newarray) = 1; /* one dimensional array */ - ARR_LBOUND(newarray)[0] = 1; /* axis starts at one */ - ARR_DIMS(newarray)[0] = nmembers; /* axis is this long */ - i = 0; - foreach(item, members) - ((int *) ARR_DATA_PTR(newarray))[i++] = lfirst_int(item); - - return newarray; -} - -/* - * Convert an array of sysids to an integer list. - */ -static List * -IdArrayToList(IdList *oldarray) -{ - List *newlist = NIL; - int hibound, - i; - - if (oldarray == NULL) - return NIL; - - Assert(ARR_NDIM(oldarray) == 1); - Assert(ARR_ELEMTYPE(oldarray) == INT4OID); - - hibound = ARR_DIMS(oldarray)[0]; - - for (i = 0; i < hibound; i++) + if (!superuser()) { - int32 sysid; + HeapTuple authmem_chk_tuple; + Form_pg_auth_members authmem_chk; + + authmem_chk_tuple = SearchSysCache(AUTHMEMROLEMEM, + ObjectIdGetDatum(roleid), + ObjectIdGetDatum(GetUserId()), + 0, 0); + if (!HeapTupleIsValid(authmem_chk_tuple)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have admin option on role \"%s\"", + rolename))); - sysid = ((int32 *) ARR_DATA_PTR(oldarray))[i]; - /* filter out any duplicates --- probably a waste of time */ - if (!list_member_int(newlist, sysid)) - newlist = lappend_int(newlist, sysid); + authmem_chk = (Form_pg_auth_members) GETSTRUCT(authmem_chk_tuple); + if (!authmem_chk->admin_option) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser or have admin option on role \"%s\"", + rolename))); + ReleaseSysCache(authmem_chk_tuple); } - return newlist; -} - - -/* - * DROP GROUP - */ -void -DropGroup(DropGroupStmt *stmt) -{ - Relation pg_group_rel; - HeapTuple tuple; - /* - * Make sure the user can do this. + * Secure exclusive lock to protect our update of the flat auth file. */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to drop groups"))); + pg_authmem_rel = heap_open(AuthMemRelationId, ExclusiveLock); + pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); - /* - * Secure exclusive lock to protect our update of the flat group file. - */ - pg_group_rel = heap_open(GroupRelationId, ExclusiveLock); + forboth(nameitem, memberNames, iditem, memberIds) + { + const char *membername = strVal(lfirst(nameitem)); + Oid memberid = lfirst_oid(iditem); + HeapTuple authmem_tuple; - /* Find and delete the group. */ + /* + * Find entry for this role/member + */ + authmem_tuple = SearchSysCache(AUTHMEMROLEMEM, + ObjectIdGetDatum(roleid), + ObjectIdGetDatum(memberid), + 0, 0); + if (!HeapTupleIsValid(authmem_tuple)) + { + ereport(WARNING, + (errmsg("role \"%s\" is not a member of role \"%s\"", + membername, rolename))); + continue; + } - tuple = SearchSysCacheCopy(GRONAME, - PointerGetDatum(stmt->name), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("group \"%s\" does not exist", stmt->name))); + if (!admin_opt) + { + /* Remove the entry altogether */ + simple_heap_delete(pg_authmem_rel, &authmem_tuple->t_self); + } + else + { + /* Just turn off the admin option */ + HeapTuple tuple; + Datum new_record[Natts_pg_auth_members]; + char new_record_nulls[Natts_pg_auth_members]; + char new_record_repl[Natts_pg_auth_members]; + + /* Build a tuple to update with */ + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, ' ', sizeof(new_record_nulls)); + MemSet(new_record_repl, ' ', sizeof(new_record_repl)); + + new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false); + new_record_repl[Anum_pg_auth_members_admin_option - 1] = 'r'; + + tuple = heap_modifytuple(authmem_tuple, pg_authmem_dsc, + new_record, + new_record_nulls, new_record_repl); + simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + } - simple_heap_delete(pg_group_rel, &tuple->t_self); + ReleaseSysCache(authmem_tuple); + } /* * Now we can clean up; but keep lock until commit (to avoid possible * deadlock when commit code tries to acquire lock). */ - heap_close(pg_group_rel, NoLock); - - /* - * Set flag to update flat group file at commit. - */ - group_file_update_needed(); -} - - -/* - * Rename group - */ -void -RenameGroup(const char *oldname, const char *newname) -{ - HeapTuple tup; - Relation rel; - - /* ExclusiveLock because we need to update the flat group file */ - rel = heap_open(GroupRelationId, ExclusiveLock); - - tup = SearchSysCacheCopy(GRONAME, - CStringGetDatum(oldname), - 0, 0, 0); - if (!HeapTupleIsValid(tup)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("group \"%s\" does not exist", oldname))); - - /* make sure the new name doesn't exist */ - if (SearchSysCacheExists(GRONAME, - CStringGetDatum(newname), - 0, 0, 0)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("group \"%s\" already exists", newname))); - - /* must be superuser */ - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to rename groups"))); - - /* rename */ - namestrcpy(&(((Form_pg_group) GETSTRUCT(tup))->groname), newname); - simple_heap_update(rel, &tup->t_self, tup); - CatalogUpdateIndexes(rel, tup); - - heap_close(rel, NoLock); - heap_freetuple(tup); - - group_file_update_needed(); + heap_close(pg_authmem_rel, NoLock); } |