diff options
Diffstat (limited to 'src/backend/commands/user.c')
-rw-r--r-- | src/backend/commands/user.c | 192 |
1 files changed, 149 insertions, 43 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 94135fdd6b6..fc42b1cfd77 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -911,6 +911,7 @@ DropRole(DropRoleStmt *stmt) Relation pg_authid_rel, pg_auth_members_rel; ListCell *item; + List *role_addresses = NIL; if (!have_createrole_privilege()) ereport(ERROR, @@ -919,7 +920,7 @@ DropRole(DropRoleStmt *stmt) /* * Scan the pg_authid relation to find the Oid of the role(s) to be - * deleted. + * deleted and perform preliminary permissions and sanity checks. */ pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock); pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock); @@ -932,10 +933,9 @@ DropRole(DropRoleStmt *stmt) tmp_tuple; Form_pg_authid roleform; ScanKeyData scankey; - char *detail; - char *detail_log; SysScanDesc sscan; Oid roleid; + ObjectAddress *role_address; if (rolspec->roletype != ROLESPEC_CSTRING) ereport(ERROR, @@ -991,34 +991,31 @@ DropRole(DropRoleStmt *stmt) /* DROP hook for the role being removed */ InvokeObjectDropHook(AuthIdRelationId, roleid, 0); + /* Don't leak the syscache tuple */ + ReleaseSysCache(tuple); + /* * Lock the role, so nobody can add dependencies to her while we drop * her. We keep the lock until the end of transaction. */ LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock); - /* Check for pg_shdepend entries depending on this role */ - if (checkSharedDependencies(AuthIdRelationId, roleid, - &detail, &detail_log)) - ereport(ERROR, - (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), - errmsg("role \"%s\" cannot be dropped because some objects depend on it", - role), - errdetail_internal("%s", detail), - errdetail_log("%s", detail_log))); - - /* - * Remove the role from the pg_authid table - */ - CatalogTupleDelete(pg_authid_rel, &tuple->t_self); - - ReleaseSysCache(tuple); - /* - * Remove role from the pg_auth_members table. We have to remove all - * tuples that show it as either a role or a member. + * If there is a pg_auth_members entry that has one of the roles to be + * dropped as the roleid or member, it should be silently removed, but + * if there is a pg_auth_members entry that has one of the roles to be + * dropped as the grantor, the operation should fail. + * + * It's possible, however, that a single pg_auth_members entry could + * fall into multiple categories - e.g. the user could do "GRANT foo + * TO bar GRANTED BY baz" and then "DROP ROLE baz, bar". We want such + * an operation to succeed regardless of the order in which the + * to-be-dropped roles are passed to DROP ROLE. * - * XXX what about grantor entries? Maybe we should do one heap scan. + * To make that work, we remove all pg_auth_members entries that can + * be silently removed in this loop, and then below we'll make a + * second pass over the list of roles to be removed and check for any + * remaining dependencies. */ ScanKeyInit(&scankey, Anum_pg_auth_members_roleid, @@ -1030,6 +1027,11 @@ DropRole(DropRoleStmt *stmt) while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) { + Form_pg_auth_members authmem_form; + + authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple); + deleteSharedDependencyRecordsFor(AuthMemRelationId, + authmem_form->oid, 0); CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self); } @@ -1045,23 +1047,17 @@ DropRole(DropRoleStmt *stmt) while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) { + Form_pg_auth_members authmem_form; + + authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple); + deleteSharedDependencyRecordsFor(AuthMemRelationId, + authmem_form->oid, 0); CatalogTupleDelete(pg_auth_members_rel, &tmp_tuple->t_self); } systable_endscan(sscan); /* - * Remove any comments or security labels on this role. - */ - DeleteSharedComments(roleid, AuthIdRelationId); - DeleteSharedSecurityLabel(roleid, AuthIdRelationId); - - /* - * Remove settings for this role. - */ - DropSetting(InvalidOid, roleid); - - /* * Advance command counter so that later iterations of this loop will * see the changes already made. This is essential if, for example, * we are trying to drop both a role and one of its direct members --- @@ -1071,6 +1067,72 @@ DropRole(DropRoleStmt *stmt) * itself.) */ CommandCounterIncrement(); + + /* Looks tentatively OK, add it to the list. */ + role_address = palloc(sizeof(ObjectAddress)); + role_address->classId = AuthIdRelationId; + role_address->objectId = roleid; + role_address->objectSubId = 0; + role_addresses = lappend(role_addresses, role_address); + } + + /* + * Second pass over the roles to be removed. + */ + foreach(item, role_addresses) + { + ObjectAddress *role_address = lfirst(item); + Oid roleid = role_address->objectId; + HeapTuple tuple; + Form_pg_authid roleform; + char *detail; + char *detail_log; + + /* + * Re-find the pg_authid tuple. + * + * Since we've taken a lock on the role OID, it shouldn't be possible + * for the tuple to have been deleted -- or for that matter updated -- + * unless the user is manually modifying the system catalogs. + */ + tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for role %u", roleid); + roleform = (Form_pg_authid) GETSTRUCT(tuple); + + /* + * Check for pg_shdepend entries depending on this role. + * + * This needs to happen after we've completed removing any + * pg_auth_members entries that can be removed silently, in order to + * avoid spurious failures. See notes above for more details. + */ + if (checkSharedDependencies(AuthIdRelationId, roleid, + &detail, &detail_log)) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("role \"%s\" cannot be dropped because some objects depend on it", + NameStr(roleform->rolname)), + errdetail_internal("%s", detail), + errdetail_log("%s", detail_log))); + + /* + * Remove the role from the pg_authid table + */ + CatalogTupleDelete(pg_authid_rel, &tuple->t_self); + + ReleaseSysCache(tuple); + + /* + * Remove any comments or security labels on this role. + */ + DeleteSharedComments(roleid, AuthIdRelationId); + DeleteSharedSecurityLabel(roleid, AuthIdRelationId); + + /* + * Remove settings for this role. + */ + DropSetting(InvalidOid, roleid); } /* @@ -1443,6 +1505,7 @@ AddRoleMems(const char *rolename, Oid roleid, Datum new_record[Natts_pg_auth_members] = {0}; bool new_record_nulls[Natts_pg_auth_members] = {0}; bool new_record_repl[Natts_pg_auth_members] = {0}; + Form_pg_auth_members authmem_form; /* * pg_database_owner is never a role member. Lifting this restriction @@ -1488,15 +1551,22 @@ AddRoleMems(const char *rolename, Oid roleid, authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM, ObjectIdGetDatum(roleid), ObjectIdGetDatum(memberid)); - if (HeapTupleIsValid(authmem_tuple) && - (!admin_opt || - ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option)) + if (!HeapTupleIsValid(authmem_tuple)) { - ereport(NOTICE, - (errmsg("role \"%s\" is already a member of role \"%s\"", - get_rolespec_name(memberRole), rolename))); - ReleaseSysCache(authmem_tuple); - continue; + authmem_form = NULL; + } + else + { + authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple); + + if (!admin_opt || authmem_form->admin_option) + { + ereport(NOTICE, + (errmsg("role \"%s\" is already a member of role \"%s\"", + get_rolespec_name(memberRole), rolename))); + ReleaseSysCache(authmem_tuple); + continue; + } } /* Build a tuple to insert or update */ @@ -1513,13 +1583,41 @@ AddRoleMems(const char *rolename, Oid roleid, new_record, new_record_nulls, new_record_repl); CatalogTupleUpdate(pg_authmem_rel, &tuple->t_self, tuple); + + if (authmem_form->grantor != grantorId) + { + Oid *oldmembers = palloc(sizeof(Oid)); + Oid *newmembers = palloc(sizeof(Oid)); + + /* updateAclDependencies wants to pfree array inputs */ + oldmembers[0] = authmem_form->grantor; + newmembers[0] = grantorId; + + updateAclDependencies(AuthMemRelationId, authmem_form->oid, + 0, InvalidOid, + 1, oldmembers, + 1, newmembers); + } + ReleaseSysCache(authmem_tuple); } else { + Oid objectId; + Oid *newmembers = palloc(sizeof(Oid)); + + objectId = GetNewObjectId(); + new_record[Anum_pg_auth_members_oid - 1] = objectId; tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls); CatalogTupleInsert(pg_authmem_rel, tuple); + + /* updateAclDependencies wants to pfree array inputs */ + newmembers[0] = grantorId; + updateAclDependencies(AuthMemRelationId, objectId, + 0, InvalidOid, + 0, NULL, + 1, newmembers); } /* CCI after each change, in case there are duplicates in list */ @@ -1586,6 +1684,7 @@ DelRoleMems(const char *rolename, Oid roleid, RoleSpec *memberRole = lfirst(specitem); Oid memberid = lfirst_oid(iditem); HeapTuple authmem_tuple; + Form_pg_auth_members authmem_form; /* * Find entry for this role/member @@ -1601,9 +1700,16 @@ DelRoleMems(const char *rolename, Oid roleid, continue; } + authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple); + if (!admin_opt) { - /* Remove the entry altogether */ + /* + * Remove the entry altogether, after first removing its + * dependencies + */ + deleteSharedDependencyRecordsFor(AuthMemRelationId, + authmem_form->oid, 0); CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self); } else |