6868 */
6969public class CompositeRolesStore {
7070
71-
71+ private static final String ROLES_STORE_SOURCE = "roles_stores" ;
7272 private static final Setting <Integer > CACHE_SIZE_SETTING =
7373 Setting .intSetting ("xpack.security.authz.store.roles.cache.max_size" , 10000 , Property .NodeScope );
7474 private static final Setting <Integer > NEGATIVE_LOOKUP_CACHE_SIZE_SETTING =
@@ -92,7 +92,8 @@ public class CompositeRolesStore {
9292 private final NativeRolesStore nativeRolesStore ;
9393 private final NativePrivilegeStore privilegeStore ;
9494 private final XPackLicenseState licenseState ;
95- private final Cache <Set <String >, Role > roleCache ;
95+ private final FieldPermissionsCache fieldPermissionsCache ;
96+ private final Cache <RoleKey , Role > roleCache ;
9697 private final Cache <String , Boolean > negativeLookupCache ;
9798 private final ThreadContext threadContext ;
9899 private final AtomicLong numInvalidation = new AtomicLong ();
@@ -102,13 +103,14 @@ public class CompositeRolesStore {
102103 public CompositeRolesStore (Settings settings , FileRolesStore fileRolesStore , NativeRolesStore nativeRolesStore ,
103104 ReservedRolesStore reservedRolesStore , NativePrivilegeStore privilegeStore ,
104105 List <BiConsumer <Set <String >, ActionListener <RoleRetrievalResult >>> rolesProviders ,
105- ThreadContext threadContext , XPackLicenseState licenseState ) {
106+ ThreadContext threadContext , XPackLicenseState licenseState , FieldPermissionsCache fieldPermissionsCache ) {
106107 this .fileRolesStore = fileRolesStore ;
107108 fileRolesStore .addListener (this ::invalidate );
108109 this .nativeRolesStore = nativeRolesStore ;
109110 this .privilegeStore = privilegeStore ;
110111 this .licenseState = licenseState ;
111- CacheBuilder <Set <String >, Role > builder = CacheBuilder .builder ();
112+ this .fieldPermissionsCache = fieldPermissionsCache ;
113+ CacheBuilder <RoleKey , Role > builder = CacheBuilder .builder ();
112114 final int cacheSize = CACHE_SIZE_SETTING .get (settings );
113115 if (cacheSize >= 0 ) {
114116 builder .setMaximumWeight (cacheSize );
@@ -133,8 +135,9 @@ public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, Nat
133135 }
134136 }
135137
136- public void roles (Set <String > roleNames , FieldPermissionsCache fieldPermissionsCache , ActionListener <Role > roleActionListener ) {
137- Role existing = roleCache .get (roleNames );
138+ public void roles (Set <String > roleNames , ActionListener <Role > roleActionListener ) {
139+ final RoleKey roleKey = new RoleKey (roleNames , ROLES_STORE_SOURCE );
140+ Role existing = roleCache .get (roleKey );
138141 if (existing != null ) {
139142 roleActionListener .onResponse (existing );
140143 } else {
@@ -154,33 +157,54 @@ public void roles(Set<String> roleNames, FieldPermissionsCache fieldPermissionsC
154157 .filter ((rd ) -> rd .isUsingDocumentOrFieldLevelSecurity () == false )
155158 .collect (Collectors .toSet ());
156159 }
157- logger .trace ("Building role from descriptors [{}] for names [{}]" , effectiveDescriptors , roleNames );
158- buildRoleFromDescriptors (effectiveDescriptors , fieldPermissionsCache , privilegeStore , ActionListener .wrap (role -> {
159- if (role != null && rolesRetrievalResult .isSuccess ()) {
160- try (ReleasableLock ignored = readLock .acquire ()) {
161- /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold
162- * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching
163- * stuff in an async fashion we need to make sure that if the cache got invalidated since we
164- * started the request we don't put a potential stale result in the cache, hence the
165- * numInvalidation.get() comparison to the number of invalidation when we started. we just try to
166- * be on the safe side and don't cache potentially stale results
167- */
168- if (invalidationCounter == numInvalidation .get ()) {
169- roleCache .computeIfAbsent (roleNames , (s ) -> role );
170- }
171- }
172-
173- for (String missingRole : rolesRetrievalResult .getMissingRoles ()) {
174- negativeLookupCache .computeIfAbsent (missingRole , s -> Boolean .TRUE );
175- }
176- }
177- roleActionListener .onResponse (role );
178- }, roleActionListener ::onFailure ));
160+ buildThenMaybeCacheRole (roleKey , effectiveDescriptors , rolesRetrievalResult .getMissingRoles (),
161+ rolesRetrievalResult .isSuccess (), invalidationCounter , roleActionListener );
179162 },
180163 roleActionListener ::onFailure ));
181164 }
182165 }
183166
167+ public void buildAndCacheRoleFromDescriptors (Collection <RoleDescriptor > roleDescriptors , String source ,
168+ ActionListener <Role > listener ) {
169+ if (ROLES_STORE_SOURCE .equals (source )) {
170+ throw new IllegalArgumentException ("source [" + ROLES_STORE_SOURCE + "] is reserved for internal use" );
171+ }
172+ RoleKey roleKey = new RoleKey (roleDescriptors .stream ().map (RoleDescriptor ::getName ).collect (Collectors .toSet ()), source );
173+ Role existing = roleCache .get (roleKey );
174+ if (existing != null ) {
175+ listener .onResponse (existing );
176+ } else {
177+ final long invalidationCounter = numInvalidation .get ();
178+ buildThenMaybeCacheRole (roleKey , roleDescriptors , Collections .emptySet (), true , invalidationCounter , listener );
179+ }
180+ }
181+
182+ private void buildThenMaybeCacheRole (RoleKey roleKey , Collection <RoleDescriptor > roleDescriptors , Set <String > missing ,
183+ boolean tryCache , long invalidationCounter , ActionListener <Role > listener ) {
184+ logger .trace ("Building role from descriptors [{}] for names [{}] from source [{}]" , roleDescriptors , roleKey .names , roleKey .source );
185+ buildRoleFromDescriptors (roleDescriptors , fieldPermissionsCache , privilegeStore , ActionListener .wrap (role -> {
186+ if (role != null && tryCache ) {
187+ try (ReleasableLock ignored = readLock .acquire ()) {
188+ /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold
189+ * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching
190+ * stuff in an async fashion we need to make sure that if the cache got invalidated since we
191+ * started the request we don't put a potential stale result in the cache, hence the
192+ * numInvalidation.get() comparison to the number of invalidation when we started. we just try to
193+ * be on the safe side and don't cache potentially stale results
194+ */
195+ if (invalidationCounter == numInvalidation .get ()) {
196+ roleCache .computeIfAbsent (roleKey , (s ) -> role );
197+ }
198+ }
199+
200+ for (String missingRole : missing ) {
201+ negativeLookupCache .computeIfAbsent (missingRole , s -> Boolean .TRUE );
202+ }
203+ }
204+ listener .onResponse (role );
205+ }, listener ::onFailure ));
206+ }
207+
184208 public void getRoleDescriptors (Set <String > roleNames , ActionListener <Set <RoleDescriptor >> listener ) {
185209 roleDescriptors (roleNames , ActionListener .wrap (rolesRetrievalResult -> {
186210 if (rolesRetrievalResult .isSuccess ()) {
@@ -241,11 +265,6 @@ private String names(Collection<RoleDescriptor> descriptors) {
241265 return descriptors .stream ().map (RoleDescriptor ::getName ).collect (Collectors .joining ("," ));
242266 }
243267
244- public void buildRoleFromDescriptors (Collection <RoleDescriptor > roleDescriptors , FieldPermissionsCache fieldPermissionsCache ,
245- ActionListener <Role > listener ) {
246- buildRoleFromDescriptors (roleDescriptors , fieldPermissionsCache , privilegeStore , listener );
247- }
248-
249268 public static void buildRoleFromDescriptors (Collection <RoleDescriptor > roleDescriptors , FieldPermissionsCache fieldPermissionsCache ,
250269 NativePrivilegeStore privilegeStore , ActionListener <Role > listener ) {
251270 if (roleDescriptors .isEmpty ()) {
@@ -346,10 +365,10 @@ public void invalidate(String role) {
346365
347366 // the cache cannot be modified while doing this operation per the terms of the cache iterator
348367 try (ReleasableLock ignored = writeLock .acquire ()) {
349- Iterator <Set < String > > keyIter = roleCache .keys ().iterator ();
368+ Iterator <RoleKey > keyIter = roleCache .keys ().iterator ();
350369 while (keyIter .hasNext ()) {
351- Set < String > key = keyIter .next ();
352- if (key .contains (role )) {
370+ RoleKey key = keyIter .next ();
371+ if (key .names . contains (role )) {
353372 keyIter .remove ();
354373 }
355374 }
@@ -362,10 +381,10 @@ public void invalidate(Set<String> roles) {
362381
363382 // the cache cannot be modified while doing this operation per the terms of the cache iterator
364383 try (ReleasableLock ignored = writeLock .acquire ()) {
365- Iterator <Set < String > > keyIter = roleCache .keys ().iterator ();
384+ Iterator <RoleKey > keyIter = roleCache .keys ().iterator ();
366385 while (keyIter .hasNext ()) {
367- Set < String > key = keyIter .next ();
368- if (Sets .haveEmptyIntersection (key , roles ) == false ) {
386+ RoleKey key = keyIter .next ();
387+ if (Sets .haveEmptyIntersection (key . names , roles ) == false ) {
369388 keyIter .remove ();
370389 }
371390 }
@@ -461,6 +480,31 @@ private Set<String> getMissingRoles() {
461480 }
462481 }
463482
483+ private static final class RoleKey {
484+
485+ private final Set <String > names ;
486+ private final String source ;
487+
488+ private RoleKey (Set <String > names , String source ) {
489+ this .names = Objects .requireNonNull (names );
490+ this .source = Objects .requireNonNull (source );
491+ }
492+
493+ @ Override
494+ public boolean equals (Object o ) {
495+ if (this == o ) return true ;
496+ if (o == null || getClass () != o .getClass ()) return false ;
497+ RoleKey roleKey = (RoleKey ) o ;
498+ return names .equals (roleKey .names ) &&
499+ source .equals (roleKey .source );
500+ }
501+
502+ @ Override
503+ public int hashCode () {
504+ return Objects .hash (names , source );
505+ }
506+ }
507+
464508 public static List <Setting <?>> getSettings () {
465509 return Arrays .asList (CACHE_SIZE_SETTING , NEGATIVE_LOOKUP_CACHE_SIZE_SETTING );
466510 }
0 commit comments