@@ -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
8173static 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 (foreach AUTHMEM search in this file) or
4679- *pg_authid (for has_rolinherit())
4670+ * of pg_auth_members (forroles_is_member_of()) or pg_authid (for
4671+ * has_rolinherit())
46804672 */
46814673CacheRegisterSyscacheCallback (AUTHMEMROLEMEM ,
46824674RoleMembershipCacheCallback ,
@@ -4695,8 +4687,8 @@ static void
46954687RoleMembershipCacheCallback (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 roleidhas the privileges of
4713+ * Get a list of roles that the specified roleidis 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 ofroles_has_privs_of ()!
4722+ * the next call ofroles_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 */
47344727static 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{
47374731List * roles_list ;
47384732ListCell * l ;
4739- List * new_cached_privs_roles ;
4733+ List * new_cached_roles ;
47404734MemoryContext 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)
47624759CatCList * memlist ;
47634760int 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 */
47704766memlist = SearchSysCacheList1 (AUTHMEMMEMROLE ,
@@ -4775,83 +4771,13 @@ roles_has_privs_of(Oid roleid)
47754771Oid 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 */
48694795oldctx = MemoryContextSwitchTo (TopMemoryContext );
4870- new_cached_membership_roles = list_copy (roles_list );
4796+ new_cached_roles = list_copy (roles_list );
48714797MemoryContextSwitchTo (oldctx );
48724798list_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
49774909is_admin_of_role (Oid member ,Oid role )
49784910{
49794911bool result = false;
4980- List * roles_list ;
4981- ListCell * l ;
49824912
49834913if (superuser_arg (member ))
49844914return true;
@@ -5016,44 +4946,7 @@ is_admin_of_role(Oid member, Oid role)
50164946return 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 );
50574950return 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 ;