Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit f687bf6

Browse files
committed
Merge similar algorithms into roles_is_member_of().
The next commit would have complicated two or three algorithms, so take this opportunity to consolidate. No functional changes. Reviewed by John Naylor. Discussion: https://postgr.es/m/20201228043148.GA1053024@rfd.leadboat.com
1 parent 73b96ba commit f687bf6

File tree

1 file changed

+59
-165
lines changed
  • src/backend/utils/adt

1 file changed

+59
-165
lines changed

src/backend/utils/adt/acl.c

+59-165
Original file line numberDiff line numberDiff line change
@@ -50,32 +50,24 @@ typedef struct
5050
/*
5151
* We frequently need to test whether a given role is a member of some other
5252
* role. In most of these tests the "given role" is the same, namely the
53-
* active current user. So we can optimize it by keeping a cached list of
54-
* all the roles the "given role" is a member of, directly or indirectly.
55-
*
56-
* There are actually two caches, one computed under "has_privs" rules
57-
* (do not recurse where rolinherit isn't true) and one computed under
58-
* "is_member" rules (recurse regardless of rolinherit).
53+
* active current user. So we can optimize it by keeping cached lists of all
54+
* the roles the "given role" is a member of, directly or indirectly.
5955
*
6056
* Possibly this mechanism should be generalized to allow caching membership
6157
* info for multiple roles?
6258
*
63-
* The has_privs cache is:
64-
* cached_privs_role is the role OID the cache is for.
65-
* cached_privs_roles is an OID list of roles that cached_privs_role
66-
* has the privileges of (always including itself).
67-
* The cache is valid if cached_privs_role is not InvalidOid.
68-
*
69-
* The is_member cache is similarly:
70-
* cached_member_role is the role OID the cache is for.
71-
* cached_membership_roles is an OID list of roles that cached_member_role
72-
* is a member of (always including itself).
73-
* The cache is valid if cached_member_role is not InvalidOid.
59+
* Each element of cached_roles is an OID list of constituent roles for the
60+
* corresponding element of cached_role (always including the cached_role
61+
* itself). One cache has ROLERECURSE_PRIVS semantics, and the other has
62+
* ROLERECURSE_MEMBERS semantics.
7463
*/
75-
static Oid cached_privs_role = InvalidOid;
76-
static List *cached_privs_roles = NIL;
77-
static Oid cached_member_role = InvalidOid;
78-
static List *cached_membership_roles = NIL;
64+
enum RoleRecurseType
65+
{
66+
ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
67+
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
68+
};
69+
static Oid cached_role[] = {InvalidOid, InvalidOid};
70+
static List *cached_roles[] = {NIL, NIL};
7971

8072

8173
static const char *getid(const char *s, char *n);
@@ -4675,8 +4667,8 @@ initialize_acl(void)
46754667
{
46764668
/*
46774669
* In normal mode, set a callback on any syscache invalidation of rows
4678-
* of pg_auth_members (for each AUTHMEM search in this file) or
4679-
* pg_authid (for has_rolinherit())
4670+
* of pg_auth_members (for roles_is_member_of()) or pg_authid (for
4671+
* has_rolinherit())
46804672
*/
46814673
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
46824674
RoleMembershipCacheCallback,
@@ -4695,8 +4687,8 @@ static void
46954687
RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
46964688
{
46974689
/* Force membership caches to be recomputed on next use */
4698-
cached_privs_role = InvalidOid;
4699-
cached_member_role = InvalidOid;
4690+
cached_role[ROLERECURSE_PRIVS] = InvalidOid;
4691+
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
47004692
}
47014693

47024694

@@ -4718,30 +4710,35 @@ has_rolinherit(Oid roleid)
47184710

47194711

47204712
/*
4721-
* Get a list of roles that the specified roleid has the privileges of
4713+
* Get a list of roles that the specified roleid is a member of
47224714
*
4723-
* This is defined not to recurse through roles that don't have rolinherit
4724-
* set; for such roles, membership implies the ability to do SET ROLE, but
4725-
* the privileges are not available until you've done so.
4715+
* Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
4716+
* set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
4717+
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
4718+
* in role "admin_of".
47264719
*
47274720
* Since indirect membership testing is relatively expensive, we cache
47284721
* a list of memberships. Hence, the result is only guaranteed good until
4729-
* the next call of roles_has_privs_of()!
4722+
* the next call of roles_is_member_of()!
47304723
*
47314724
* For the benefit of select_best_grantor, the result is defined to be
47324725
* in breadth-first order, ie, closer relationships earlier.
47334726
*/
47344727
static List *
4735-
roles_has_privs_of(Oid roleid)
4728+
roles_is_member_of(Oid roleid, enum RoleRecurseType type,
4729+
Oid admin_of, bool *is_admin)
47364730
{
47374731
List *roles_list;
47384732
ListCell *l;
4739-
List *new_cached_privs_roles;
4733+
List *new_cached_roles;
47404734
MemoryContext oldctx;
47414735

4742-
/* If cache is already valid, just return the list */
4743-
if (OidIsValid(cached_privs_role) && cached_privs_role == roleid)
4744-
return cached_privs_roles;
4736+
Assert(OidIsValid(admin_of) == PointerIsValid(is_admin));
4737+
4738+
/* If cache is valid and ADMIN OPTION not sought, just return the list */
4739+
if (cached_role[type] == roleid && !OidIsValid(admin_of) &&
4740+
OidIsValid(cached_role[type]))
4741+
return cached_roles[type];
47454742

47464743
/*
47474744
* Find all the roles that roleid is a member of, including multi-level
@@ -4762,9 +4759,8 @@ roles_has_privs_of(Oid roleid)
47624759
CatCList *memlist;
47634760
int i;
47644761

4765-
/* Ignore non-inheriting roles */
4766-
if (!has_rolinherit(memberid))
4767-
continue;
4762+
if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
4763+
continue; /* ignore non-inheriting roles */
47684764

47694765
/* Find roles that memberid is directly a member of */
47704766
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
@@ -4775,83 +4771,13 @@ roles_has_privs_of(Oid roleid)
47754771
Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
47764772

47774773
/*
4778-
* Even though there shouldn't be any loops in the membership
4779-
* graph, we must test for having already seen this role. It is
4780-
* legal for instance to have both A->B and A->C->B.
4774+
* While otherid==InvalidOid shouldn't appear in the catalog, the
4775+
* OidIsValid() avoids crashing if that arises.
47814776
*/
4782-
roles_list = list_append_unique_oid(roles_list, otherid);
4783-
}
4784-
ReleaseSysCacheList(memlist);
4785-
}
4786-
4787-
/*
4788-
* Copy the completed list into TopMemoryContext so it will persist.
4789-
*/
4790-
oldctx = MemoryContextSwitchTo(TopMemoryContext);
4791-
new_cached_privs_roles = list_copy(roles_list);
4792-
MemoryContextSwitchTo(oldctx);
4793-
list_free(roles_list);
4794-
4795-
/*
4796-
* Now safe to assign to state variable
4797-
*/
4798-
cached_privs_role = InvalidOid; /* just paranoia */
4799-
list_free(cached_privs_roles);
4800-
cached_privs_roles = new_cached_privs_roles;
4801-
cached_privs_role = roleid;
4802-
4803-
/* And now we can return the answer */
4804-
return cached_privs_roles;
4805-
}
4806-
4807-
4808-
/*
4809-
* Get a list of roles that the specified roleid is a member of
4810-
*
4811-
* This is defined to recurse through roles regardless of rolinherit.
4812-
*
4813-
* Since indirect membership testing is relatively expensive, we cache
4814-
* a list of memberships. Hence, the result is only guaranteed good until
4815-
* the next call of roles_is_member_of()!
4816-
*/
4817-
static List *
4818-
roles_is_member_of(Oid roleid)
4819-
{
4820-
List *roles_list;
4821-
ListCell *l;
4822-
List *new_cached_membership_roles;
4823-
MemoryContext oldctx;
4824-
4825-
/* If cache is already valid, just return the list */
4826-
if (OidIsValid(cached_member_role) && cached_member_role == roleid)
4827-
return cached_membership_roles;
4828-
4829-
/*
4830-
* Find all the roles that roleid is a member of, including multi-level
4831-
* recursion. The role itself will always be the first element of the
4832-
* resulting list.
4833-
*
4834-
* Each element of the list is scanned to see if it adds any indirect
4835-
* memberships. We can use a single list as both the record of
4836-
* already-found memberships and the agenda of roles yet to be scanned.
4837-
* This is a bit tricky but works because the foreach() macro doesn't
4838-
* fetch the next list element until the bottom of the loop.
4839-
*/
4840-
roles_list = list_make1_oid(roleid);
4841-
4842-
foreach(l, roles_list)
4843-
{
4844-
Oid memberid = lfirst_oid(l);
4845-
CatCList *memlist;
4846-
int i;
4847-
4848-
/* Find roles that memberid is directly a member of */
4849-
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
4850-
ObjectIdGetDatum(memberid));
4851-
for (i = 0; i < memlist->n_members; i++)
4852-
{
4853-
HeapTuple tup = &memlist->members[i]->tuple;
4854-
Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
4777+
if (otherid == admin_of &&
4778+
((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
4779+
OidIsValid(admin_of))
4780+
*is_admin = true;
48554781

48564782
/*
48574783
* Even though there shouldn't be any loops in the membership
@@ -4867,20 +4793,20 @@ roles_is_member_of(Oid roleid)
48674793
* Copy the completed list into TopMemoryContext so it will persist.
48684794
*/
48694795
oldctx = MemoryContextSwitchTo(TopMemoryContext);
4870-
new_cached_membership_roles = list_copy(roles_list);
4796+
new_cached_roles = list_copy(roles_list);
48714797
MemoryContextSwitchTo(oldctx);
48724798
list_free(roles_list);
48734799

48744800
/*
48754801
* Now safe to assign to state variable
48764802
*/
4877-
cached_member_role = InvalidOid; /* just paranoia */
4878-
list_free(cached_membership_roles);
4879-
cached_membership_roles = new_cached_membership_roles;
4880-
cached_member_role = roleid;
4803+
cached_role[type] = InvalidOid; /* just paranoia */
4804+
list_free(cached_roles[type]);
4805+
cached_roles[type] = new_cached_roles;
4806+
cached_role[type] = roleid;
48814807

48824808
/* And now we can return the answer */
4883-
return cached_membership_roles;
4809+
return cached_roles[type];
48844810
}
48854811

48864812

@@ -4906,7 +4832,9 @@ has_privs_of_role(Oid member, Oid role)
49064832
* Find all the roles that member has the privileges of, including
49074833
* multi-level recursion, then see if target role is any one of them.
49084834
*/
4909-
return list_member_oid(roles_has_privs_of(member), role);
4835+
return list_member_oid(roles_is_member_of(member, ROLERECURSE_PRIVS,
4836+
InvalidOid, NULL),
4837+
role);
49104838
}
49114839

49124840

@@ -4930,7 +4858,9 @@ is_member_of_role(Oid member, Oid role)
49304858
* Find all the roles that member is a member of, including multi-level
49314859
* recursion, then see if target role is any one of them.
49324860
*/
4933-
return list_member_oid(roles_is_member_of(member), role);
4861+
return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS,
4862+
InvalidOid, NULL),
4863+
role);
49344864
}
49354865

49364866
/*
@@ -4964,7 +4894,9 @@ is_member_of_role_nosuper(Oid member, Oid role)
49644894
* Find all the roles that member is a member of, including multi-level
49654895
* recursion, then see if target role is any one of them.
49664896
*/
4967-
return list_member_oid(roles_is_member_of(member), role);
4897+
return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS,
4898+
InvalidOid, NULL),
4899+
role);
49684900
}
49694901

49704902

@@ -4977,8 +4909,6 @@ bool
49774909
is_admin_of_role(Oid member, Oid role)
49784910
{
49794911
bool result = false;
4980-
List *roles_list;
4981-
ListCell *l;
49824912

49834913
if (superuser_arg(member))
49844914
return true;
@@ -5016,44 +4946,7 @@ is_admin_of_role(Oid member, Oid role)
50164946
return member == GetSessionUserId() &&
50174947
!InLocalUserIdChange() && !InSecurityRestrictedOperation();
50184948

5019-
/*
5020-
* Find all the roles that member is a member of, including multi-level
5021-
* recursion. We build a list in the same way that is_member_of_role does
5022-
* to track visited and unvisited roles.
5023-
*/
5024-
roles_list = list_make1_oid(member);
5025-
5026-
foreach(l, roles_list)
5027-
{
5028-
Oid memberid = lfirst_oid(l);
5029-
CatCList *memlist;
5030-
int i;
5031-
5032-
/* Find roles that memberid is directly a member of */
5033-
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
5034-
ObjectIdGetDatum(memberid));
5035-
for (i = 0; i < memlist->n_members; i++)
5036-
{
5037-
HeapTuple tup = &memlist->members[i]->tuple;
5038-
Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
5039-
5040-
if (otherid == role &&
5041-
((Form_pg_auth_members) GETSTRUCT(tup))->admin_option)
5042-
{
5043-
/* Found what we came for, so can stop searching */
5044-
result = true;
5045-
break;
5046-
}
5047-
5048-
roles_list = list_append_unique_oid(roles_list, otherid);
5049-
}
5050-
ReleaseSysCacheList(memlist);
5051-
if (result)
5052-
break;
5053-
}
5054-
5055-
list_free(roles_list);
5056-
4949+
(void) roles_is_member_of(member, ROLERECURSE_MEMBERS, role, &result);
50574950
return result;
50584951
}
50594952

@@ -5125,10 +5018,11 @@ select_best_grantor(Oid roleId, AclMode privileges,
51255018
/*
51265019
* Otherwise we have to do a careful search to see if roleId has the
51275020
* privileges of any suitable role. Note: we can hang onto the result of
5128-
* roles_has_privs_of() throughout this loop, because aclmask_direct()
5021+
* roles_is_member_of() throughout this loop, because aclmask_direct()
51295022
* doesn't query any role memberships.
51305023
*/
5131-
roles_list = roles_has_privs_of(roleId);
5024+
roles_list = roles_is_member_of(roleId, ROLERECURSE_PRIVS,
5025+
InvalidOid, NULL);
51325026

51335027
/* initialize candidate result as default */
51345028
*grantorId = roleId;

0 commit comments

Comments
 (0)