postgresql/src/backend/commands/user.c

1189 lines
33 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* user.c
* 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.154 2005/06/28 22:16:44 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
#include "postgres.h"
1999-07-16 07:00:38 +02:00
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
1999-07-16 07:00:38 +02:00
#include "commands/user.h"
#include "libpq/crypt.h"
1999-07-16 07:00:38 +02:00
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
extern bool Password_encryption;
1998-12-14 07:50:32 +01:00
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 ROLE
*/
void
CreateRole(CreateRoleStmt *stmt)
{
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
1999-05-25 18:15:34 +02:00
HeapTuple tuple;
Datum new_record[Natts_pg_authid];
char new_record_nulls[Natts_pg_authid];
Oid roleid;
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
bool encrypt_password = Password_encryption; /* encrypt password? */
char encrypted_password[MD5_PASSWD_LEN + 1];
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 canlogin = false; /* Can this user login? */
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 */
DefElem *dpassword = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcanlogin = NULL;
DefElem *daddroleto = NULL;
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
}
else if (strcmp(defel->defname, "sysid") == 0)
{
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")));
dcreaterole = defel;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreatedb = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcanlogin = defel;
}
else if (strcmp(defel->defname, "addroleto") == 0)
{
if (daddroleto)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
daddroleto = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0)
{
if (drolemembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
drolemembers = defel;
}
else if (strcmp(defel->defname, "adminmembers") == 0)
{
if (dadminmembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dadminmembers = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dcreatedb)
createdb = intVal(dcreatedb->arg) != 0;
if (dcreaterole)
{
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 (daddroleto)
addroleto = (List *) daddroleto->arg;
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
if (dadminmembers)
adminmembers = (List *) dadminmembers->arg;
/* Check some permissions first */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create roles")));
if (strcmp(stmt->role, "public") == 0)
ereport(ERROR,
(errcode(ERRCODE_RESERVED_NAME),
errmsg("role name \"%s\" is reserved",
stmt->role)));
/*
* 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_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
tuple = SearchSysCache(AUTHNAME,
PointerGetDatum(stmt->role),
0, 0, 0);
if (HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists",
stmt->role)));
/*
* Build a tuple to insert
*/
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, ' ', sizeof(new_record_nulls));
new_record[Anum_pg_authid_rolname - 1] =
DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_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_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(password));
else
{
if (!EncryptMD5(password, stmt->role, strlen(stmt->role),
encrypted_password))
elog(ERROR, "password encryption failed");
new_record[Anum_pg_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
}
}
else
new_record_nulls[Anum_pg_authid_rolpassword - 1] = 'n';
if (validUntil)
new_record[Anum_pg_authid_rolvaliduntil - 1] =
DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
else
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = 'n';
new_record_nulls[Anum_pg_authid_rolconfig - 1] = 'n';
tuple = heap_formtuple(pg_authid_dsc, new_record, new_record_nulls);
/*
* Insert new record in the pg_authid table
*/
roleid = simple_heap_insert(pg_authid_rel, tuple);
Assert(OidIsValid(roleid));
/* Update indexes */
CatalogUpdateIndexes(pg_authid_rel, tuple);
/*
* Add the new role to the specified existing roles.
*/
foreach(item, addroleto)
{
char *oldrolename = strVal(lfirst(item));
Oid oldroleid = get_roleid_checked(oldrolename);
AddRoleMems(oldrolename, oldroleid,
list_make1(makeString(stmt->role)),
list_make1_oid(roleid),
GetUserId(), false);
}
/*
* Add the specified members to this new role. adminmembers get the
* admin option, rolemembers don't.
*/
AddRoleMems(stmt->role, roleid,
adminmembers, roleNamesToIds(adminmembers),
GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
GetUserId(), false);
/*
* Now we can clean up; but keep lock until commit (to avoid possible
* deadlock when commit code tries to acquire lock).
*/
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
}
/*
* ALTER ROLE
*/
void
AlterRole(AlterRoleStmt *stmt)
{
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; /* user password */
bool encrypt_password = Password_encryption; /* encrypt password? */
char encrypted_password[MD5_PASSWD_LEN + 1];
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? */
List *rolemembers = NIL; /* roles to be added/removed */
char *validUntil = NULL; /* time the login is valid until */
DefElem *dpassword = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcanlogin = NULL;
DefElem *dvalidUntil = NULL;
DefElem *drolemembers = NULL;
Oid roleid;
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "password") == 0 ||
strcmp(defel->defname, "encryptedPassword") == 0 ||
strcmp(defel->defname, "unencryptedPassword") == 0)
{
if (dpassword)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dpassword = defel;
if (strcmp(defel->defname, "encryptedPassword") == 0)
encrypt_password = true;
else if (strcmp(defel->defname, "unencryptedPassword") == 0)
encrypt_password = false;
}
else if (strcmp(defel->defname, "createdb") == 0)
{
if (dcreatedb)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreatedb = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcreaterole = defel;
}
else if (strcmp(defel->defname, "canlogin") == 0)
{
if (dcanlogin)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dcanlogin = defel;
}
else if (strcmp(defel->defname, "validUntil") == 0)
{
if (dvalidUntil)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
else if (strcmp(defel->defname, "rolemembers") == 0 &&
stmt->action != 0)
{
if (drolemembers)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
drolemembers = defel;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (dcreatedb)
createdb = intVal(dcreatedb->arg);
if (dcreaterole)
{
createrole = intVal(dcreaterole->arg);
/* XXX createrole implies issuper for now */
issuper = createrole;
}
if (dcanlogin)
canlogin = intVal(dcanlogin->arg);
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
if (dpassword)
password = strVal(dpassword->arg);
if (drolemembers)
rolemembers = (List *) drolemembers->arg;
/* must be superuser or just want to change your own password */
if (!superuser() &&
!(issuper < 0 &&
createrole < 0 &&
createdb < 0 &&
canlogin < 0 &&
!validUntil &&
!rolemembers &&
password &&
strcmp(GetUserNameFromId(GetUserId()), stmt->role) == 0))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
/*
* 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_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_authid_dsc = RelationGetDescr(pg_authid_rel);
tuple = SearchSysCache(AUTHNAME,
PointerGetDatum(stmt->role),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", stmt->role)));
roleid = HeapTupleGetOid(tuple);
/*
* Build an updated tuple, perusing the information just obtained
*/
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_authid_rolname - 1] = DirectFunctionCall1(namein,
CStringGetDatum(stmt->role));
new_record_repl[Anum_pg_authid_rolname - 1] = 'r';
/*
* issuper/createrole/catupdate/etc
*
* XXX It's rather unclear how to handle catupdate. It's probably best to
* keep it equal to the superuser status, otherwise you could end up
* with a situation where no existing superuser can alter the
* catalogs, including pg_authid!
*/
if (issuper >= 0)
{
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0);
new_record_repl[Anum_pg_authid_rolsuper - 1] = '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_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(password));
else
{
if (!EncryptMD5(password, stmt->role, strlen(stmt->role),
encrypted_password))
elog(ERROR, "password encryption failed");
new_record[Anum_pg_authid_rolpassword - 1] =
DirectFunctionCall1(textin, CStringGetDatum(encrypted_password));
}
new_record_repl[Anum_pg_authid_rolpassword - 1] = 'r';
}
/* valid until */
if (validUntil)
{
new_record[Anum_pg_authid_rolvaliduntil - 1] =
DirectFunctionCall3(timestamptz_in,
CStringGetDatum(validUntil),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = 'r';
}
new_tuple = heap_modifytuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
/* Update indexes */
CatalogUpdateIndexes(pg_authid_rel, new_tuple);
ReleaseSysCache(tuple);
heap_freetuple(new_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_authid_rel, NoLock);
if (stmt->action == +1) /* add members to role */
AddRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
GetUserId(), false);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(stmt->role, roleid,
rolemembers, roleNamesToIds(rolemembers),
false);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
}
/*
* ALTER ROLE ... SET
*/
void
AlterRoleSet(AlterRoleSetStmt *stmt)
{
char *valuestr;
HeapTuple oldtuple,
newtuple;
Relation rel;
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);
/*
2002-09-04 22:31:48 +02:00
* RowExclusiveLock is sufficient, because we don't need to update the
* flat auth file.
*/
rel = heap_open(AuthIdRelationId, RowExclusiveLock);
oldtuple = SearchSysCache(AUTHNAME,
PointerGetDatum(stmt->role),
0, 0, 0);
if (!HeapTupleIsValid(oldtuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", stmt->role)));
if (!(superuser() ||
(HeapTupleGetOid(oldtuple) == GetUserId())))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied")));
for (i = 0; i < Natts_pg_authid; i++)
repl_repl[i] = ' ';
repl_repl[Anum_pg_authid_rolconfig - 1] = 'r';
2002-09-04 22:31:48 +02:00
if (strcmp(stmt->variable, "all") == 0 && valuestr == NULL)
{
/* RESET ALL */
repl_null[Anum_pg_authid_rolconfig - 1] = 'n';
}
else
{
2002-09-04 22:31:48 +02:00
Datum datum;
bool isnull;
ArrayType *array;
repl_null[Anum_pg_authid_rolconfig - 1] = ' ';
datum = SysCacheGetAttr(AUTHNAME, oldtuple,
Anum_pg_authid_rolconfig, &isnull);
array = isnull ? NULL : DatumGetArrayTypeP(datum);
if (valuestr)
array = GUCArrayAdd(array, stmt->variable, valuestr);
else
array = GUCArrayDelete(array, stmt->variable);
if (array)
repl_val[Anum_pg_authid_rolconfig - 1] = PointerGetDatum(array);
else
repl_null[Anum_pg_authid_rolconfig - 1] = 'n';
}
newtuple = heap_modifytuple(oldtuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
simple_heap_update(rel, &oldtuple->t_self, newtuple);
CatalogUpdateIndexes(rel, newtuple);
ReleaseSysCache(oldtuple);
heap_close(rel, RowExclusiveLock);
}
/*
* DROP ROLE
*/
void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel, pg_auth_members_rel;
ListCell *item;
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to drop roles")));
/*
* 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 auth file.
*/
pg_authid_rel = heap_open(AuthIdRelationId, ExclusiveLock);
pg_auth_members_rel = heap_open(AuthMemRelationId, ExclusiveLock);
foreach(item, stmt->roles)
{
const char *role = strVal(lfirst(item));
HeapTuple tuple,
tmp_tuple;
Relation pg_rel;
TupleDesc pg_dsc;
ScanKeyData scankey;
HeapScanDesc scan;
CatCList *auth_mem_list;
Oid roleid;
int i;
tuple = SearchSysCache(AUTHNAME,
PointerGetDatum(role),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", role)));
roleid = HeapTupleGetOid(tuple);
if (roleid == GetUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("current role cannot be dropped")));
if (roleid == GetSessionUserId())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("session role cannot be dropped")));
/*
* 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
* don't read the manual, it doesn't seem to be the behaviour one
* would expect either.) -- petere 2000/01/14)
*/
pg_rel = heap_open(DatabaseRelationId, AccessShareLock);
pg_dsc = RelationGetDescr(pg_rel);
ScanKeyInit(&scankey,
Anum_pg_database_datdba,
BTEqualStrategyNumber, F_OIDEQ,
roleid);
scan = heap_beginscan(pg_rel, SnapshotNow, 1, &scankey);
if ((tmp_tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
char *dbname;
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
dbname = NameStr(((Form_pg_database) GETSTRUCT(tmp_tuple))->datname);
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("role \"%s\" cannot be dropped", role),
errdetail("The role owns database \"%s\".", dbname)));
}
heap_endscan(scan);
Clean up various to-do items associated with system indexes: pg_database now has unique indexes on oid and on datname. pg_shadow now has unique indexes on usename and on usesysid. pg_am now has unique index on oid. pg_opclass now has unique index on oid. pg_amproc now has unique index on amid+amopclaid+amprocnum. Remove pg_rewrite's unnecessary index on oid, delete unused RULEOID syscache. Remove index on pg_listener and associated syscache for performance reasons (caching rows that are certain to change before you need 'em again is rather pointless). Change pg_attrdef's nonunique index on adrelid into a unique index on adrelid+adnum. Fix various incorrect settings of pg_class.relisshared, make that the primary reference point for whether a relation is shared or not. IsSharedSystemRelationName() is now only consulted to initialize relisshared during initial creation of tables and indexes. In theory we might now support shared user relations, though it's not clear how one would get entries for them into pg_class &etc of multiple databases. Fix recently reported bug that pg_attribute rows created for an index all have the same OID. (Proof that non-unique OID doesn't matter unless it's actually used to do lookups ;-)) There's no need to treat pg_trigger, pg_attrdef, pg_relcheck as bootstrap relations. Convert them into plain system catalogs without hardwired entries in pg_class and friends. Unify global.bki and template1.bki into a single init script postgres.bki, since the alleged distinction between them was misleading and pointless. Not to mention that it didn't work for setting up indexes on shared system relations. Rationalize locking of pg_shadow, pg_group, pg_attrdef (no need to use AccessExclusiveLock where ExclusiveLock or even RowExclusiveLock will do). Also, hold locks until transaction commit where necessary.
2001-06-12 07:55:50 +02:00
heap_close(pg_rel, AccessShareLock);
/*
* Somehow we'd have to check for tables, views, etc. owned by the
* role as well, but those could be spread out over all sorts of
* databases which we don't have access to (easily).
*/
/*
* Remove the role from the pg_authid table
*/
simple_heap_delete(pg_authid_rel, &tuple->t_self);
ReleaseSysCache(tuple);
/*
* Remove role from roles
*
* scan pg_auth_members and remove tuples which have
* roleid == member or roleid == role
*/
auth_mem_list = SearchSysCacheList(AUTHMEMROLEMEM, 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);
2001-03-22 05:01:46 +01:00
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_auth_members_rel, NoLock);
heap_close(pg_authid_rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
}
2003-06-27 16:45:32 +02:00
/*
* Rename role
2003-06-27 16:45:32 +02:00
*/
void
RenameRole(const char *oldname, const char *newname)
2003-06-27 16:45:32 +02:00
{
HeapTuple oldtuple,
newtuple;
TupleDesc dsc;
2003-06-27 16:45:32 +02:00
Relation rel;
Datum datum;
bool isnull;
Datum repl_val[Natts_pg_authid];
char repl_null[Natts_pg_authid];
char repl_repl[Natts_pg_authid];
int i;
Oid roleid;
2004-08-29 07:07:03 +02:00
2003-06-27 16:45:32 +02:00
/* ExclusiveLock because we need to update the password file */
rel = heap_open(AuthIdRelationId, ExclusiveLock);
dsc = RelationGetDescr(rel);
2003-06-27 16:45:32 +02:00
oldtuple = SearchSysCache(AUTHNAME,
2004-08-29 07:07:03 +02:00
CStringGetDatum(oldname),
0, 0, 0);
if (!HeapTupleIsValid(oldtuple))
2003-06-27 16:45:32 +02:00
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("role \"%s\" does not exist", oldname)));
2003-06-27 16:45:32 +02:00
/*
2003-08-04 02:43:34 +02:00
* 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.
2003-06-27 16:45:32 +02:00
*/
roleid = HeapTupleGetOid(oldtuple);
if (roleid == GetSessionUserId())
2003-06-27 16:45:32 +02:00
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("session role may not be renamed")));
2003-06-27 16:45:32 +02:00
/* make sure the new name doesn't exist */
if (SearchSysCacheExists(AUTHNAME,
2003-06-27 16:45:32 +02:00
CStringGetDatum(newname),
0, 0, 0))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("role \"%s\" already exists", newname)));
2003-06-27 16:45:32 +02:00
/* must be superuser */
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to rename roles")));
2003-06-27 16:45:32 +02:00
for (i = 0; i < Natts_pg_authid; i++)
repl_repl[i] = ' ';
repl_repl[Anum_pg_authid_rolname - 1] = 'r';
repl_val[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein,
2004-08-29 07:07:03 +02:00
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = ' ';
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_authid_rolpassword - 1] = 'r';
repl_null[Anum_pg_authid_rolpassword - 1] = 'n';
2004-08-29 07:07:03 +02:00
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
2004-08-29 07:07:03 +02:00
newtuple = heap_modifytuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
simple_heap_update(rel, &oldtuple->t_self, newtuple);
2004-08-29 07:07:03 +02:00
CatalogUpdateIndexes(rel, newtuple);
2003-06-27 16:45:32 +02:00
ReleaseSysCache(oldtuple);
2003-06-27 16:45:32 +02:00
heap_close(rel, NoLock);
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
2003-06-27 16:45:32 +02:00
}
/*
* GrantRoleStmt
*
* Grant/Revoke roles to/from roles
*/
void
GrantRole(GrantRoleStmt *stmt)
{
Oid grantor;
List *grantee_ids;
ListCell *item;
if (stmt->grantor)
grantor = get_roleid_checked(stmt->grantor);
else
grantor = GetUserId();
grantee_ids = roleNamesToIds(stmt->grantee_roles);
/*
* Step through all of the granted roles and add/remove
* entries for the grantees, or, if admin_opt is set, then
* just add/remove the admin option.
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
foreach(item, stmt->granted_roles)
{
char *rolename = strVal(lfirst(item));
Oid roleid = get_roleid_checked(rolename);
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
grantor, stmt->admin_opt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
stmt->admin_opt);
}
/*
* Set flag to update flat auth file at commit.
*/
auth_file_update_needed();
}
/*
* roleNamesToIds
*
* Given a list of role names (as String nodes), generate a list of role OIDs
* in the same order.
*/
static List *
roleNamesToIds(List *memberNames)
{
List *result = NIL;
ListCell *l;
foreach(l, memberNames)
{
char *rolename = strVal(lfirst(l));
Oid roleid = get_roleid_checked(rolename);
result = lappend_oid(result, roleid);
}
return result;
}
/*
* AddRoleMems -- Add given members to the specified role
*
* rolename: name of role to add to (used only for error messages)
* roleid: OID of role to add to
* memberNames: list of names of roles to add (used only for error messages)
* memberIds: OIDs of roles to add
* grantorId: who is granting the membership
* admin_opt: granting admin option?
*/
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
Oid grantorId, bool admin_opt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
ListCell *nameitem;
ListCell *iditem;
Assert(list_length(memberNames) == list_length(memberIds));
/* Skip permission check if nothing to do */
if (!memberIds)
return;
/*
* Check permissions: must 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.
*/
if (!superuser())
{
HeapTuple authmem_chk_tuple;
Form_pg_auth_members authmem_chk;
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)));
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);
}
/*
* Secure exclusive lock to protect our update of the flat auth file.
*/
pg_authmem_rel = heap_open(AuthMemRelationId, ExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
forboth(nameitem, memberNames, iditem, memberIds)
{
const char *membername = strVal(lfirst(nameitem));
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
char new_record_nulls[Natts_pg_auth_members];
char new_record_repl[Natts_pg_auth_members];
/*
* Check if entry for this role/member already exists;
* if so, give warning unless we are adding admin option.
*/
authmem_tuple = SearchSysCache(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid),
0, 0);
if (HeapTupleIsValid(authmem_tuple) && !admin_opt)
{
ereport(NOTICE,
(errmsg("role \"%s\" is already a member of role \"%s\"",
membername, rolename)));
ReleaseSysCache(authmem_tuple);
continue;
}
/* 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));
new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
if (HeapTupleIsValid(authmem_tuple))
{
new_record_repl[Anum_pg_auth_members_grantor - 1] = '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
{
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_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
* 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
DelRoleMems(const char *rolename, Oid roleid,
List *memberNames, List *memberIds,
bool admin_opt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
ListCell *nameitem;
ListCell *iditem;
Assert(list_length(memberNames) == list_length(memberIds));
/* Skip permission check if nothing to do */
if (!memberIds)
return;
/*
* Check permissions: must 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.
*/
if (!superuser())
{
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)));
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);
}
/*
* Secure exclusive lock to protect our update of the flat auth file.
*/
pg_authmem_rel = heap_open(AuthMemRelationId, ExclusiveLock);
pg_authmem_dsc = RelationGetDescr(pg_authmem_rel);
forboth(nameitem, memberNames, iditem, memberIds)
{
const char *membername = strVal(lfirst(nameitem));
Oid memberid = lfirst_oid(iditem);
HeapTuple authmem_tuple;
/*
* 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;
}
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);
}
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_authmem_rel, NoLock);
2003-06-27 16:45:32 +02:00
}