Do not cache partial results when FGAP is enabled

Closes #38705

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor
2025-04-08 03:22:22 -03:00
committed by GitHub
parent f88747eaed
commit be880ae204
23 changed files with 241 additions and 128 deletions

View File

@@ -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<S, D> implements LazyLoader<S, D> {
}
@Override
public D get(Supplier<S> sourceSupplier) {
public D get(KeycloakSession session, Supplier<S> 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;
}

View File

@@ -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<String> getAttributeStream(String name) {
if (isUpdated()) return updated.getAttributeStream(name);
List<String> values = cached.getAttributes(modelSupplier).get(name);
List<String> 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<String, List<String>> 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<RoleModel> getRoleMappingsStream() {
if (isUpdated()) return updated.getRoleMappingsStream();
Set<RoleModel> 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<GroupModel> getSubGroupsStream() {
if (isUpdated()) return updated.getSubGroupsStream();
Set<GroupModel> 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

View File

@@ -18,6 +18,8 @@ package org.keycloak.models.cache.infinispan;
import java.util.function.Supplier;
import org.keycloak.models.KeycloakSession;
/**
* <p>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<S, D> {
* 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<S> source);
D get(KeycloakSession session, Supplier<S> source);
}

View File

@@ -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<ClientScopeModel> getDefaultClientScopesStream(boolean defaultScope) {
if (isUpdated()) return updated.getDefaultClientScopesStream(defaultScope);
List<String> clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(modelSupplier) : cached.getOptionalDefaultClientScopes(modelSupplier);
List<String> clientScopeIds = defaultScope ? cached.getDefaultDefaultClientScopes(session, modelSupplier) : cached.getOptionalDefaultClientScopes(session, modelSupplier);
return clientScopeIds.stream()
.map(scope -> cacheSession.getClientScopeById(this, scope))
.filter(Objects::nonNull);

View File

@@ -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<RoleModel> composites;
private final Supplier<RoleModel> 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<String> result = cached.getAttributes(modelSupplier).get(name);
List<String> 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() {

View File

@@ -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<String> getAttributeStream(String name) {
if (updated != null) return updated.getAttributeStream(name);
List<String> result = cached.getAttributes(modelSupplier).get(name);
List<String> result = cached.getAttributes(keycloakSession, modelSupplier).get(name);
return (result == null) ? Stream.empty() : result.stream();
}
@Override
public Map<String, List<String>> getAttributes() {
if (updated != null) return updated.getAttributes();
return cached.getAttributes(modelSupplier);
return cached.getAttributes(keycloakSession, modelSupplier);
}
@Override
public Stream<String> 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<CredentialModel> 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<CredentialModel> 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<RoleModel> getRoleMappingsStream() {
if (updated != null) return updated.getRoleMappingsStream();
Set<RoleModel> 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<GroupModel> 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

View File

@@ -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<Policy> {
private final Supplier<Policy> 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<Policy> {
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<Policy> {
@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<Policy> {
@Override
public Map<String, String> getConfig() {
if (isUpdated()) return updated.getConfig();
return cached.getConfig(modelSupplier);
return cached.getConfig(session, modelSupplier);
}
@Override
public void setConfig(Map<String, String> 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<Policy> {
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<Policy> {
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<Policy> {
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<Policy> {
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<Policy> {
@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<Policy> {
getDelegateForUpdate();
HashSet<String> 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<Policy> {
getDelegateForUpdate();
HashSet<String> 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<Policy> {
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<Policy> {
@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);
}

View File

@@ -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<Resource> {
private final Supplier<Resource> 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<Resource> {
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<Resource> {
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<Resource> {
@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<Resource> {
@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<Resource> {
@Override
public Set<String> getUris() {
if (isUpdated()) return updated.getUris();
return cached.getUris(modelSupplier);
return cached.getUris(session, modelSupplier);
}
@Override
public void updateUris(Set<String> 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<Resource> {
@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<Resource> {
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<Resource> {
@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<Resource> {
}
}
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<String, List<String>> 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<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
List<String> values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList());
if (values.isEmpty()) {
return null;
@@ -246,7 +249,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
public List<String> getAttribute(String name) {
if (updated != null) return updated.getAttribute(name);
List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
List<String> values = cached.getAttributes(session, modelSupplier).getOrDefault(name, Collections.emptyList());
if (values.isEmpty()) {
return null;

View File

@@ -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<String, String> getConfig(Supplier<Policy> policy) {
return this.config.get(policy);
public Map<String, String> getConfig(KeycloakSession session, Supplier<Policy> 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<String> getAssociatedPoliciesIds(Supplier<Policy> policy) {
return this.associatedPoliciesIds.get(policy);
public Set<String> getAssociatedPoliciesIds(KeycloakSession session, Supplier<Policy> policy) {
return this.associatedPoliciesIds.get(session, policy);
}
public Set<String> getResourcesIds(Supplier<Policy> policy) {
return this.resourcesIds.get(policy);
public Set<String> getResourcesIds(KeycloakSession session, Supplier<Policy> policy) {
return this.resourcesIds.get(session, policy);
}
public Set<String> getScopesIds(Supplier<Policy> policy) {
return this.scopesIds.get(policy);
public Set<String> getScopesIds(KeycloakSession session, Supplier<Policy> policy) {
return this.scopesIds.get(session, policy);
}
public String getResourceServerId() {

View File

@@ -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<String> getUris(Supplier<Resource> source) {
return this.uris.get(source);
public Set<String> getUris(KeycloakSession session, Supplier<Resource> 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<String> getScopesIds(Supplier<Resource> source) {
return this.scopesIds.get(source);
public Set<String> getScopesIds(KeycloakSession session, Supplier<Resource> source) {
return this.scopesIds.get(session, source);
}
public Map<String, List<String>> getAttributes(Supplier<Resource> source) {
return attributes.get(source);
public Map<String, List<String>> getAttributes(KeycloakSession session, Supplier<Resource> source) {
return attributes.get(session, source);
}
}

View File

@@ -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<GroupModel, MultivaluedHashMap<String, String>> attributes;
private final LazyLoader<GroupModel, Set<String>> roleMappings;
private final LazyLoader<GroupModel, Set<String>> subGroups;
private final LazyLoader<GroupModel, Long> 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<String, String> getAttributes(Supplier<GroupModel> group) {
return attributes.get(group);
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<GroupModel> group) {
return attributes.get(session, group);
}
public Set<String> getRoleMappings(Supplier<GroupModel> group) {
public Set<String> getRoleMappings(KeycloakSession session, Supplier<GroupModel> 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<String> getSubGroups(Supplier<GroupModel> group) {
return subGroups.get(group);
}
public Long getSubGroupsCount(Supplier<GroupModel> group) {
return subGroupsCount.get(group);
public Set<String> getSubGroups(KeycloakSession session, Supplier<GroupModel> group) {
return subGroups.get(session, group);
}
public Type getType() {

View File

@@ -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<RealmModel> modelSupplier) {
return deviceConfig.get(modelSupplier);
public OAuth2DeviceConfig getOAuth2DeviceConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
return deviceConfig.get(session, modelSupplier);
}
public CibaConfig getCibaConfig(Supplier<RealmModel> modelSupplier) {
return cibaConfig.get(modelSupplier);
public CibaConfig getCibaConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
return cibaConfig.get(session, modelSupplier);
}
public ParConfig getParConfig(Supplier<RealmModel> modelSupplier) {
return parConfig.get(modelSupplier);
public ParConfig getParConfig(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
return parConfig.get(session, modelSupplier);
}
public int getActionTokenGeneratedByAdminLifespan() {
@@ -702,12 +703,12 @@ public class CachedRealm extends AbstractExtendableRevisioned {
return defaultGroups;
}
public List<String> getDefaultDefaultClientScopes(Supplier<RealmModel> modelSupplier) {
return defaultDefaultClientScopes.get(modelSupplier);
public List<String> getDefaultDefaultClientScopes(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
return defaultDefaultClientScopes.get(session, modelSupplier);
}
public List<String> getOptionalDefaultClientScopes(Supplier<RealmModel> modelSupplier) {
return optionalDefaultClientScopes.get(modelSupplier);
public List<String> getOptionalDefaultClientScopes(KeycloakSession session, Supplier<RealmModel> modelSupplier) {
return optionalDefaultClientScopes.get(session, modelSupplier);
}
public List<AuthenticationFlowModel> getAuthenticationFlowList() {

View File

@@ -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<String, String> getAttributes(Supplier<RoleModel> roleModel) {
return attributes.get(roleModel);
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<RoleModel> roleModel) {
return attributes.get(session, roleModel);
}
}

View File

@@ -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> userModel) {
public String getFirstAttribute(KeycloakSession session, String name, Supplier<UserModel> 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<String, String> getAttributes(Supplier<UserModel> userModel) {
return lazyLoadedAttributes.get(userModel);
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<UserModel> userModel) {
return lazyLoadedAttributes.get(session, userModel);
}
public Set<String> getRequiredActions(Supplier<UserModel> userModel) {
return this.requiredActions.get(userModel);
public Set<String> getRequiredActions(KeycloakSession session, Supplier<UserModel> userModel) {
return this.requiredActions.get(session, userModel);
}
public Set<String> getRoleMappings(Supplier<UserModel> userModel) {
return roleMappings.get(userModel);
public Set<String> getRoleMappings(KeycloakSession session, Supplier<UserModel> userModel) {
return roleMappings.get(session, userModel);
}
public String getFederationLink() {
@@ -126,17 +127,17 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return serviceAccountClientLink;
}
public Set<String> getGroups(Supplier<UserModel> userModel) {
return groups.get(userModel);
public Set<String> getGroups(KeycloakSession session, Supplier<UserModel> userModel) {
return groups.get(session, userModel);
}
public int getNotBefore() {
return notBefore;
}
public List<CredentialModel> getStoredCredentials(Supplier<UserModel> userModel) {
public List<CredentialModel> getStoredCredentials(KeycloakSession session, Supplier<UserModel> 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());
}
}

View File

@@ -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<String, String> getAttributes(Supplier<OrganizationModel> organizationModel) {
return attributes.get(organizationModel);
public MultivaluedHashMap<String, String> getAttributes(KeycloakSession session, Supplier<OrganizationModel> organizationModel) {
return attributes.get(session, organizationModel);
}
public Stream<OrganizationDomainModel> getDomains() {

View File

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

View File

@@ -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<OrganizationModel> modelSupplier;
private final KeycloakSession session;
private final CachedOrganization cached;
private final Supplier<OrganizationProvider> delegate;
private final InfinispanOrganizationProvider organizationCache;
public OrganizationAdapter(CachedOrganization cached, Supplier<OrganizationProvider> delegate, InfinispanOrganizationProvider organizationCache) {
public OrganizationAdapter(KeycloakSession session, CachedOrganization cached, Supplier<OrganizationProvider> 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<String, List<String>> getAttributes() {
if (isUpdated()) return updated.getAttributes();
return cached.getAttributes(modelSupplier);
return cached.getAttributes(session, modelSupplier);
}
@Override

View File

@@ -44,7 +44,7 @@ public class HasRolePredicate implements Predicate<Map.Entry<String, Revisioned>
public boolean test(Map.Entry<String, Revisioned> 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));

View File

@@ -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<GroupEntity> {
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<GroupEntity> {
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]));

View File

@@ -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;
}
/**
* <p>Disables authorization and evaluation of permissions for realm resource types when executing the given {@code runnable}
* in the context of the given {@code session}.
*
* <p>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()));
}
}

View File

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

View File

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

View File

@@ -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<GroupRepresentation> search = realmAdminClient.realm(realm.getName()).groups().groups("subgroup-0.0", -1, -1);
List<GroupRepresentation> 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<GroupRepresentation> subGroups = realmAdminClient.realm(realm.getName()).groups().group(group.getId()).getSubGroups(-1, -1, false);
List<GroupRepresentation> 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<GroupRepresentation> 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());
}
}