Skip to content

Commit e7a8260

Browse files
author
Commitfest Bot
committed
[CF 5608] v2 - Orphaned users in PG16 and above can only be managed by Superusers
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5608 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CAE9k0PmLw57PTVJGhk9bG+tWKhfRQ3fd6erF+wm+PH-UULei6Q@mail.gmail.com Author(s): Ashutosh Sharma
2 parents 5c8eda1 + 509f031 commit e7a8260

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed

src/backend/commands/user.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,131 @@ have_createrole_privilege(void)
125125
return has_createrole_privilege(GetUserId());
126126
}
127127

128+
/*
129+
* check_drop_role_dependency
130+
*
131+
* Check if there are any dependencies associated with the role being dropped.
132+
*
133+
* This function scans the pg_auth_members table to find: 1) roles that are
134+
* admins of the role being dropped, and 2) roles where the role being dropped
135+
* is an admin. If both conditions are met, it means the roles that are admins
136+
* of the role being dropped rely on it for admin privileges on roles where the
137+
* role being dropped is an admin.
138+
*
139+
* For example, if userA creates userB, and userB creates userC, both userB and
140+
* userA would have admin privileges on userC. UserB has admin privileges on
141+
* userC because it created userC, while userA inherits those admin privileges
142+
* from userB. This means userA relies on userB for admin privileges on userC.
143+
* Therefore, if userB is dropped, userA will lose its admin privileges on
144+
* userC.
145+
*
146+
* If such a dependency exists, this function raises an error to prevent the
147+
* role from being dropped.
148+
*
149+
* pg_auth_members_rel: relation object representing the pg_auth_members system
150+
* catalog.
151+
* roleid: Oid of the role that is being dropped.
152+
*/
153+
static void
154+
check_drop_role_dependency(Relation pg_auth_members_rel, Oid roleid)
155+
{
156+
ScanKeyData scankey;
157+
SysScanDesc sscan;
158+
HeapTuple tmp_tuple;
159+
List *admin_members_of_drop_role = NULL; /* List of roles that are admin members of the role to drop */
160+
List *roles_with_drop_role_as_admin_member = NULL; /* List of roles where the role to drop is an admin member */
161+
162+
ScanKeyInit(&scankey,
163+
Anum_pg_auth_members_roleid,
164+
BTEqualStrategyNumber, F_OIDEQ,
165+
ObjectIdGetDatum(roleid));
166+
167+
sscan = systable_beginscan(pg_auth_members_rel,
168+
AuthMemRoleMemIndexId,
169+
true, NULL, 1, &scankey);
170+
171+
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
172+
{
173+
Form_pg_auth_members authmem_form;
174+
authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
175+
176+
if (authmem_form->admin_option)
177+
{
178+
if (!admin_members_of_drop_role)
179+
admin_members_of_drop_role =
180+
list_make1_oid(authmem_form->member);
181+
else
182+
admin_members_of_drop_role =
183+
lappend_oid(admin_members_of_drop_role,
184+
authmem_form->member);
185+
}
186+
}
187+
188+
systable_endscan(sscan);
189+
190+
ScanKeyInit(&scankey,
191+
Anum_pg_auth_members_member,
192+
BTEqualStrategyNumber, F_OIDEQ,
193+
ObjectIdGetDatum(roleid));
194+
195+
sscan = systable_beginscan(pg_auth_members_rel,
196+
AuthMemRoleMemIndexId,
197+
true, NULL, 1, &scankey);
198+
199+
while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
200+
{
201+
Form_pg_auth_members authmem_form;
202+
authmem_form = (Form_pg_auth_members) GETSTRUCT(tmp_tuple);
203+
204+
if (authmem_form->admin_option)
205+
{
206+
if (!roles_with_drop_role_as_admin_member)
207+
roles_with_drop_role_as_admin_member =
208+
list_make1_oid(authmem_form->roleid);
209+
else
210+
roles_with_drop_role_as_admin_member =
211+
lappend_oid(roles_with_drop_role_as_admin_member,
212+
authmem_form->roleid);
213+
}
214+
}
215+
216+
systable_endscan(sscan);
217+
218+
/* If there are dependencies, raise an error */
219+
if (admin_members_of_drop_role && roles_with_drop_role_as_admin_member)
220+
{
221+
ListCell *dependent_role;
222+
ListCell *referenced_role;
223+
StringInfoData detail_log;
224+
225+
/* Initialize StringInfo to build the detailed error log message */
226+
initStringInfo(&detail_log);
227+
228+
foreach(dependent_role, admin_members_of_drop_role)
229+
{
230+
Oid dependent_role_oid = lfirst_oid(dependent_role);
231+
232+
foreach(referenced_role, roles_with_drop_role_as_admin_member)
233+
{
234+
Oid referenced_role_oid = lfirst_oid(referenced_role);
235+
236+
if (detail_log.len > 0)
237+
appendStringInfoChar(&detail_log, '\n');
238+
239+
appendStringInfo(&detail_log, "role %s inherits ADMIN privileges on role %s through role %s",
240+
GetUserNameFromId(dependent_role_oid, false),
241+
GetUserNameFromId(referenced_role_oid, false),
242+
GetUserNameFromId(roleid, false));
243+
}
244+
}
245+
246+
ereport(ERROR,
247+
(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
248+
errmsg("role \"%s\" cannot be dropped because some objects depend on it",
249+
GetUserNameFromId(roleid, false)),
250+
errdetail("%s", detail_log.data)));
251+
}
252+
}
128253

129254
/*
130255
* CREATE ROLE
@@ -1190,6 +1315,12 @@ DropRole(DropRoleStmt *stmt)
11901315
*/
11911316
LockSharedObject(AuthIdRelationId, roleid, 0, AccessExclusiveLock);
11921317

1318+
/*
1319+
* Check if there are any existing dependencies on the role before dropping
1320+
* it.
1321+
*/
1322+
check_drop_role_dependency(pg_auth_members_rel, roleid);
1323+
11931324
/*
11941325
* If there is a pg_auth_members entry that has one of the roles to be
11951326
* dropped as the roleid or member, it should be silently removed, but

src/test/regress/expected/create_role.out

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,37 @@ REVOKE CREATE ON DATABASE regression FROM regress_createrole CASCADE;
225225
DROP ROLE regress_replication_bypassrls;
226226
DROP ROLE regress_replication;
227227
DROP ROLE regress_bypassrls;
228+
-- should fail, as some roles have dependency on these roles
229+
DROP ROLE regress_createdb;
230+
ERROR: role "regress_createdb" cannot be dropped because some objects depend on it
231+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_createdb
232+
DROP ROLE regress_createrole;
233+
ERROR: role "regress_createrole" cannot be dropped because some objects depend on it
234+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_createrole
235+
role regress_role_admin inherits ADMIN privileges on role regress_rolecreator through role regress_createrole
236+
role regress_role_admin inherits ADMIN privileges on role regress_tenant through role regress_createrole
237+
role regress_role_admin inherits ADMIN privileges on role regress_tenant2 through role regress_createrole
238+
DROP ROLE regress_login;
239+
ERROR: role "regress_login" cannot be dropped because some objects depend on it
240+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_login
241+
DROP ROLE regress_inherit;
242+
ERROR: role "regress_inherit" cannot be dropped because some objects depend on it
243+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_inherit
244+
DROP ROLE regress_connection_limit;
245+
ERROR: role "regress_connection_limit" cannot be dropped because some objects depend on it
246+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_connection_limit
247+
DROP ROLE regress_encrypted_password;
248+
ERROR: role "regress_encrypted_password" cannot be dropped because some objects depend on it
249+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_encrypted_password
250+
DROP ROLE regress_password_null;
251+
ERROR: role "regress_password_null" cannot be dropped because some objects depend on it
252+
DETAIL: role regress_role_admin inherits ADMIN privileges on role regress_adminroles through role regress_password_null
253+
-- remove any dependencies associated with the role before dropping it
254+
-- should pass
255+
RESET SESSION AUTHORIZATION;
256+
REVOKE regress_createdb, regress_createrole, regress_login,
257+
regress_inherit, regress_connection_limit, regress_encrypted_password, regress_password_null
258+
FROM regress_role_admin;
228259
DROP ROLE regress_createdb;
229260
DROP ROLE regress_createrole;
230261
DROP ROLE regress_login;
@@ -235,6 +266,7 @@ DROP ROLE regress_password_null;
235266
DROP ROLE regress_noiseword;
236267
DROP ROLE regress_inroles;
237268
DROP ROLE regress_adminroles;
269+
SET SESSION AUTHORIZATION regress_role_admin;
238270
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
239271
DROP ROLE regress_role_super;
240272
ERROR: permission denied to drop role

src/test/regress/sql/create_role.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ REVOKE CREATE ON DATABASE regression FROM regress_createrole CASCADE;
183183
DROP ROLE regress_replication_bypassrls;
184184
DROP ROLE regress_replication;
185185
DROP ROLE regress_bypassrls;
186+
-- should fail, as some roles have dependency on these roles
187+
DROP ROLE regress_createdb;
188+
DROP ROLE regress_createrole;
189+
DROP ROLE regress_login;
190+
DROP ROLE regress_inherit;
191+
DROP ROLE regress_connection_limit;
192+
DROP ROLE regress_encrypted_password;
193+
DROP ROLE regress_password_null;
194+
-- remove any dependencies associated with the role before dropping it
195+
-- should pass
196+
RESET SESSION AUTHORIZATION;
197+
REVOKE regress_createdb, regress_createrole, regress_login,
198+
regress_inherit, regress_connection_limit, regress_encrypted_password, regress_password_null
199+
FROM regress_role_admin;
186200
DROP ROLE regress_createdb;
187201
DROP ROLE regress_createrole;
188202
DROP ROLE regress_login;
@@ -193,6 +207,7 @@ DROP ROLE regress_password_null;
193207
DROP ROLE regress_noiseword;
194208
DROP ROLE regress_inroles;
195209
DROP ROLE regress_adminroles;
210+
SET SESSION AUTHORIZATION regress_role_admin;
196211

197212
-- fail, cannot drop ourself, nor superusers or roles we lack ADMIN for
198213
DROP ROLE regress_role_super;

0 commit comments

Comments
 (0)