diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/catalog/catalog.c | 2 | ||||
-rw-r--r-- | src/backend/catalog/dependency.c | 14 | ||||
-rw-r--r-- | src/backend/catalog/objectaddress.c | 108 | ||||
-rw-r--r-- | src/backend/catalog/pg_shdepend.c | 47 | ||||
-rw-r--r-- | src/backend/catalog/system_views.sql | 2 | ||||
-rw-r--r-- | src/backend/commands/alter.c | 1 | ||||
-rw-r--r-- | src/backend/commands/event_trigger.c | 1 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 1 | ||||
-rw-r--r-- | src/backend/commands/user.c | 192 | ||||
-rw-r--r-- | src/include/catalog/dependency.h | 1 | ||||
-rw-r--r-- | src/include/catalog/pg_auth_members.h | 5 | ||||
-rw-r--r-- | src/test/regress/expected/create_role.out | 16 | ||||
-rw-r--r-- | src/test/regress/expected/privileges.out | 32 | ||||
-rw-r--r-- | src/test/regress/sql/create_role.sql | 7 | ||||
-rw-r--r-- | src/test/regress/sql/privileges.sql | 27 |
15 files changed, 388 insertions, 68 deletions
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 6f43870779f..2abd6b007a2 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -262,6 +262,8 @@ IsSharedRelation(Oid relationId) relationId == AuthIdRolnameIndexId || relationId == AuthMemMemRoleIndexId || relationId == AuthMemRoleMemIndexId || + relationId == AuthMemOidIndexId || + relationId == AuthMemGrantorIndexId || relationId == DatabaseNameIndexId || relationId == DatabaseOidIndexId || relationId == DbRoleSettingDatidRolidIndexId || diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index e119674b1f2..39768fa22be 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -28,6 +28,7 @@ #include "catalog/pg_amproc.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_authid.h" +#include "catalog/pg_auth_members.h" #include "catalog/pg_cast.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -172,6 +173,7 @@ static const Oid object_classes[] = { TSTemplateRelationId, /* OCLASS_TSTEMPLATE */ TSConfigRelationId, /* OCLASS_TSCONFIG */ AuthIdRelationId, /* OCLASS_ROLE */ + AuthMemRelationId, /* OCLASS_ROLE_MEMBERSHIP */ DatabaseRelationId, /* OCLASS_DATABASE */ TableSpaceRelationId, /* OCLASS_TBLSPACE */ ForeignDataWrapperRelationId, /* OCLASS_FDW */ @@ -1502,6 +1504,7 @@ doDeletion(const ObjectAddress *object, int flags) case OCLASS_DEFACL: case OCLASS_EVENT_TRIGGER: case OCLASS_TRANSFORM: + case OCLASS_ROLE_MEMBERSHIP: DropObjectById(object); break; @@ -1529,9 +1532,8 @@ doDeletion(const ObjectAddress *object, int flags) * Accepts the same flags as performDeletion (though currently only * PERFORM_DELETION_CONCURRENTLY does anything). * - * We use LockRelation for relations, LockDatabaseObject for everything - * else. Shared-across-databases objects are not currently supported - * because no caller cares, but could be modified to use LockSharedObject. + * We use LockRelation for relations, and otherwise LockSharedObject or + * LockDatabaseObject as appropriate for the object type. */ void AcquireDeletionLock(const ObjectAddress *object, int flags) @@ -1549,6 +1551,9 @@ AcquireDeletionLock(const ObjectAddress *object, int flags) else LockRelationOid(object->objectId, AccessExclusiveLock); } + else if (object->classId == AuthMemRelationId) + LockSharedObject(object->classId, object->objectId, 0, + AccessExclusiveLock); else { /* assume we should lock the whole object not a sub-object */ @@ -2914,6 +2919,9 @@ getObjectClass(const ObjectAddress *object) case AuthIdRelationId: return OCLASS_ROLE; + case AuthMemRelationId: + return OCLASS_ROLE_MEMBERSHIP; + case DatabaseRelationId: return OCLASS_DATABASE; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index dab375f2b26..798c1a2d1e0 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -27,6 +27,7 @@ #include "catalog/pg_amproc.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_authid.h" +#include "catalog/pg_auth_members.h" #include "catalog/pg_cast.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -387,6 +388,20 @@ static const ObjectPropertyType ObjectProperty[] = true }, { + "role membership", + AuthMemRelationId, + AuthMemOidIndexId, + -1, + -1, + Anum_pg_auth_members_oid, + InvalidAttrNumber, + InvalidAttrNumber, + Anum_pg_auth_members_grantor, + InvalidAttrNumber, + -1, + true + }, + { "rule", RewriteRelationId, RewriteOidIndexId, @@ -787,6 +802,10 @@ static const struct object_type_map { "role", OBJECT_ROLE }, + /* OCLASS_ROLE_MEMBERSHIP */ + { + "role membership", -1 /* unmapped */ + }, /* OCLASS_DATABASE */ { "database", OBJECT_DATABASE @@ -3644,6 +3663,48 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case OCLASS_ROLE_MEMBERSHIP: + { + Relation amDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_auth_members amForm; + + amDesc = table_open(AuthMemRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_auth_members_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(amDesc, AuthMemOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for role membership %u", + object->objectId); + + systable_endscan(rcscan); + table_close(amDesc, AccessShareLock); + break; + } + + amForm = (Form_pg_auth_members) GETSTRUCT(tup); + + appendStringInfo(&buffer, _("membership of role %s in role %s"), + GetUserNameFromId(amForm->member, false), + GetUserNameFromId(amForm->roleid, false)); + + systable_endscan(rcscan); + table_close(amDesc, AccessShareLock); + break; + } + case OCLASS_DATABASE: { char *datname; @@ -4533,6 +4594,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "role"); break; + case OCLASS_ROLE_MEMBERSHIP: + appendStringInfoString(&buffer, "role membership"); + break; + case OCLASS_DATABASE: appendStringInfoString(&buffer, "database"); break; @@ -5476,6 +5541,49 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case OCLASS_ROLE_MEMBERSHIP: + { + Relation authMemDesc; + ScanKeyData skey[1]; + SysScanDesc amscan; + HeapTuple tup; + Form_pg_auth_members amForm; + + authMemDesc = table_open(AuthMemRelationId, + AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_auth_members_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + amscan = systable_beginscan(authMemDesc, AuthMemOidIndexId, true, + NULL, 1, skey); + + tup = systable_getnext(amscan); + + if (!HeapTupleIsValid(tup)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for pg_auth_members entry %u", + object->objectId); + + systable_endscan(amscan); + table_close(authMemDesc, AccessShareLock); + break; + } + + amForm = (Form_pg_auth_members) GETSTRUCT(tup); + + appendStringInfo(&buffer, _("membership of role %s in role %s"), + GetUserNameFromId(amForm->member, false), + GetUserNameFromId(amForm->roleid, false)); + + systable_endscan(amscan); + table_close(authMemDesc, AccessShareLock); + break; + } + case OCLASS_DATABASE: { char *datname; diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 3e8fa008b9d..f2f227f887b 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -22,6 +22,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_authid.h" +#include "catalog/pg_auth_members.h" #include "catalog/pg_collation.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" @@ -1364,11 +1365,6 @@ shdepDropOwned(List *roleids, DropBehavior behavior) case SHARED_DEPENDENCY_INVALID: elog(ERROR, "unexpected dependency type"); break; - case SHARED_DEPENDENCY_ACL: - RemoveRoleFromObjectACL(roleid, - sdepForm->classid, - sdepForm->objid); - break; case SHARED_DEPENDENCY_POLICY: /* @@ -1398,22 +1394,37 @@ shdepDropOwned(List *roleids, DropBehavior behavior) add_exact_object_address(&obj, deleteobjs); } break; + case SHARED_DEPENDENCY_ACL: + + /* + * Dependencies on role grants are recorded using + * SHARED_DEPENDENCY_ACL, but unlike a regular ACL list + * which stores all permissions for a particular object in + * a single ACL array, there's a separate catalog row for + * each grant - so removing the grant just means removing + * the entire row. + */ + if (sdepForm->classid != AuthMemRelationId) + { + RemoveRoleFromObjectACL(roleid, + sdepForm->classid, + sdepForm->objid); + break; + } + /* FALLTHROUGH */ case SHARED_DEPENDENCY_OWNER: - /* If a local object, save it for deletion below */ - if (sdepForm->dbid == MyDatabaseId) + /* Save it for deletion below */ + obj.classId = sdepForm->classid; + obj.objectId = sdepForm->objid; + obj.objectSubId = sdepForm->objsubid; + /* as above */ + AcquireDeletionLock(&obj, 0); + if (!systable_recheck_tuple(scan, tuple)) { - obj.classId = sdepForm->classid; - obj.objectId = sdepForm->objid; - obj.objectSubId = sdepForm->objsubid; - /* as above */ - AcquireDeletionLock(&obj, 0); - if (!systable_recheck_tuple(scan, tuple)) - { - ReleaseDeletionLock(&obj); - break; - } - add_exact_object_address(&obj, deleteobjs); + ReleaseDeletionLock(&obj); + break; } + add_exact_object_address(&obj, deleteobjs); break; } } diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index f369b1fc141..5a844b63a1c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -53,7 +53,7 @@ CREATE VIEW pg_group AS SELECT rolname AS groname, oid AS grosysid, - ARRAY(SELECT member FROM pg_auth_members WHERE roleid = oid) AS grolist + ARRAY(SELECT member FROM pg_auth_members WHERE roleid = pg_authid.oid) AS grolist FROM pg_authid WHERE NOT rolcanlogin; diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 5456b8222ba..55219bb0974 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -650,6 +650,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_TRIGGER: case OCLASS_SCHEMA: case OCLASS_ROLE: + case OCLASS_ROLE_MEMBERSHIP: case OCLASS_DATABASE: case OCLASS_TBLSPACE: case OCLASS_FDW: diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 9f8fee7c168..635d05405e1 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1015,6 +1015,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_DATABASE: case OCLASS_TBLSPACE: case OCLASS_ROLE: + case OCLASS_ROLE_MEMBERSHIP: case OCLASS_PARAMETER_ACL: /* no support for global objects */ return false; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 8d7c68b8b3c..9be04c8a1e7 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12667,6 +12667,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, case OCLASS_TSTEMPLATE: case OCLASS_TSCONFIG: case OCLASS_ROLE: + case OCLASS_ROLE_MEMBERSHIP: case OCLASS_DATABASE: case OCLASS_TBLSPACE: case OCLASS_FDW: 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 diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 6684933dacf..729c4c46c0d 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -112,6 +112,7 @@ typedef enum ObjectClass OCLASS_TSTEMPLATE, /* pg_ts_template */ OCLASS_TSCONFIG, /* pg_ts_config */ OCLASS_ROLE, /* pg_authid */ + OCLASS_ROLE_MEMBERSHIP, /* pg_auth_members */ OCLASS_DATABASE, /* pg_database */ OCLASS_TBLSPACE, /* pg_tablespace */ OCLASS_FDW, /* pg_foreign_data_wrapper */ diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h index 1bc027f133d..c9d7697730c 100644 --- a/src/include/catalog/pg_auth_members.h +++ b/src/include/catalog/pg_auth_members.h @@ -29,6 +29,7 @@ */ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2843,AuthMemRelation_Rowtype_Id) BKI_SCHEMA_MACRO { + Oid oid; /* oid */ Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */ Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */ Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */ @@ -42,7 +43,9 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_ */ typedef FormData_pg_auth_members *Form_pg_auth_members; -DECLARE_UNIQUE_INDEX_PKEY(pg_auth_members_role_member_index, 2694, AuthMemRoleMemIndexId, on pg_auth_members using btree(roleid oid_ops, member oid_ops)); +DECLARE_UNIQUE_INDEX_PKEY(pg_auth_members_oid_index, 9385, AuthMemOidIndexId, on pg_auth_members using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_auth_members_role_member_index, 2694, AuthMemRoleMemIndexId, on pg_auth_members using btree(roleid oid_ops, member oid_ops)); DECLARE_UNIQUE_INDEX(pg_auth_members_member_role_index, 2695, AuthMemMemRoleIndexId, on pg_auth_members using btree(member oid_ops, roleid oid_ops)); +DECLARE_INDEX(pg_auth_members_grantor_index, 9384, AuthMemGrantorIndexId, on pg_auth_members using btree(grantor oid_ops)); #endif /* PG_AUTH_MEMBERS_H */ diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out index 4e67d727603..c2465d0f492 100644 --- a/src/test/regress/expected/create_role.out +++ b/src/test/regress/expected/create_role.out @@ -103,9 +103,21 @@ ERROR: role "regress_nosuch_recursive" does not exist DROP ROLE regress_nosuch_admin_recursive; ERROR: role "regress_nosuch_admin_recursive" does not exist DROP ROLE regress_plainrole; +-- fail, can't drop regress_createrole yet, due to outstanding grants +DROP ROLE regress_createrole; +ERROR: role "regress_createrole" cannot be dropped because some objects depend on it +DETAIL: privileges for membership of role regress_read_all_data in role pg_read_all_data +privileges for membership of role regress_write_all_data in role pg_write_all_data +privileges for membership of role regress_monitor in role pg_monitor +privileges for membership of role regress_read_all_settings in role pg_read_all_settings +privileges for membership of role regress_read_all_stats in role pg_read_all_stats +privileges for membership of role regress_stat_scan_tables in role pg_stat_scan_tables +privileges for membership of role regress_read_server_files in role pg_read_server_files +privileges for membership of role regress_write_server_files in role pg_write_server_files +privileges for membership of role regress_execute_server_program in role pg_execute_server_program +privileges for membership of role regress_signal_backend in role pg_signal_backend -- ok, should be able to drop non-superuser roles we created DROP ROLE regress_createdb; -DROP ROLE regress_createrole; DROP ROLE regress_login; DROP ROLE regress_inherit; DROP ROLE regress_connection_limit; @@ -125,6 +137,8 @@ DROP ROLE regress_read_server_files; DROP ROLE regress_write_server_files; DROP ROLE regress_execute_server_program; DROP ROLE regress_signal_backend; +-- ok, dropped the other roles first so this is ok now +DROP ROLE regress_createrole; -- fail, role still owns database objects DROP ROLE regress_tenant; ERROR: role "regress_tenant" cannot be dropped because some objects depend on it diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index e10dd6f9ae5..65b4a22ebc5 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -33,6 +33,38 @@ CREATE USER regress_priv_user8; CREATE USER regress_priv_user9; CREATE USER regress_priv_user10; CREATE ROLE regress_priv_role; +-- test GRANTED BY with DROP OWNED and REASSIGN OWNED +GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user3 GRANTED BY regress_priv_user2; +DROP ROLE regress_priv_user2; -- fail, dependency +ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it +DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1 +REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; +DROP ROLE regress_priv_user2; -- still fail, REASSIGN OWNED doesn't help +ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it +DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1 +DROP OWNED BY regress_priv_user2; +DROP ROLE regress_priv_user2; -- ok now, DROP OWNED does the job +-- test that removing granted role or grantee role removes dependency +GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user4 GRANTED BY regress_priv_user3; +DROP ROLE regress_priv_user3; -- should fail, dependency +ERROR: role "regress_priv_user3" cannot be dropped because some objects depend on it +DETAIL: privileges for membership of role regress_priv_user4 in role regress_priv_user1 +DROP ROLE regress_priv_user4; -- ok +DROP ROLE regress_priv_user3; -- ok now +GRANT regress_priv_user1 TO regress_priv_user5 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user6 GRANTED BY regress_priv_user5; +DROP ROLE regress_priv_user5; -- should fail, dependency +ERROR: role "regress_priv_user5" cannot be dropped because some objects depend on it +DETAIL: privileges for membership of role regress_priv_user6 in role regress_priv_user1 +DROP ROLE regress_priv_user1, regress_priv_user5; -- ok, despite order +-- recreate the roles we just dropped +CREATE USER regress_priv_user1; +CREATE USER regress_priv_user2; +CREATE USER regress_priv_user3; +CREATE USER regress_priv_user4; +CREATE USER regress_priv_user5; GRANT pg_read_all_data TO regress_priv_user6; GRANT pg_write_all_data TO regress_priv_user7; GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; diff --git a/src/test/regress/sql/create_role.sql b/src/test/regress/sql/create_role.sql index 292dc087975..b696628238a 100644 --- a/src/test/regress/sql/create_role.sql +++ b/src/test/regress/sql/create_role.sql @@ -98,9 +98,11 @@ DROP ROLE regress_nosuch_recursive; DROP ROLE regress_nosuch_admin_recursive; DROP ROLE regress_plainrole; +-- fail, can't drop regress_createrole yet, due to outstanding grants +DROP ROLE regress_createrole; + -- ok, should be able to drop non-superuser roles we created DROP ROLE regress_createdb; -DROP ROLE regress_createrole; DROP ROLE regress_login; DROP ROLE regress_inherit; DROP ROLE regress_connection_limit; @@ -121,6 +123,9 @@ DROP ROLE regress_write_server_files; DROP ROLE regress_execute_server_program; DROP ROLE regress_signal_backend; +-- ok, dropped the other roles first so this is ok now +DROP ROLE regress_createrole; + -- fail, role still owns database objects DROP ROLE regress_tenant; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 6d1fd3391a2..66834e32a7e 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -37,6 +37,33 @@ CREATE USER regress_priv_user9; CREATE USER regress_priv_user10; CREATE ROLE regress_priv_role; +-- test GRANTED BY with DROP OWNED and REASSIGN OWNED +GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user3 GRANTED BY regress_priv_user2; +DROP ROLE regress_priv_user2; -- fail, dependency +REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; +DROP ROLE regress_priv_user2; -- still fail, REASSIGN OWNED doesn't help +DROP OWNED BY regress_priv_user2; +DROP ROLE regress_priv_user2; -- ok now, DROP OWNED does the job + +-- test that removing granted role or grantee role removes dependency +GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user4 GRANTED BY regress_priv_user3; +DROP ROLE regress_priv_user3; -- should fail, dependency +DROP ROLE regress_priv_user4; -- ok +DROP ROLE regress_priv_user3; -- ok now +GRANT regress_priv_user1 TO regress_priv_user5 WITH ADMIN OPTION; +GRANT regress_priv_user1 TO regress_priv_user6 GRANTED BY regress_priv_user5; +DROP ROLE regress_priv_user5; -- should fail, dependency +DROP ROLE regress_priv_user1, regress_priv_user5; -- ok, despite order + +-- recreate the roles we just dropped +CREATE USER regress_priv_user1; +CREATE USER regress_priv_user2; +CREATE USER regress_priv_user3; +CREATE USER regress_priv_user4; +CREATE USER regress_priv_user5; + GRANT pg_read_all_data TO regress_priv_user6; GRANT pg_write_all_data TO regress_priv_user7; GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; |