From be880ae204cf5fb4bfc304634725cfd1cae46ad0 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 8 Apr 2025 03:22:22 -0300 Subject: [PATCH] Do not cache partial results when FGAP is enabled Closes #38705 Signed-off-by: Pedro Igor --- .../cache/infinispan/DefaultLazyLoader.java | 13 ++++- .../models/cache/infinispan/GroupAdapter.java | 16 +++--- .../models/cache/infinispan/LazyLoader.java | 5 +- .../models/cache/infinispan/RealmAdapter.java | 8 +-- .../models/cache/infinispan/RoleAdapter.java | 13 +++-- .../models/cache/infinispan/UserAdapter.java | 26 ++++----- .../authorization/PolicyAdapter.java | 37 ++++++------ .../authorization/ResourceAdapter.java | 27 +++++---- .../authorization/entities/CachedPolicy.java | 17 +++--- .../entities/CachedResource.java | 13 +++-- .../infinispan/entities/CachedGroup.java | 19 +++---- .../infinispan/entities/CachedRealm.java | 21 +++---- .../cache/infinispan/entities/CachedRole.java | 5 +- .../cache/infinispan/entities/CachedUser.java | 25 ++++---- .../organization/CachedOrganization.java | 5 +- .../InfinispanOrganizationProvider.java | 2 +- .../organization/OrganizationAdapter.java | 7 ++- .../infinispan/stream/HasRolePredicate.java | 2 +- .../org/keycloak/models/jpa/GroupAdapter.java | 6 +- .../authorization/AdminPermissionsSchema.java | 37 ++++++++++++ .../authorization/PartialEvaluator.java | 6 ++ .../resources/admin/UserResource.java | 2 +- .../fgap/GroupResourceTypeFilteringTest.java | 57 +++++++++++++++++-- 23 files changed, 241 insertions(+), 128 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java index d0c428dfe8b..b8619226854 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java @@ -16,9 +16,13 @@ */ package org.keycloak.models.cache.infinispan; +import static org.keycloak.authorization.AdminPermissionsSchema.runWithoutAuthorization; + import java.util.function.Function; import java.util.function.Supplier; +import org.keycloak.models.KeycloakSession; + /** * Default implementation of {@link DefaultLazyLoader} that only fetches data once. This implementation is not thread-safe * and cached data is assumed to not be shared across different threads to sync state. @@ -37,10 +41,13 @@ public class DefaultLazyLoader implements LazyLoader { } @Override - public D get(Supplier sourceSupplier) { + public D get(KeycloakSession session, Supplier sourceSupplier) { if (data == null) { - S source = sourceSupplier.get(); - data = source == null ? fallback.get() : this.loader.apply(source); + runWithoutAuthorization(session, () -> { + // make sure caching does not include partial results when FGAP is enabled + S source = sourceSupplier.get(); + data = source == null ? fallback.get() : loader.apply(source); + }); } return data; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java index 223a4be2e53..0bf06a467ca 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java @@ -134,13 +134,13 @@ public class GroupAdapter implements GroupModel { @Override public String getFirstAttribute(String name) { if (isUpdated()) return updated.getFirstAttribute(name); - return cached.getAttributes(modelSupplier).getFirst(name); + return cached.getAttributes(keycloakSession, modelSupplier).getFirst(name); } @Override public Stream getAttributeStream(String name) { if (isUpdated()) return updated.getAttributeStream(name); - List values = cached.getAttributes(modelSupplier).get(name); + List values = cached.getAttributes(keycloakSession, modelSupplier).get(name); if (values == null) return Stream.empty(); return values.stream(); } @@ -148,7 +148,7 @@ public class GroupAdapter implements GroupModel { @Override public Map> getAttributes() { if (isUpdated()) return updated.getAttributes(); - return cached.getAttributes(modelSupplier); + return cached.getAttributes(keycloakSession, modelSupplier); } @Override @@ -167,13 +167,13 @@ public class GroupAdapter implements GroupModel { public boolean hasDirectRole(RoleModel role) { if (isUpdated()) return updated.hasDirectRole(role); - return cached.getRoleMappings(modelSupplier).contains(role.getId()); + return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId()); } @Override public boolean hasRole(RoleModel role) { if (isUpdated()) return updated.hasRole(role); - if (cached.getRoleMappings(modelSupplier).contains(role.getId())) return true; + if (cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId())) return true; if (getRoleMappingsStream().anyMatch(r -> r.hasRole(role))) return true; GroupModel parent = getParent(); return parent != null && parent.hasRole(role); @@ -189,7 +189,7 @@ public class GroupAdapter implements GroupModel { public Stream getRoleMappingsStream() { if (isUpdated()) return updated.getRoleMappingsStream(); Set roles = new HashSet<>(); - for (String id : cached.getRoleMappings(modelSupplier)) { + for (String id : cached.getRoleMappings(keycloakSession, modelSupplier)) { RoleModel roleById = keycloakSession.roles().getRoleById(realm, id); if (roleById == null) { // chance that role was removed, so just delegate to persistence and get user invalidated @@ -225,7 +225,7 @@ public class GroupAdapter implements GroupModel { public Stream getSubGroupsStream() { if (isUpdated()) return updated.getSubGroupsStream(); Set subGroups = new HashSet<>(); - for (String id : cached.getSubGroups(modelSupplier)) { + for (String id : cached.getSubGroups(keycloakSession, modelSupplier)) { GroupModel subGroup = keycloakSession.groups().getGroupById(realm, id); if (subGroup == null) { // chance that role was removed, so just delegate to persistence and get user invalidated @@ -259,7 +259,7 @@ public class GroupAdapter implements GroupModel { @Override public Long getSubGroupsCount() { if (isUpdated()) return updated.getSubGroupsCount(); - return cached.getSubGroupsCount(modelSupplier); + return getGroupModel().getSubGroupsCount(); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java index b37cde7d076..9ab0b8772a1 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java @@ -18,6 +18,8 @@ package org.keycloak.models.cache.infinispan; import java.util.function.Supplier; +import org.keycloak.models.KeycloakSession; + /** *

A functional interface that can be used to return data {@code D} from a source {@code S} where implementations are free to define how and when * data is fetched from source as well how it is internally cached. @@ -33,8 +35,9 @@ public interface LazyLoader { * Returns data from the given {@code source}. Data is only fetched from {@code source} once and only if necessary, it is * up to implementations to decide the momentum to actually fetch data from source. * + * @param session the session * @param source the source from where data will be fetched. * @return the data from source */ - D get(Supplier source); + D get(KeycloakSession session, Supplier source); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 39bc4a5bdd6..7a3928690df 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -721,19 +721,19 @@ public class RealmAdapter implements CachedRealmModel { public OAuth2DeviceConfig getOAuth2DeviceConfig() { if (isUpdated()) return updated.getOAuth2DeviceConfig(); - return cached.getOAuth2DeviceConfig(modelSupplier); + return cached.getOAuth2DeviceConfig(session, modelSupplier); } @Override public CibaConfig getCibaPolicy() { if (isUpdated()) return updated.getCibaPolicy(); - return cached.getCibaConfig(modelSupplier); + return cached.getCibaConfig(session, modelSupplier); } @Override public ParConfig getParPolicy() { if (isUpdated()) return updated.getParPolicy(); - return cached.getParConfig(modelSupplier); + return cached.getParConfig(session, modelSupplier); } @Override @@ -1610,7 +1610,7 @@ public class RealmAdapter implements CachedRealmModel { @Override public Stream getDefaultClientScopesStream(boolean defaultScope) { if (isUpdated()) return updated.getDefaultClientScopesStream(defaultScope); - List clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(modelSupplier) : cached.getOptionalDefaultClientScopes(modelSupplier); + List clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(session, modelSupplier) : cached.getOptionalDefaultClientScopes(session, modelSupplier); return clientScopeIds.stream() .map(scope -> cacheSession.getClientScopeById(this, scope)) .filter(Objects::nonNull); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java index 0c70cbc8b5d..9f5a30b62e7 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java @@ -17,6 +17,7 @@ package org.keycloak.models.cache.infinispan; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleContainerModel; import org.keycloak.models.RoleModel; @@ -40,15 +41,17 @@ import java.util.stream.Stream; public class RoleAdapter implements RoleModel { protected RoleModel updated; + private final KeycloakSession session; protected CachedRole cached; protected RealmCacheSession cacheSession; protected RealmModel realm; protected Set composites; private final Supplier modelSupplier; - public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) { + public RoleAdapter(CachedRole cached, RealmCacheSession cacheSession, RealmModel realm) { this.cached = cached; - this.cacheSession = session; + this.cacheSession = cacheSession; + this.session = cacheSession.session; this.realm = realm; this.modelSupplier = this::getRoleModel; } @@ -215,7 +218,7 @@ public class RoleAdapter implements RoleModel { return updated.getFirstAttribute(name); } - return cached.getAttributes(modelSupplier).getFirst(name); + return cached.getAttributes(session, modelSupplier).getFirst(name); } @Override @@ -224,7 +227,7 @@ public class RoleAdapter implements RoleModel { return updated.getAttributeStream(name); } - List result = cached.getAttributes(modelSupplier).get(name); + List result = cached.getAttributes(session, modelSupplier).get(name); if (result == null) { return Stream.empty(); } @@ -237,7 +240,7 @@ public class RoleAdapter implements RoleModel { return updated.getAttributes(); } - return cached.getAttributes(modelSupplier); + return cached.getAttributes(session, modelSupplier); } private RoleModel getRoleModel() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index f67f274c9e8..486ce9adf63 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -223,26 +223,26 @@ public class UserAdapter implements CachedUserModel { @Override public String getFirstAttribute(String name) { if (updated != null) return updated.getFirstAttribute(name); - return cached.getFirstAttribute(name, modelSupplier); + return cached.getFirstAttribute(keycloakSession, name, modelSupplier); } @Override public Stream getAttributeStream(String name) { if (updated != null) return updated.getAttributeStream(name); - List result = cached.getAttributes(modelSupplier).get(name); + List result = cached.getAttributes(keycloakSession, modelSupplier).get(name); return (result == null) ? Stream.empty() : result.stream(); } @Override public Map> getAttributes() { if (updated != null) return updated.getAttributes(); - return cached.getAttributes(modelSupplier); + return cached.getAttributes(keycloakSession, modelSupplier); } @Override public Stream getRequiredActionsStream() { if (updated != null) return updated.getRequiredActionsStream(); - return cached.getRequiredActions(modelSupplier).stream(); + return cached.getRequiredActions(keycloakSession, modelSupplier).stream(); } @Override @@ -318,7 +318,7 @@ public class UserAdapter implements CachedUserModel { @Override public CredentialModel getStoredCredentialById(String id) { if (updated == null) { - return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> + return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential -> Objects.equals(id, credential.getId())) .findFirst().orElse(null); } @@ -328,7 +328,7 @@ public class UserAdapter implements CachedUserModel { @Override public Stream getStoredCredentialsStream() { if (updated == null) { - return cached.getStoredCredentials(modelSupplier).stream(); + return cached.getStoredCredentials(keycloakSession, modelSupplier).stream(); } return super.getStoredCredentialsStream(); } @@ -336,7 +336,7 @@ public class UserAdapter implements CachedUserModel { @Override public Stream getStoredCredentialsByTypeStream(String type) { if (updated == null) { - return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType())); + return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType())); } return super.getStoredCredentialsByTypeStream(type); } @@ -344,7 +344,7 @@ public class UserAdapter implements CachedUserModel { @Override public CredentialModel getStoredCredentialByNameAndType(String name, String type) { if (updated == null) { - return cached.getStoredCredentials(modelSupplier).stream().filter(credential -> + return cached.getStoredCredentials(keycloakSession, modelSupplier).stream().filter(credential -> Objects.equals(type, credential.getType()) && Objects.equals(name, credential.getUserLabel())) .findFirst().orElse(null); } @@ -375,13 +375,13 @@ public class UserAdapter implements CachedUserModel { @Override public boolean hasDirectRole(RoleModel role) { if (updated != null) return updated.hasDirectRole(role); - return cached.getRoleMappings(modelSupplier).contains(role.getId()); + return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId()); } @Override public boolean hasRole(RoleModel role) { if (updated != null) return updated.hasRole(role); - return cached.getRoleMappings(modelSupplier).contains(role.getId()) || + return cached.getRoleMappings(keycloakSession, modelSupplier).contains(role.getId()) || getRoleMappingsStream().anyMatch(r -> r.hasRole(role)) || RoleUtils.hasRoleFromGroup(getGroupsStream(), role, true); } @@ -396,7 +396,7 @@ public class UserAdapter implements CachedUserModel { public Stream getRoleMappingsStream() { if (updated != null) return updated.getRoleMappingsStream(); Set roles = new HashSet<>(); - for (String id : cached.getRoleMappings(modelSupplier)) { + for (String id : cached.getRoleMappings(keycloakSession, modelSupplier)) { RoleModel roleById = keycloakSession.roles().getRoleById(realm, id); if (roleById == null) { // chance that role was removed, so just delete to persistence and get user invalidated @@ -423,7 +423,7 @@ public class UserAdapter implements CachedUserModel { result = updated.getGroupsStream(); } else { Set groups = null; - for (String id : cached.getGroups(modelSupplier)) { + for (String id : cached.getGroups(keycloakSession, modelSupplier)) { GroupModel groupModel = keycloakSession.groups().getGroupById(realm, id); if (groupModel == null) { // chance that role was removed, so just delegate to persistence and get user invalidated @@ -468,7 +468,7 @@ public class UserAdapter implements CachedUserModel { @Override public boolean isMemberOf(GroupModel group) { if (updated != null) return updated.isMemberOf(group); - return cached.getGroups(modelSupplier).contains(group.getId()) || RoleUtils.isMember(getGroupsStream(), group); + return cached.getGroups(keycloakSession, modelSupplier).contains(group.getId()) || RoleUtils.isMember(getGroupsStream(), group); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java index 6b670815a6b..6478a9272cd 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java @@ -24,6 +24,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; @@ -45,11 +46,13 @@ public class PolicyAdapter implements Policy, CachedModel { private final Supplier modelSupplier; protected final CachedPolicy cached; protected final StoreFactoryCacheSession cacheSession; + private final KeycloakSession session; protected Policy updated; public PolicyAdapter(CachedPolicy cached, StoreFactoryCacheSession cacheSession) { this.cached = cached; this.cacheSession = cacheSession; + this.session = cacheSession.session; this.modelSupplier = this::getPolicyModel; } @@ -58,7 +61,7 @@ public class PolicyAdapter implements Policy, CachedModel { if (updated == null) { updated = modelSupplier.get(); String defaultResourceType = updated.getConfig().get("defaultResourceType"); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), defaultResourceType, cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), defaultResourceType, cached.getResourceServerId()); if (updated == null) throw new IllegalStateException("Not found in database"); } return updated; @@ -106,7 +109,7 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void setName(String name) { getDelegateForUpdate(); - cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.setName(name); } @@ -150,15 +153,15 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public Map getConfig() { if (isUpdated()) return updated.getConfig(); - return cached.getConfig(modelSupplier); + return cached.getConfig(session, modelSupplier); } @Override public void setConfig(Map config) { getDelegateForUpdate(); - if (config.containsKey("defaultResourceType") || cached.getConfig(modelSupplier).containsKey("defaultResourceType")) { - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId()); + if (config.containsKey("defaultResourceType") || cached.getConfig(session, modelSupplier).containsKey("defaultResourceType")) { + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId()); } updated.setConfig(config); @@ -168,7 +171,7 @@ public class PolicyAdapter implements Policy, CachedModel { public void removeConfig(String name) { getDelegateForUpdate(); if (name.equals("defaultResourceType")) { - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); } updated.removeConfig(name); @@ -178,8 +181,8 @@ public class PolicyAdapter implements Policy, CachedModel { public void putConfig(String name, String value) { getDelegateForUpdate(); if (name.equals("defaultResourceType")) { - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), value, cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), value, cached.getResourceServerId()); } updated.putConfig(name, value); } @@ -207,7 +210,7 @@ public class PolicyAdapter implements Policy, CachedModel { associatedPolicies = new HashSet<>(); PolicyStore policyStore = cacheSession.getPolicyStore(); String resourceServerId = cached.getResourceServerId(); - for (String id : cached.getAssociatedPoliciesIds(modelSupplier)) { + for (String id : cached.getAssociatedPoliciesIds(session, modelSupplier)) { Policy policy = policyStore.findById(cacheSession.getResourceServerStore().findById(resourceServerId), id); if (policy == null) { // probably because the policy was removed @@ -228,7 +231,7 @@ public class PolicyAdapter implements Policy, CachedModel { resources = new HashSet<>(); ResourceStore resourceStore = cacheSession.getResourceStore(); ResourceServer resourceServer = getResourceServer(); - for (String resourceId : cached.getResourcesIds(modelSupplier)) { + for (String resourceId : cached.getResourcesIds(session, modelSupplier)) { Resource resource = resourceStore.findById(resourceServer, resourceId); cacheSession.cacheResource(resource); resources.add(resource); @@ -239,14 +242,14 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void addScope(Scope scope) { getDelegateForUpdate(); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.addScope(scope); } @Override public void removeScope(Scope scope) { getDelegateForUpdate(); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.removeScope(scope); } @@ -269,7 +272,7 @@ public class PolicyAdapter implements Policy, CachedModel { getDelegateForUpdate(); HashSet resources = new HashSet<>(); resources.add(resource.getId()); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.addResource(resource); } @@ -279,7 +282,7 @@ public class PolicyAdapter implements Policy, CachedModel { getDelegateForUpdate(); HashSet resources = new HashSet<>(); resources.add(resource.getId()); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.removeResource(resource); } @@ -293,7 +296,7 @@ public class PolicyAdapter implements Policy, CachedModel { scopes = new HashSet<>(); ResourceServer resourceServer = getResourceServer(); ScopeStore scopeStore = cacheSession.getScopeStore(); - for (String scopeId : cached.getScopesIds(modelSupplier)) { + for (String scopeId : cached.getScopesIds(session, modelSupplier)) { Scope scope = scopeStore.findById(resourceServer, scopeId); cacheSession.cacheScope(scope); scopes.add(scope); @@ -310,7 +313,7 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public void setOwner(String owner) { getDelegateForUpdate(); - cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); + cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getConfig(session, modelSupplier).get("defaultResourceType"), cached.getResourceServerId()); updated.setOwner(owner); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java index 4c65c4a4253..830f5635756 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java @@ -23,6 +23,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PermissionTicketStore; import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource; import java.util.Collections; @@ -40,6 +41,7 @@ import java.util.stream.Collectors; public class ResourceAdapter implements Resource, CachedModel { private final Supplier modelSupplier; + private final KeycloakSession session; protected final CachedResource cached; protected final StoreFactoryCacheSession cacheSession; protected Resource updated; @@ -47,6 +49,7 @@ public class ResourceAdapter implements Resource, CachedModel { public ResourceAdapter(CachedResource cached, StoreFactoryCacheSession cacheSession) { this.cached = cached; this.cacheSession = cacheSession; + this.session = cacheSession.session; this.modelSupplier = this::getResourceModel; } @@ -54,7 +57,7 @@ public class ResourceAdapter implements Resource, CachedModel { public Resource getDelegateForUpdate() { if (updated == null) { updated = modelSupplier.get(); - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); if (updated == null) throw new IllegalStateException("Not found in database"); } return updated; @@ -102,7 +105,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setName(String name) { getDelegateForUpdate(); - cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); updated.setName(name); } @@ -115,7 +118,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setDisplayName(String name) { getDelegateForUpdate(); - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); updated.setDisplayName(name); } @@ -140,13 +143,13 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public Set getUris() { if (isUpdated()) return updated.getUris(); - return cached.getUris(modelSupplier); + return cached.getUris(session, modelSupplier); } @Override public void updateUris(Set uris) { getDelegateForUpdate(); - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); updated.updateUris(uris); } @@ -159,7 +162,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setType(String type) { getDelegateForUpdate(); - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); updated.setType(type); } @@ -171,7 +174,7 @@ public class ResourceAdapter implements Resource, CachedModel { if (isUpdated()) return updated.getScopes(); if (scopes != null) return scopes; scopes = new LinkedList<>(); - for (String scopeId : cached.getScopesIds(modelSupplier)) { + for (String scopeId : cached.getScopesIds(session, modelSupplier)) { scopes.add(cacheSession.getScopeStore().findById(getResourceServer(), scopeId)); } return scopes = Collections.unmodifiableList(scopes); @@ -192,7 +195,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public void setOwnerManagedAccess(boolean ownerManagedAccess) { getDelegateForUpdate(); - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), cached.getScopesIds(session, modelSupplier), cached.getResourceServerId(), cached.getOwner()); updated.setOwnerManagedAccess(ownerManagedAccess); } @@ -219,21 +222,21 @@ public class ResourceAdapter implements Resource, CachedModel { } } - cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner()); + cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(session, modelSupplier), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner()); updated.updateScopes(scopes); } @Override public Map> getAttributes() { if (updated != null) return updated.getAttributes(); - return cached.getAttributes(modelSupplier); + return cached.getAttributes(session, modelSupplier); } @Override public String getSingleAttribute(String name) { if (updated != null) return updated.getSingleAttribute(name); - List values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList()); + List values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList()); if (values.isEmpty()) { return null; @@ -246,7 +249,7 @@ public class ResourceAdapter implements Resource, CachedModel { public List getAttribute(String name) { if (updated != null) return updated.getAttribute(name); - List values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList()); + List values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList()); if (values.isEmpty()) { return null; diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java index 1f208ed1832..3842d014d56 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java @@ -21,6 +21,7 @@ package org.keycloak.models.cache.infinispan.authorization.entities; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Scope; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.cache.infinispan.DefaultLazyLoader; import org.keycloak.models.cache.infinispan.LazyLoader; import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; @@ -83,8 +84,8 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer return this.logic; } - public Map getConfig(Supplier policy) { - return this.config.get(policy); + public Map getConfig(KeycloakSession session, Supplier policy) { + return this.config.get(session, policy); } public String getName() { @@ -95,16 +96,16 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer return this.description; } - public Set getAssociatedPoliciesIds(Supplier policy) { - return this.associatedPoliciesIds.get(policy); + public Set getAssociatedPoliciesIds(KeycloakSession session, Supplier policy) { + return this.associatedPoliciesIds.get(session, policy); } - public Set getResourcesIds(Supplier policy) { - return this.resourcesIds.get(policy); + public Set getResourcesIds(KeycloakSession session, Supplier policy) { + return this.resourcesIds.get(session, policy); } - public Set getScopesIds(Supplier policy) { - return this.scopesIds.get(policy); + public Set getScopesIds(KeycloakSession session, Supplier policy) { + return this.scopesIds.get(session, policy); } public String getResourceServerId() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java index ada982feee4..4b18f9caede 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java @@ -21,6 +21,7 @@ package org.keycloak.models.cache.infinispan.authorization.entities; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Scope; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.cache.infinispan.DefaultLazyLoader; import org.keycloak.models.cache.infinispan.LazyLoader; import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; @@ -75,8 +76,8 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ return this.displayName; } - public Set getUris(Supplier source) { - return this.uris.get(source); + public Set getUris(KeycloakSession session, Supplier source) { + return this.uris.get(session, source); } public String getType() { @@ -99,11 +100,11 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ return this.resourceServerId; } - public Set getScopesIds(Supplier source) { - return this.scopesIds.get(source); + public Set getScopesIds(KeycloakSession session, Supplier source) { + return this.scopesIds.get(session, source); } - public Map> getAttributes(Supplier source) { - return attributes.get(source); + public Map> getAttributes(KeycloakSession session, Supplier source) { + return attributes.get(session, source); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java index 723023f1eb1..a65a97a0db9 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java @@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan.entities; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel.Type; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.cache.infinispan.DefaultLazyLoader; @@ -42,7 +43,6 @@ public class CachedGroup extends AbstractRevisioned implements InRealm { private final LazyLoader> attributes; private final LazyLoader> roleMappings; private final LazyLoader> subGroups; - private final LazyLoader subGroupsCount; private final Type type; public CachedGroup(Long revision, RealmModel realm, GroupModel group) { @@ -53,7 +53,6 @@ public class CachedGroup extends AbstractRevisioned implements InRealm { this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new); this.roleMappings = new DefaultLazyLoader<>(source -> source.getRoleMappingsStream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet); this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroupsStream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet); - this.subGroupsCount = new DefaultLazyLoader<>(GroupModel::getSubGroupsCount, () -> 0L); this.type = group.getType(); } @@ -61,16 +60,16 @@ public class CachedGroup extends AbstractRevisioned implements InRealm { return realm; } - public MultivaluedHashMap getAttributes(Supplier group) { - return attributes.get(group); + public MultivaluedHashMap getAttributes(KeycloakSession session, Supplier group) { + return attributes.get(session, group); } - public Set getRoleMappings(Supplier group) { + public Set getRoleMappings(KeycloakSession session, Supplier group) { // it may happen that groups were not loaded before so we don't actually need to invalidate entries in the cache if (group == null) { return Collections.emptySet(); } - return roleMappings.get(group); + return roleMappings.get(session, group); } public String getName() { @@ -81,12 +80,8 @@ public class CachedGroup extends AbstractRevisioned implements InRealm { return parentId; } - public Set getSubGroups(Supplier group) { - return subGroups.get(group); - } - - public Long getSubGroupsCount(Supplier group) { - return subGroupsCount.get(group); + public Set getSubGroups(KeycloakSession session, Supplier group) { + return subGroups.get(session, group); } public Type getType() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 69580367ace..57a680a7e49 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -40,6 +40,7 @@ import org.keycloak.models.CibaConfig; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelException; import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.OTPPolicy; @@ -526,16 +527,16 @@ public class CachedRealm extends AbstractExtendableRevisioned { return accessCodeLifespanLogin; } - public OAuth2DeviceConfig getOAuth2DeviceConfig(Supplier modelSupplier) { - return deviceConfig.get(modelSupplier); + public OAuth2DeviceConfig getOAuth2DeviceConfig(KeycloakSession session, Supplier modelSupplier) { + return deviceConfig.get(session, modelSupplier); } - public CibaConfig getCibaConfig(Supplier modelSupplier) { - return cibaConfig.get(modelSupplier); + public CibaConfig getCibaConfig(KeycloakSession session, Supplier modelSupplier) { + return cibaConfig.get(session, modelSupplier); } - public ParConfig getParConfig(Supplier modelSupplier) { - return parConfig.get(modelSupplier); + public ParConfig getParConfig(KeycloakSession session, Supplier modelSupplier) { + return parConfig.get(session, modelSupplier); } public int getActionTokenGeneratedByAdminLifespan() { @@ -702,12 +703,12 @@ public class CachedRealm extends AbstractExtendableRevisioned { return defaultGroups; } - public List getDefaultDefaultClientScopes(Supplier modelSupplier) { - return defaultDefaultClientScopes.get(modelSupplier); + public List getDefaultDefaultClientScopes(KeycloakSession session, Supplier modelSupplier) { + return defaultDefaultClientScopes.get(session, modelSupplier); } - public List getOptionalDefaultClientScopes(Supplier modelSupplier) { - return optionalDefaultClientScopes.get(modelSupplier); + public List getOptionalDefaultClientScopes(KeycloakSession session, Supplier modelSupplier) { + return optionalDefaultClientScopes.get(session, modelSupplier); } public List getAuthenticationFlowList() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java index 13684175058..49551edb41a 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java @@ -18,6 +18,7 @@ package org.keycloak.models.cache.infinispan.entities; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.cache.infinispan.DefaultLazyLoader; @@ -73,7 +74,7 @@ public class CachedRole extends AbstractRevisioned implements InRealm { return composites; } - public MultivaluedHashMap getAttributes(Supplier roleModel) { - return attributes.get(roleModel); + public MultivaluedHashMap getAttributes(KeycloakSession session, Supplier roleModel) { + return attributes.get(session, roleModel); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java index eb47b5dfbad..064bfae2a6a 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java @@ -20,6 +20,7 @@ package org.keycloak.models.cache.infinispan.entities; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.credential.CredentialModel; import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; @@ -83,11 +84,11 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm return eagerLoadedAttributes.getFirst(UserModel.USERNAME); } - public String getFirstAttribute(String name, Supplier userModel) { + public String getFirstAttribute(KeycloakSession session, String name, Supplier userModel) { if(eagerLoadedAttributes.containsKey(name)) return eagerLoadedAttributes.getFirst(name); else - return this.lazyLoadedAttributes.get(userModel).getFirst(name); + return this.lazyLoadedAttributes.get(session, userModel).getFirst(name); } public Long getCreatedTimestamp() { @@ -106,16 +107,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm return enabled; } - public MultivaluedHashMap getAttributes(Supplier userModel) { - return lazyLoadedAttributes.get(userModel); + public MultivaluedHashMap getAttributes(KeycloakSession session, Supplier userModel) { + return lazyLoadedAttributes.get(session, userModel); } - public Set getRequiredActions(Supplier userModel) { - return this.requiredActions.get(userModel); + public Set getRequiredActions(KeycloakSession session, Supplier userModel) { + return this.requiredActions.get(session, userModel); } - public Set getRoleMappings(Supplier userModel) { - return roleMappings.get(userModel); + public Set getRoleMappings(KeycloakSession session, Supplier userModel) { + return roleMappings.get(session, userModel); } public String getFederationLink() { @@ -126,17 +127,17 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm return serviceAccountClientLink; } - public Set getGroups(Supplier userModel) { - return groups.get(userModel); + public Set getGroups(KeycloakSession session, Supplier userModel) { + return groups.get(session, userModel); } public int getNotBefore() { return notBefore; } - public List getStoredCredentials(Supplier userModel) { + public List getStoredCredentials(KeycloakSession session, Supplier userModel) { // clone the credential model before returning it, so that modifications don't pollute the cache - return storedCredentials.get(userModel).stream().map(CredentialModel::shallowClone).collect(Collectors.toList()); + return storedCredentials.get(session, userModel).stream().map(CredentialModel::shallowClone).collect(Collectors.toList()); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganization.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganization.java index 52fb14bcba5..c22aa7f2f74 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganization.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/CachedOrganization.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OrganizationDomainModel; import org.keycloak.models.OrganizationModel; import org.keycloak.models.RealmModel; @@ -80,8 +81,8 @@ public class CachedOrganization extends AbstractRevisioned implements InRealm { return enabled; } - public MultivaluedHashMap getAttributes(Supplier organizationModel) { - return attributes.get(organizationModel); + public MultivaluedHashMap getAttributes(KeycloakSession session, Supplier organizationModel) { + return attributes.get(session, organizationModel); } public Stream getDomains() { diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java index dc1649d7db4..e664aff38ab 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/InfinispanOrganizationProvider.java @@ -107,7 +107,7 @@ public class InfinispanOrganizationProvider implements OrganizationProvider { } else if (managedOrganizations.containsKey(id)) { return managedOrganizations.get(id); } - OrganizationAdapter adapter = new OrganizationAdapter(cached, () -> getDelegate(), this); + OrganizationAdapter adapter = new OrganizationAdapter(session, cached, this::getDelegate, this); managedOrganizations.put(id, adapter); return adapter; } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java index 14d4b9e6cc1..999cec601c9 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/organization/OrganizationAdapter.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OrganizationDomainModel; import org.keycloak.models.OrganizationModel; import org.keycloak.models.UserModel; @@ -32,11 +33,13 @@ public class OrganizationAdapter implements OrganizationModel { private volatile boolean invalidated; private volatile OrganizationModel updated; private final Supplier modelSupplier; + private final KeycloakSession session; private final CachedOrganization cached; private final Supplier delegate; private final InfinispanOrganizationProvider organizationCache; - public OrganizationAdapter(CachedOrganization cached, Supplier delegate, InfinispanOrganizationProvider organizationCache) { + public OrganizationAdapter(KeycloakSession session, CachedOrganization cached, Supplier delegate, InfinispanOrganizationProvider organizationCache) { + this.session = session; this.cached = cached; this.delegate = delegate; this.organizationCache = organizationCache; @@ -136,7 +139,7 @@ public class OrganizationAdapter implements OrganizationModel { @Override public Map> getAttributes() { if (isUpdated()) return updated.getAttributes(); - return cached.getAttributes(modelSupplier); + return cached.getAttributes(session, modelSupplier); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java index e55bb7fe5a2..98c490486fd 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java @@ -44,7 +44,7 @@ public class HasRolePredicate implements Predicate public boolean test(Map.Entry entry) { Object value = entry.getValue(); return (value instanceof CachedRole cachedRole && cachedRole.getComposites().contains(role)) || - (value instanceof CachedGroup cachedGroup && cachedGroup.getRoleMappings(null).contains(role)) || + (value instanceof CachedGroup cachedGroup && cachedGroup.getRoleMappings(null, null).contains(role)) || (value instanceof RoleQuery roleQuery && roleQuery.getRoles().contains(role)) || (value instanceof CachedClient cachedClient && cachedClient.getScope().contains(role)) || (value instanceof CachedClientScope cachedClientScope && cachedClientScope.getScope().contains(role)); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java index 9cf3ca25e52..d998866a5c5 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java @@ -22,6 +22,7 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import org.keycloak.authorization.AdminPermissionsSchema; +import org.keycloak.authorization.policy.provider.PartialEvaluationStorageProvider; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; @@ -43,6 +44,7 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import jakarta.persistence.LockModeType; +import org.keycloak.storage.UserStoragePrivateUtil; import static java.util.Optional.ofNullable; import static org.keycloak.common.util.CollectionUtil.collectionEquals; @@ -161,7 +163,7 @@ public class GroupAdapter implements GroupModel , JpaModel { predicates.add(builder.like(builder.lower(root.get("name")), builder.lower(builder.literal("%" + search + "%")))); } - predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root)); + predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, (PartialEvaluationStorageProvider) UserStoragePrivateUtil.userLocalStorage(session), realm, builder, queryBuilder, root)); queryBuilder.where(predicates.toArray(new Predicate[0])); queryBuilder.orderBy(builder.asc(root.get("name"))); @@ -186,7 +188,7 @@ public class GroupAdapter implements GroupModel , JpaModel { predicates.add(builder.equal(root.get("realm"), realm.getId())); predicates.add(builder.equal(root.get("type"), Type.REALM.intValue())); predicates.add(builder.equal(root.get("parentId"), group.getId())); - predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, realm, builder, queryBuilder, root)); + predicates.addAll(AdminPermissionsSchema.SCHEMA.applyAuthorizationFilters(session, AdminPermissionsSchema.GROUPS, (PartialEvaluationStorageProvider) UserStoragePrivateUtil.userLocalStorage(session), realm, builder, queryBuilder, root)); queryBuilder.where(predicates.toArray(new Predicate[0])); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java index b627e53b2df..68d5eaf4c7e 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/AdminPermissionsSchema.java @@ -99,6 +99,7 @@ public class AdminPermissionsSchema extends AuthorizationSchema { public static final ResourceType GROUPS = new ResourceType(GROUPS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, MANAGE_MEMBERSHIP, MANAGE_MEMBERS, VIEW_MEMBERS, IMPERSONATE_MEMBERS)); public static final ResourceType ROLES = new ResourceType(ROLES_RESOURCE_TYPE, Set.of(MAP_ROLE, MAP_ROLE_CLIENT_SCOPE, MAP_ROLE_COMPOSITE)); public static final ResourceType USERS = new ResourceType(USERS_RESOURCE_TYPE, Set.of(MANAGE, VIEW, IMPERSONATE, MAP_ROLES, MANAGE_GROUP_MEMBERSHIP), Map.of(VIEW, Set.of(VIEW_MEMBERS), MANAGE, Set.of(MANAGE_MEMBERS), IMPERSONATE, Set.of(IMPERSONATE_MEMBERS)), GROUPS.getType()); + private static final String SKIP_EVALUATION = "kc.authz.fgap.skip"; public static final AdminPermissionsSchema SCHEMA = new AdminPermissionsSchema(); private final PartialEvaluator partialEvaluator = new PartialEvaluator(); @@ -476,4 +477,40 @@ public class AdminPermissionsSchema extends AuthorizationSchema { return aliases; } + + /** + *

Disables authorization and evaluation of permissions for realm resource types when executing the given {@code runnable} + * in the context of the given {@code session}. + * + *

This method should be used whenever a code block should be executed without any evaluation or filtering based on + * the permissions set to a realm. For instance, when caching realm resources where access enforcement does not apply. + * + * @param session the session. If {@code null}, authorization is enabled when executing the code block + * @param runnable the runnable to execute + */ + public static void runWithoutAuthorization(KeycloakSession session, Runnable runnable) { + if (isSkipEvaluation(session)) { + runnable.run(); + return; + } + + try { + session.setAttribute(SKIP_EVALUATION, Boolean.TRUE.toString()); + runnable.run(); + } finally { + session.removeAttribute(SKIP_EVALUATION); + } + } + + /** + * Returns if authorization is disabled in the context of the given {@code session} at the moment that this method is called. + * + * @param session the session + * @return {@code true} if authorization is disabled. Otherwise, returns {@code false}. + * Otherwise, {@code false}. + * @see AdminPermissionsSchema#runWithoutAuthorization(KeycloakSession, Runnable) + */ + public static boolean isSkipEvaluation(KeycloakSession session) { + return session == null || Boolean.parseBoolean(session.getAttributeOrDefault(SKIP_EVALUATION, Boolean.FALSE.toString())); + } } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java index a6480841968..c1a1884686a 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/PartialEvaluator.java @@ -17,6 +17,8 @@ package org.keycloak.authorization; +import static org.keycloak.authorization.AdminPermissionsSchema.isSkipEvaluation; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -220,6 +222,10 @@ public class PartialEvaluator { } private boolean shouldSkipPartialEvaluation(KeycloakSession session, UserModel user, ResourceType resourceType) { + if (isSkipEvaluation(session)) { + return true; + } + if (user == null) { return true; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index 9e1ca4844d4..7cef9ab9052 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -1108,7 +1108,7 @@ public class UserResource { @QueryParam("max") Integer maxResults, @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { auth.users().requireView(user); - return user.getGroupsStream(search, firstResult, maxResults).map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation)); + return user.getGroupsStream(search, firstResult, maxResults).filter(auth.groups()::canView).map(g -> ModelToRepresentation.toRepresentation(g, !briefRepresentation)); } @GET diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java index 3a9c72ae036..704b9753e31 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/authz/fgap/GroupResourceTypeFilteringTest.java @@ -19,8 +19,10 @@ package org.keycloak.tests.admin.authz.fgap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.keycloak.authorization.AdminPermissionsSchema.GROUPS_RESOURCE_TYPE; +import static org.keycloak.authorization.AdminPermissionsSchema.USERS_RESOURCE_TYPE; import static org.keycloak.authorization.AdminPermissionsSchema.VIEW; import java.util.List; @@ -40,7 +42,9 @@ import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.testframework.annotations.InjectAdminClient; +import org.keycloak.testframework.annotations.InjectUser; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.realm.ManagedUser; import org.keycloak.testframework.util.ApiUtil; @KeycloakIntegrationTest(config = KeycloakAdminPermissionsServerConfig.class) @@ -49,6 +53,9 @@ public class GroupResourceTypeFilteringTest extends AbstractPermissionTest { @InjectAdminClient(mode = InjectAdminClient.Mode.MANAGED_REALM, client = "myclient", user = "myadmin") Keycloak realmAdminClient; + @InjectUser(ref = "alice") + ManagedUser userAlice; + @BeforeEach public void onBeforeEach() { for (int i = 0; i < 50; i++) { @@ -123,21 +130,59 @@ public class GroupResourceTypeFilteringTest extends AbstractPermissionTest { UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId()); createAllPermission(client, GROUPS_RESOURCE_TYPE, policy, Set.of(VIEW)); - List search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1); + List search = realmAdminClient.realm(realm.getName()).groups().groups("group-0", -1, -1); assertFalse(search.isEmpty()); assertEquals(1, search.size()); - GroupRepresentation group = search.get(0); - assertEquals(1, group.getSubGroups().size()); - GroupRepresentation subGroup = group.getSubGroups().get(0); - assertEquals("subgroup-0.0", subGroup.getName()); + GroupRepresentation parentGroup = search.get(0); + assertEquals(5, parentGroup.getSubGroups().size()); + assertEquals(5, parentGroup.getSubGroupCount()); + GroupRepresentation subGroup = parentGroup.getSubGroups().stream().filter(group -> group.getName().equals("subgroup-0.0")).findFirst().orElse(null); + assertNotNull(subGroup); UserPolicyRepresentation notMyAdminPolicy = createUserPolicy(Logic.NEGATIVE, realm, client,"Not My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId()); createPermission(client, subGroup.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW), notMyAdminPolicy); search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1); assertTrue(search.isEmpty()); - List subGroups = realmAdminClient.realm(realm.getName()).groups().group(group.getId()).getSubGroups(-1, -1, false); + List subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups(-1, -1, false); + assertEquals(4, subGroups.size()); assertTrue(subGroups.stream().map(GroupRepresentation::getId).noneMatch(subGroup.getId()::equals)); + search = realmAdminClient.realm(realm.getName()).groups().groups("group-0", -1, -1); + assertFalse(search.isEmpty()); + parentGroup = search.get(0); + assertEquals(4, parentGroup.getSubGroups().size()); + assertEquals(4, parentGroup.getSubGroupCount()); + + subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups(subGroup.getName(), true, -1, -1, true); + assertTrue(subGroups.isEmpty()); + subGroups = realmAdminClient.realm(realm.getName()).groups().group(parentGroup.getId()).getSubGroups("subgroup-0.1", true, -1, -1, true); + assertEquals(1, subGroups.size()); + + assertEquals(5, realm.admin().groups().group(parentGroup.getId()).getSubGroups(-1, -1, false).size()); + assertEquals(5, realm.admin().groups().group(parentGroup.getId()).getSubGroups(null, false, -1, -1, false).size()); + assertEquals(5, realm.admin().groups().group(parentGroup.getId()).toRepresentation().getSubGroupCount()); + } + + @Test + public void testGetUserGroups() { + GroupRepresentation parentGroup = realm.admin().groups().groups("group-0", -1, -1).get(0); + GroupRepresentation subGroup = realm.admin().groups().groups("subgroup-1.0", -1, -1).get(0); + + userAlice.admin().joinGroup(parentGroup.getId()); + userAlice.admin().joinGroup(subGroup.getId()); + + List groups = userAlice.admin().groups(); + assertEquals(2, groups.size()); + + UserPolicyRepresentation policy = createUserPolicy(realm, client,"Only My Admin User Policy", realm.admin().users().search("myadmin").get(0).getId()); + createPermission(client, userAlice.getId(), USERS_RESOURCE_TYPE, Set.of(VIEW), policy); + createPermission(client, subGroup.getId(), GROUPS_RESOURCE_TYPE, Set.of(VIEW), policy); + + groups = realmAdminClient.realm(realm.getName()).users().get(userAlice.getId()).groups(); + assertEquals(1, groups.size()); + + groups = userAlice.admin().groups(); + assertEquals(2, groups.size()); } }