Ensure that pg_auth_members.grantor is always valid.

Previously, "GRANT foo TO bar" or "GRANT foo TO bar GRANTED BY baz"
would record the OID of the grantor in pg_auth_members.grantor, but
that role could later be dropped without modifying or removing the
pg_auth_members record. That's not great, because we typically try
to avoid dangling references in catalog data.

Now, a role grant depends on the grantor, and the grantor can't be
dropped without removing the grant or changing the grantor.  "DROP
OWNED BY" will remove the grant, just as it does for other kinds of
privileges. "REASSIGN OWNED BY" will not, again just like what we do
in other cases involving privileges.

pg_auth_members now has an OID column, because that is needed in order
for dependencies to work. It also now has an index on the grantor
column, because otherwise dropping a role would require a sequential
scan of the entire table to see whether the role's OID is in use as
a grantor. That probably wouldn't be too large a problem in practice,
but it seems better to have an index just in case.

A follow-on patch is planned with the goal of more thoroughly
rationalizing the behavior of role grants. This patch is just trying
to do enough to make sure that the data we store in the catalogs is at
some basic level valid.

Patch by me, reviewed by Stephen Frost

Discussion: http://postgr.es/m/CA+TgmoaFr-RZeQ+WoQ5nKPv97oT9+aDgK_a5+qWHSgbDsMp1Vg@mail.gmail.com
This commit is contained in:
Robert Haas 2022-08-18 13:13:02 -04:00
parent 2f17b57017
commit 6566133c5f
16 changed files with 398 additions and 69 deletions

View File

@ -1669,6 +1669,15 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
</thead>
<tbody>
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>oid</structfield> <type>oid</type>
</para>
<para>
Row identifier
</para></entry>
</row>
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>roleid</structfield> <type>oid</type>

View File

@ -262,6 +262,8 @@ IsSharedRelation(Oid relationId)
relationId == AuthIdRolnameIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemOidIndexId ||
relationId == AuthMemGrantorIndexId ||
relationId == DatabaseNameIndexId ||
relationId == DatabaseOidIndexId ||
relationId == DbRoleSettingDatidRolidIndexId ||

View File

@ -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;

View File

@ -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"
@ -386,6 +387,20 @@ static const ObjectPropertyType ObjectProperty[] =
-1,
true
},
{
"role membership",
AuthMemRelationId,
AuthMemOidIndexId,
-1,
-1,
Anum_pg_auth_members_oid,
InvalidAttrNumber,
InvalidAttrNumber,
Anum_pg_auth_members_grantor,
InvalidAttrNumber,
-1,
true
},
{
"rule",
RewriteRelationId,
@ -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;

View File

@ -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_OWNER:
/* If a local object, save it for deletion below */
if (sdepForm->dbid == MyDatabaseId)
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)
{
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);
RemoveRoleFromObjectACL(roleid,
sdepForm->classid,
sdepForm->objid);
break;
}
/* FALLTHROUGH */
case SHARED_DEPENDENCY_OWNER:
/* 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))
{
ReleaseDeletionLock(&obj);
break;
}
add_exact_object_address(&obj, deleteobjs);
break;
}
}

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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:

View File

@ -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.
*
* XXX what about grantor entries? Maybe we should do one heap scan.
* It's possible, however, that a single pg_auth_members entry could
* fall into multiple categories - e.g. the user could do "GRANT foo
* TO bar GRANTED BY baz" and then "DROP ROLE baz, bar". We want such
* an operation to succeed regardless of the order in which the
* to-be-dropped roles are passed to DROP ROLE.
*
* To make that work, we remove all pg_auth_members entries that can
* be silently removed in this loop, and then below we'll make a
* second pass over the list of roles to be removed and check for any
* remaining dependencies.
*/
ScanKeyInit(&scankey,
Anum_pg_auth_members_roleid,
@ -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,22 +1047,16 @@ 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,
@ -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

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;