From 88bb22bb20e7523697ebe6c77887e246b5b0740e Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Sat, 4 Apr 2026 15:28:51 +0530 Subject: [PATCH] Respect storage provider cache policies for federated roles and groups RealmCacheSession unconditionally cached federated roles and groups without checking the storage provider's configured cache policy. This meant policies like NO_CACHE, MAX_LIFESPAN, EVICT_DAILY, and EVICT_WEEKLY were silently ignored for roles and groups, even though they worked correctly for clients. Apply the same cache policy pattern used by cacheClient() and validateCache() to roles and groups: - On cache miss: check isEnabled() and getCachePolicy() before storing, skip caching for NO_CACHE, and apply lifespan for timed policies - On cache hit: validate with shouldInvalidate() and reload from delegate if the cached entry has expired per the configured policy Closes #47660 Signed-off-by: Asish Kumar --- .../cache/infinispan/RealmCacheSession.java | 160 +++++++++++++++--- 1 file changed, 136 insertions(+), 24 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index 935f2d38809..0d9ef3becf1 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -78,10 +78,13 @@ import org.keycloak.models.cache.infinispan.events.RoleAddedEvent; import org.keycloak.models.cache.infinispan.events.RoleRemovedEvent; import org.keycloak.models.cache.infinispan.events.RoleUpdatedEvent; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.CacheableStorageProviderModel; import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.StorageId; import org.keycloak.storage.StoreManagers; import org.keycloak.storage.client.ClientStorageProviderModel; +import org.keycloak.storage.group.GroupStorageProviderModel; +import org.keycloak.storage.role.RoleStorageProviderModel; import org.jboss.logging.Logger; @@ -932,15 +935,79 @@ public class RealmCacheSession implements CacheRealmProvider { return managedRoles.get(id); } - CachedRole cached = getCachedRole(realm, id); - if (cached == null) { - return null; + CachedRole cached = cache.get(id, CachedRole.class); + if (cached != null && !cached.getRealm().equals(realm.getId())) { + cached = null; + } + + RoleModel adapter; + if (cached != null) { + adapter = validateCachedRole(realm, cached); + } else { + adapter = cacheRole(realm, id); + } + if (adapter instanceof RoleAdapter roleAdapter) { + managedRoles.put(id, roleAdapter); } - RoleAdapter adapter = new RoleAdapter(cached,this, realm); - managedRoles.put(id, adapter); return adapter; } + private RoleModel cacheRole(RealmModel realm, String id) { + long loaded = cache.getCurrentRevision(id); + RoleModel model = getRoleDelegate().getRoleById(realm, id); + if (model == null) return null; + + StorageId storageId = new StorageId(id); + if (!storageId.isLocal()) { + ComponentModel component = realm.getComponent(storageId.getProviderId()); + RoleStorageProviderModel providerModel = new RoleStorageProviderModel(component); + if (!providerModel.isEnabled()) { + return model; + } + CacheableStorageProviderModel.CachePolicy policy = providerModel.getCachePolicy(); + if (policy == CacheableStorageProviderModel.CachePolicy.NO_CACHE) { + return model; + } + + CachedRole cached = createCachedRole(loaded, model, realm); + long lifespan = providerModel.getLifespan(); + if (lifespan > 0) { + cache.addRevisioned(cached, startupRevision, lifespan); + } else { + cache.addRevisioned(cached, startupRevision); + } + return new RoleAdapter(cached, this, realm); + } + + CachedRole cached = createCachedRole(loaded, model, realm); + cache.addRevisioned(cached, startupRevision); + return new RoleAdapter(cached, this, realm); + } + + private RoleModel validateCachedRole(RealmModel realm, CachedRole cached) { + StorageId storageId = new StorageId(cached.getId()); + if (!storageId.isLocal()) { + ComponentModel component = realm.getComponent(storageId.getProviderId()); + if (component == null) { + return null; + } + RoleStorageProviderModel model = new RoleStorageProviderModel(component); + if (model.shouldInvalidate(cached)) { + String containerId = (cached instanceof CachedClientRole) ? ((CachedClientRole) cached).getClientId() : realm.getId(); + registerRoleInvalidation(cached.getId(), cached.getName(), containerId); + return getRoleDelegate().getRoleById(realm, cached.getId()); + } + } + return new RoleAdapter(cached, this, realm); + } + + private CachedRole createCachedRole(long loaded, RoleModel model, RealmModel realm) { + if (model.isClientRole()) { + return new CachedClientRole(loaded, model.getContainerId(), model, realm); + } + return new CachedRealmRole(loaded, model, realm); + } + protected CachedRole getCachedRole(RealmModel realm, String id) { CachedRole cached = cache.get(id, CachedRole.class); if (cached != null && !cached.getRealm().equals(realm.getId())) { @@ -951,11 +1018,7 @@ public class RealmCacheSession implements CacheRealmProvider { long loaded = cache.getCurrentRevision(id); RoleModel model = getRoleDelegate().getRoleById(realm, id); if (model == null) return null; - if (model.isClientRole()) { - cached = new CachedClientRole(loaded, model.getContainerId(), model, realm); - } else { - cached = new CachedRealmRole(loaded, model, realm); - } + cached = createCachedRole(loaded, model, realm); cache.addRevisioned(cached, startupRevision); } return cached; @@ -963,29 +1026,78 @@ public class RealmCacheSession implements CacheRealmProvider { @Override public GroupModel getGroupById(RealmModel realm, String id) { + if (invalidations.contains(id)) { + return getGroupDelegate().getGroupById(realm, id); + } else if (managedGroups.containsKey(id)) { + return managedGroups.get(id); + } + CachedGroup cached = cache.get(id, CachedGroup.class); if (cached != null && !cached.getRealm().equals(realm.getId())) { cached = null; } - if (cached == null) { - long loaded = cache.getCurrentRevision(id); - GroupModel model = getGroupDelegate().getGroupById(realm, id); - if (model == null) return null; - if (invalidations.contains(id)) return model; - cached = new CachedGroup(loaded, realm, model); - cache.addRevisioned(cached, startupRevision); - - } else if (invalidations.contains(id)) { - return getGroupDelegate().getGroupById(realm, id); - } else if (managedGroups.containsKey(id)) { - return managedGroups.get(id); + GroupModel adapter; + if (cached != null) { + adapter = validateCachedGroup(realm, cached); + } else { + adapter = cacheGroup(realm, id); + } + if (adapter instanceof GroupAdapter groupAdapter) { + managedGroups.put(id, groupAdapter); } - GroupAdapter adapter = new GroupAdapter(cached, this, session, realm); - managedGroups.put(id, adapter); return adapter; } + private GroupModel cacheGroup(RealmModel realm, String id) { + long loaded = cache.getCurrentRevision(id); + GroupModel model = getGroupDelegate().getGroupById(realm, id); + if (model == null) return null; + if (invalidations.contains(id)) return model; + + StorageId storageId = new StorageId(id); + if (!storageId.isLocal()) { + ComponentModel component = realm.getComponent(storageId.getProviderId()); + GroupStorageProviderModel providerModel = new GroupStorageProviderModel(component); + if (!providerModel.isEnabled()) { + return model; + } + CacheableStorageProviderModel.CachePolicy policy = providerModel.getCachePolicy(); + if (policy == CacheableStorageProviderModel.CachePolicy.NO_CACHE) { + return model; + } + + CachedGroup cachedGroup = new CachedGroup(loaded, realm, model); + long lifespan = providerModel.getLifespan(); + if (lifespan > 0) { + cache.addRevisioned(cachedGroup, startupRevision, lifespan); + } else { + cache.addRevisioned(cachedGroup, startupRevision); + } + return new GroupAdapter(cachedGroup, this, session, realm); + } + + CachedGroup cachedGroup = new CachedGroup(loaded, realm, model); + cache.addRevisioned(cachedGroup, startupRevision); + return new GroupAdapter(cachedGroup, this, session, realm); + } + + private GroupModel validateCachedGroup(RealmModel realm, CachedGroup cached) { + StorageId storageId = new StorageId(cached.getId()); + if (!storageId.isLocal()) { + ComponentModel component = realm.getComponent(storageId.getProviderId()); + if (component == null) { + return null; + } + GroupStorageProviderModel model = new GroupStorageProviderModel(component); + if (model.shouldInvalidate(cached)) { + registerGroupInvalidation(cached.getId()); + return getGroupDelegate().getGroupById(realm, cached.getId()); + } + } + return new GroupAdapter(cached, this, session, realm); + } + @Override public GroupModel getGroupByName(RealmModel realm, GroupModel parent, String name) { String cacheKey = getGroupByNameCacheKey(realm.getId(), parent != null? parent.getId(): null, name);