@@ -50,32 +50,24 @@ typedef struct
50
50
/*
51
51
* We frequently need to test whether a given role is a member of some other
52
52
* 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.
59
55
*
60
56
* Possibly this mechanism should be generalized to allow caching membership
61
57
* info for multiple roles?
62
58
*
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.
74
63
*/
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 };
79
71
80
72
81
73
static const char * getid (const char * s , char * n );
@@ -4675,8 +4667,8 @@ initialize_acl(void)
4675
4667
{
4676
4668
/*
4677
4669
* 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())
4680
4672
*/
4681
4673
CacheRegisterSyscacheCallback (AUTHMEMROLEMEM ,
4682
4674
RoleMembershipCacheCallback ,
@@ -4695,8 +4687,8 @@ static void
4695
4687
RoleMembershipCacheCallback (Datum arg , int cacheid , uint32 hashvalue )
4696
4688
{
4697
4689
/* 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 ;
4700
4692
}
4701
4693
4702
4694
@@ -4718,30 +4710,35 @@ has_rolinherit(Oid roleid)
4718
4710
4719
4711
4720
4712
/*
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
4722
4714
*
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".
4726
4719
*
4727
4720
* Since indirect membership testing is relatively expensive, we cache
4728
4721
* 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 ()!
4730
4723
*
4731
4724
* For the benefit of select_best_grantor, the result is defined to be
4732
4725
* in breadth-first order, ie, closer relationships earlier.
4733
4726
*/
4734
4727
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 )
4736
4730
{
4737
4731
List * roles_list ;
4738
4732
ListCell * l ;
4739
- List * new_cached_privs_roles ;
4733
+ List * new_cached_roles ;
4740
4734
MemoryContext oldctx ;
4741
4735
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 ];
4745
4742
4746
4743
/*
4747
4744
* Find all the roles that roleid is a member of, including multi-level
@@ -4762,9 +4759,8 @@ roles_has_privs_of(Oid roleid)
4762
4759
CatCList * memlist ;
4763
4760
int i ;
4764
4761
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 */
4768
4764
4769
4765
/* Find roles that memberid is directly a member of */
4770
4766
memlist = SearchSysCacheList1 (AUTHMEMMEMROLE ,
@@ -4775,83 +4771,13 @@ roles_has_privs_of(Oid roleid)
4775
4771
Oid otherid = ((Form_pg_auth_members ) GETSTRUCT (tup ))-> roleid ;
4776
4772
4777
4773
/*
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.
4781
4776
*/
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;
4855
4781
4856
4782
/*
4857
4783
* Even though there shouldn't be any loops in the membership
@@ -4867,20 +4793,20 @@ roles_is_member_of(Oid roleid)
4867
4793
* Copy the completed list into TopMemoryContext so it will persist.
4868
4794
*/
4869
4795
oldctx = MemoryContextSwitchTo (TopMemoryContext );
4870
- new_cached_membership_roles = list_copy (roles_list );
4796
+ new_cached_roles = list_copy (roles_list );
4871
4797
MemoryContextSwitchTo (oldctx );
4872
4798
list_free (roles_list );
4873
4799
4874
4800
/*
4875
4801
* Now safe to assign to state variable
4876
4802
*/
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 ;
4881
4807
4882
4808
/* And now we can return the answer */
4883
- return cached_membership_roles ;
4809
+ return cached_roles [ type ] ;
4884
4810
}
4885
4811
4886
4812
@@ -4906,7 +4832,9 @@ has_privs_of_role(Oid member, Oid role)
4906
4832
* Find all the roles that member has the privileges of, including
4907
4833
* multi-level recursion, then see if target role is any one of them.
4908
4834
*/
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 );
4910
4838
}
4911
4839
4912
4840
@@ -4930,7 +4858,9 @@ is_member_of_role(Oid member, Oid role)
4930
4858
* Find all the roles that member is a member of, including multi-level
4931
4859
* recursion, then see if target role is any one of them.
4932
4860
*/
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 );
4934
4864
}
4935
4865
4936
4866
/*
@@ -4964,7 +4894,9 @@ is_member_of_role_nosuper(Oid member, Oid role)
4964
4894
* Find all the roles that member is a member of, including multi-level
4965
4895
* recursion, then see if target role is any one of them.
4966
4896
*/
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 );
4968
4900
}
4969
4901
4970
4902
@@ -4977,8 +4909,6 @@ bool
4977
4909
is_admin_of_role (Oid member , Oid role )
4978
4910
{
4979
4911
bool result = false;
4980
- List * roles_list ;
4981
- ListCell * l ;
4982
4912
4983
4913
if (superuser_arg (member ))
4984
4914
return true;
@@ -5016,44 +4946,7 @@ is_admin_of_role(Oid member, Oid role)
5016
4946
return member == GetSessionUserId () &&
5017
4947
!InLocalUserIdChange () && !InSecurityRestrictedOperation ();
5018
4948
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 );
5057
4950
return result ;
5058
4951
}
5059
4952
@@ -5125,10 +5018,11 @@ select_best_grantor(Oid roleId, AclMode privileges,
5125
5018
/*
5126
5019
* Otherwise we have to do a careful search to see if roleId has the
5127
5020
* 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()
5129
5022
* doesn't query any role memberships.
5130
5023
*/
5131
- roles_list = roles_has_privs_of (roleId );
5024
+ roles_list = roles_is_member_of (roleId , ROLERECURSE_PRIVS ,
5025
+ InvalidOid , NULL );
5132
5026
5133
5027
/* initialize candidate result as default */
5134
5028
* grantorId = roleId ;
0 commit comments