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 <officialasishkumar@gmail.com>
This commit is contained in:
Asish Kumar 2026-04-04 15:28:51 +05:30
parent 6250f9d57c
commit 88bb22bb20

View file

@ -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);