[KEYCLOAK-7950] - Fixes user pagination when using filtering users members of groups

This commit is contained in:
Pedro Igor 2018-09-28 16:53:23 -03:00
parent 17a1a33987
commit b4b3527df7
45 changed files with 1183 additions and 839 deletions

View file

@ -33,6 +33,14 @@ public class MultivaluedHashMap<K, V> extends HashMap<K, List<V>>
public MultivaluedHashMap() { public MultivaluedHashMap() {
} }
public MultivaluedHashMap(Map<K, List<V>> map) {
if (map == null) {
throw new IllegalArgumentException("Map can not be null");
}
putAll(map);
}
public MultivaluedHashMap(MultivaluedHashMap<K, V> config) { public MultivaluedHashMap(MultivaluedHashMap<K, V> config) {
addAll(config); addAll(config);
} }

View file

@ -0,0 +1,47 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 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.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class DefaultLazyLoader<S, D> implements LazyLoader<S, D> {
private final Function<S, D> loader;
private Supplier<D> fallback;
private D data;
public DefaultLazyLoader(Function<S, D> loader, Supplier<D> fallback) {
this.loader = loader;
this.fallback = fallback;
}
@Override
public D get(Supplier<S> sourceSupplier) {
if (data == null) {
S source = sourceSupplier.get();
data = source == null ? fallback.get() : this.loader.apply(source);
}
return data;
}
}

View file

@ -29,29 +29,33 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class GroupAdapter implements GroupModel { public class GroupAdapter implements GroupModel {
protected final CachedGroup cached;
protected final RealmCacheSession cacheSession;
protected final KeycloakSession keycloakSession;
protected final RealmModel realm;
private final Supplier<GroupModel> modelSupplier;
protected volatile GroupModel updated; protected volatile GroupModel updated;
protected CachedGroup cached;
protected RealmCacheSession cacheSession;
protected KeycloakSession keycloakSession;
protected RealmModel realm;
public GroupAdapter(CachedGroup cached, RealmCacheSession cacheSession, KeycloakSession keycloakSession, RealmModel realm) { public GroupAdapter(CachedGroup cached, RealmCacheSession cacheSession, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached; this.cached = cached;
this.cacheSession = cacheSession; this.cacheSession = cacheSession;
this.keycloakSession = keycloakSession; this.keycloakSession = keycloakSession;
this.realm = realm; this.realm = realm;
modelSupplier = this::getGroupModel;
} }
protected void getDelegateForUpdate() { protected void getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
cacheSession.registerGroupInvalidation(cached.getId()); cacheSession.registerGroupInvalidation(cached.getId());
updated = cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm); updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
} }
@ -128,19 +132,19 @@ public class GroupAdapter implements GroupModel {
@Override @Override
public String getFirstAttribute(String name) { public String getFirstAttribute(String name) {
if (isUpdated()) return updated.getFirstAttribute(name); if (isUpdated()) return updated.getFirstAttribute(name);
return cached.getAttributes().getFirst(name); return cached.getAttributes(modelSupplier).getFirst(name);
} }
@Override @Override
public List<String> getAttribute(String name) { public List<String> getAttribute(String name) {
List<String> values = cached.getAttributes().get(name); List<String> values = cached.getAttributes(modelSupplier).get(name);
if (values == null) return null; if (values == null) return null;
return values; return values;
} }
@Override @Override
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes() {
return cached.getAttributes(); return cached.getAttributes(modelSupplier);
} }
@Override @Override
@ -178,7 +182,7 @@ public class GroupAdapter implements GroupModel {
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
if (isUpdated()) return updated.hasRole(role); if (isUpdated()) return updated.hasRole(role);
if (cached.getRoleMappings().contains(role.getId())) return true; if (cached.getRoleMappings(modelSupplier).contains(role.getId())) return true;
Set<RoleModel> mappings = getRoleMappings(); Set<RoleModel> mappings = getRoleMappings();
for (RoleModel mapping: mappings) { for (RoleModel mapping: mappings) {
@ -197,7 +201,7 @@ public class GroupAdapter implements GroupModel {
public Set<RoleModel> getRoleMappings() { public Set<RoleModel> getRoleMappings() {
if (isUpdated()) return updated.getRoleMappings(); if (isUpdated()) return updated.getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>(); Set<RoleModel> roles = new HashSet<RoleModel>();
for (String id : cached.getRoleMappings()) { for (String id : cached.getRoleMappings(modelSupplier)) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm); RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
if (roleById == null) { if (roleById == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated // chance that role was removed, so just delegate to persistence and get user invalidated
@ -233,7 +237,7 @@ public class GroupAdapter implements GroupModel {
public Set<GroupModel> getSubGroups() { public Set<GroupModel> getSubGroups() {
if (isUpdated()) return updated.getSubGroups(); if (isUpdated()) return updated.getSubGroups();
Set<GroupModel> subGroups = new HashSet<>(); Set<GroupModel> subGroups = new HashSet<>();
for (String id : cached.getSubGroups()) { for (String id : cached.getSubGroups(modelSupplier)) {
GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm); GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
if (subGroup == null) { if (subGroup == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated // chance that role was removed, so just delegate to persistence and get user invalidated
@ -267,4 +271,8 @@ public class GroupAdapter implements GroupModel {
getDelegateForUpdate(); getDelegateForUpdate();
updated.removeChild(subGroup); updated.removeChild(subGroup);
} }
private GroupModel getGroupModel() {
return cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm);
}
} }

View file

@ -0,0 +1,40 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.cache.infinispan;
import java.util.function.Supplier;
/**
* <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.
*
* <p>The source does not need to worry about caching data but always fetch data as demanded. The way data will actually be cached is an implementation detail.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
* @see DefaultLazyLoader
*/
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 source the source from where data will be fetched.
* @return the data from source
*/
D get(Supplier<S> source);
}

View file

@ -35,30 +35,34 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UserAdapter implements CachedUserModel { public class UserAdapter implements CachedUserModel {
private final Supplier<UserModel> modelSupplier;
protected final CachedUser cached;
protected final UserCacheSession userProviderCache;
protected final KeycloakSession keycloakSession;
protected final RealmModel realm;
protected volatile UserModel updated; protected volatile UserModel updated;
protected CachedUser cached;
protected UserCacheSession userProviderCache;
protected KeycloakSession keycloakSession;
protected RealmModel realm;
public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) { public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached; this.cached = cached;
this.userProviderCache = userProvider; this.userProviderCache = userProvider;
this.keycloakSession = keycloakSession; this.keycloakSession = keycloakSession;
this.realm = realm; this.realm = realm;
this.modelSupplier = this::getUserModel;
} }
@Override @Override
public UserModel getDelegateForUpdate() { public UserModel getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
userProviderCache.registerUserInvalidation(realm, cached); userProviderCache.registerUserInvalidation(realm, cached);
updated = userProviderCache.getDelegate().getUserById(getId(), realm); updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
return updated; return updated;
@ -147,26 +151,26 @@ public class UserAdapter implements CachedUserModel {
@Override @Override
public String getFirstAttribute(String name) { public String getFirstAttribute(String name) {
if (updated != null) return updated.getFirstAttribute(name); if (updated != null) return updated.getFirstAttribute(name);
return cached.getAttributes().getFirst(name); return cached.getAttributes(modelSupplier).getFirst(name);
} }
@Override @Override
public List<String> getAttribute(String name) { public List<String> getAttribute(String name) {
if (updated != null) return updated.getAttribute(name); if (updated != null) return updated.getAttribute(name);
List<String> result = cached.getAttributes().get(name); List<String> result = cached.getAttributes(modelSupplier).get(name);
return (result == null) ? Collections.<String>emptyList() : result; return (result == null) ? Collections.<String>emptyList() : result;
} }
@Override @Override
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes() {
if (updated != null) return updated.getAttributes(); if (updated != null) return updated.getAttributes();
return cached.getAttributes(); return cached.getAttributes(modelSupplier);
} }
@Override @Override
public Set<String> getRequiredActions() { public Set<String> getRequiredActions() {
if (updated != null) return updated.getRequiredActions(); if (updated != null) return updated.getRequiredActions();
return cached.getRequiredActions(); return cached.getRequiredActions(modelSupplier);
} }
@Override @Override
@ -301,7 +305,7 @@ public class UserAdapter implements CachedUserModel {
@Override @Override
public boolean hasRole(RoleModel role) { public boolean hasRole(RoleModel role) {
if (updated != null) return updated.hasRole(role); if (updated != null) return updated.hasRole(role);
if (cached.getRoleMappings().contains(role.getId())) return true; if (cached.getRoleMappings(modelSupplier).contains(role.getId())) return true;
Set<RoleModel> mappings = getRoleMappings(); Set<RoleModel> mappings = getRoleMappings();
for (RoleModel mapping: mappings) { for (RoleModel mapping: mappings) {
@ -320,7 +324,7 @@ public class UserAdapter implements CachedUserModel {
public Set<RoleModel> getRoleMappings() { public Set<RoleModel> getRoleMappings() {
if (updated != null) return updated.getRoleMappings(); if (updated != null) return updated.getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>(); Set<RoleModel> roles = new HashSet<RoleModel>();
for (String id : cached.getRoleMappings()) { for (String id : cached.getRoleMappings(modelSupplier)) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm); RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
if (roleById == null) { if (roleById == null) {
// chance that role was removed, so just delete to persistence and get user invalidated // chance that role was removed, so just delete to persistence and get user invalidated
@ -343,7 +347,7 @@ public class UserAdapter implements CachedUserModel {
public Set<GroupModel> getGroups() { public Set<GroupModel> getGroups() {
if (updated != null) return updated.getGroups(); if (updated != null) return updated.getGroups();
Set<GroupModel> groups = new HashSet<GroupModel>(); Set<GroupModel> groups = new HashSet<GroupModel>();
for (String id : cached.getGroups()) { for (String id : cached.getGroups(modelSupplier)) {
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm); GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
if (groupModel == null) { if (groupModel == null) {
// chance that role was removed, so just delete to persistence and get user invalidated // chance that role was removed, so just delete to persistence and get user invalidated
@ -372,7 +376,7 @@ public class UserAdapter implements CachedUserModel {
@Override @Override
public boolean isMemberOf(GroupModel group) { public boolean isMemberOf(GroupModel group) {
if (updated != null) return updated.isMemberOf(group); if (updated != null) return updated.isMemberOf(group);
if (cached.getGroups().contains(group.getId())) return true; if (cached.getGroups(modelSupplier).contains(group.getId())) return true;
Set<GroupModel> roles = getGroups(); Set<GroupModel> roles = getGroups();
return RoleUtils.isMember(roles, group); return RoleUtils.isMember(roles, group);
} }
@ -391,7 +395,7 @@ public class UserAdapter implements CachedUserModel {
return getId().hashCode(); return getId().hashCode();
} }
private UserModel getUserModel() {
return userProviderCache.getDelegate().getUserById(cached.getId(), realm);
}
} }

View file

@ -21,6 +21,9 @@ import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; 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.cache.infinispan.authorization.entities.CachedPolicy; import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.Logic;
@ -30,27 +33,32 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class PolicyAdapter implements Policy, CachedModel<Policy> { public class PolicyAdapter implements Policy, CachedModel<Policy> {
protected CachedPolicy cached;
protected StoreFactoryCacheSession cacheSession; private final Supplier<Policy> modelSupplier;
protected final CachedPolicy cached;
protected final StoreFactoryCacheSession cacheSession;
protected Policy updated; protected Policy updated;
public PolicyAdapter(CachedPolicy cached, StoreFactoryCacheSession cacheSession) { public PolicyAdapter(CachedPolicy cached, StoreFactoryCacheSession cacheSession) {
this.cached = cached; this.cached = cached;
this.cacheSession = cacheSession; this.cacheSession = cacheSession;
this.modelSupplier = this::getPolicyModel;
} }
@Override @Override
public Policy getDelegateForUpdate() { public Policy getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId()); updated = modelSupplier.get();
String defaultResourceType = updated.getConfig().get("defaultResourceType"); String defaultResourceType = updated.getConfig().get("defaultResourceType");
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), defaultResourceType, cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), defaultResourceType, cached.getResourceServerId());
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
return updated; return updated;
@ -98,7 +106,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override @Override
public void setName(String name) { public void setName(String name) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), name, cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.setName(name); updated.setName(name);
} }
@ -142,15 +150,15 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override @Override
public Map<String, String> getConfig() { public Map<String, String> getConfig() {
if (isUpdated()) return updated.getConfig(); if (isUpdated()) return updated.getConfig();
return cached.getConfig(); return cached.getConfig(modelSupplier);
} }
@Override @Override
public void setConfig(Map<String, String> config) { public void setConfig(Map<String, String> config) {
getDelegateForUpdate(); getDelegateForUpdate();
if (config.containsKey("defaultResourceType") || cached.getConfig().containsKey("defaultResourceType")) { if (config.containsKey("defaultResourceType") || cached.getConfig(modelSupplier).containsKey("defaultResourceType")) {
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); 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(), cached.getScopesIds(), config.get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId());
} }
updated.setConfig(config); updated.setConfig(config);
@ -160,7 +168,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
public void removeConfig(String name) { public void removeConfig(String name) {
getDelegateForUpdate(); getDelegateForUpdate();
if (name.equals("defaultResourceType")) { if (name.equals("defaultResourceType")) {
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
} }
updated.removeConfig(name); updated.removeConfig(name);
@ -170,8 +178,8 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
public void putConfig(String name, String value) { public void putConfig(String name, String value) {
getDelegateForUpdate(); getDelegateForUpdate();
if (name.equals("defaultResourceType")) { if (name.equals("defaultResourceType")) {
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); 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(), cached.getScopesIds(), value, cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), value, cached.getResourceServerId());
} }
updated.putConfig(name, value); updated.putConfig(name, value);
} }
@ -192,40 +200,49 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override @Override
public Set<Policy> getAssociatedPolicies() { public Set<Policy> getAssociatedPolicies() {
if (isUpdated()) return updated.getAssociatedPolicies(); if (isUpdated()) {
return updated.getAssociatedPolicies().stream().map(policy -> new PolicyAdapter(cacheSession.createCachedPolicy(policy, policy.getId()), cacheSession)).collect(Collectors.toSet());
}
if (associatedPolicies != null) return associatedPolicies; if (associatedPolicies != null) return associatedPolicies;
associatedPolicies = new HashSet<>(); associatedPolicies = new HashSet<>();
for (String scopeId : cached.getAssociatedPoliciesIds()) { PolicyStore policyStore = cacheSession.getPolicyStore();
associatedPolicies.add(cacheSession.getPolicyStore().findById(scopeId, cached.getResourceServerId())); String resourceServerId = cached.getResourceServerId();
for (String id : cached.getAssociatedPoliciesIds(modelSupplier)) {
Policy policy = policyStore.findById(id, resourceServerId);
cacheSession.cachePolicy(policy);
associatedPolicies.add(policy);
} }
associatedPolicies = Collections.unmodifiableSet(associatedPolicies); return associatedPolicies = Collections.unmodifiableSet(associatedPolicies);
return associatedPolicies;
} }
protected Set<Resource> resources; protected Set<Resource> resources;
@Override @Override
public Set<Resource> getResources() { public Set<Resource> getResources() {
if (isUpdated()) return updated.getResources(); if (isUpdated()) return updated.getResources();
if (resources != null) return resources; if (resources != null) return resources;
resources = new HashSet<>(); resources = new HashSet<>();
for (String resourceId : cached.getResourcesIds()) { ResourceStore resourceStore = cacheSession.getResourceStore();
resources.add(cacheSession.getResourceStore().findById(resourceId, cached.getResourceServerId())); for (String resourceId : cached.getResourcesIds(modelSupplier)) {
String resourceServerId = cached.getResourceServerId();
Resource resource = resourceStore.findById(resourceId, resourceServerId);
cacheSession.cacheResource(resource);
resources.add(resource);
} }
resources = Collections.unmodifiableSet(resources); return resources = Collections.unmodifiableSet(resources);
return resources;
} }
@Override @Override
public void addScope(Scope scope) { public void addScope(Scope scope) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.addScope(scope); updated.addScope(scope);
} }
@Override @Override
public void removeScope(Scope scope) { public void removeScope(Scope scope) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), new HashSet<>(Arrays.asList(scope.getId())), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.removeScope(scope); updated.removeScope(scope);
} }
@ -248,7 +265,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate(); getDelegateForUpdate();
HashSet<String> resources = new HashSet<>(); HashSet<String> resources = new HashSet<>();
resources.add(resource.getId()); resources.add(resource.getId());
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.addResource(resource); updated.addResource(resource);
} }
@ -258,11 +275,16 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate(); getDelegateForUpdate();
HashSet<String> resources = new HashSet<>(); HashSet<String> resources = new HashSet<>();
resources.add(resource.getId()); resources.add(resource.getId());
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.removeResource(resource); updated.removeResource(resource);
} }
@Override
public boolean isFetched(String association) {
return modelSupplier.get().isFetched(association);
}
protected Set<Scope> scopes; protected Set<Scope> scopes;
@Override @Override
@ -270,11 +292,14 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
if (isUpdated()) return updated.getScopes(); if (isUpdated()) return updated.getScopes();
if (scopes != null) return scopes; if (scopes != null) return scopes;
scopes = new HashSet<>(); scopes = new HashSet<>();
for (String scopeId : cached.getScopesIds()) { ScopeStore scopeStore = cacheSession.getScopeStore();
scopes.add(cacheSession.getScopeStore().findById(scopeId, cached.getResourceServerId())); String resourceServerId = cached.getResourceServerId();
for (String scopeId : cached.getScopesIds(modelSupplier)) {
Scope scope = scopeStore.findById(scopeId, resourceServerId);
cacheSession.cacheScope(scope);
scopes.add(scope);
} }
scopes = Collections.unmodifiableSet(scopes); return scopes = Collections.unmodifiableSet(scopes);
return scopes;
} }
@Override @Override
@ -286,7 +311,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override @Override
public void setOwner(String owner) { public void setOwner(String owner) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().get("defaultResourceType"), cached.getResourceServerId()); cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
updated.setOwner(owner); updated.setOwner(owner);
} }
@ -304,7 +329,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
return getId().hashCode(); return getId().hashCode();
} }
private Policy getPolicyModel() {
return cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
}
} }

View file

@ -18,7 +18,6 @@ package org.keycloak.models.cache.infinispan.authorization;
import org.keycloak.authorization.model.CachedModel; import org.keycloak.authorization.model.CachedModel;
import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
@ -31,7 +30,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -40,20 +39,22 @@ import java.util.stream.Collectors;
*/ */
public class ResourceAdapter implements Resource, CachedModel<Resource> { public class ResourceAdapter implements Resource, CachedModel<Resource> {
protected CachedResource cached; private final Supplier<Resource> modelSupplier;
protected StoreFactoryCacheSession cacheSession; protected final CachedResource cached;
protected final StoreFactoryCacheSession cacheSession;
protected Resource updated; protected Resource updated;
public ResourceAdapter(CachedResource cached, StoreFactoryCacheSession cacheSession) { public ResourceAdapter(CachedResource cached, StoreFactoryCacheSession cacheSession) {
this.cached = cached; this.cached = cached;
this.cacheSession = cacheSession; this.cacheSession = cacheSession;
this.modelSupplier = this::getResourceModel;
} }
@Override @Override
public Resource getDelegateForUpdate() { public Resource getDelegateForUpdate() {
if (updated == null) { if (updated == null) {
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); updated = modelSupplier.get();
updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId()); cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
if (updated == null) throw new IllegalStateException("Not found in database"); if (updated == null) throw new IllegalStateException("Not found in database");
} }
return updated; return updated;
@ -101,7 +102,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override @Override
public void setName(String name) { public void setName(String name) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
updated.setName(name); updated.setName(name);
} }
@ -114,7 +115,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override @Override
public void setDisplayName(String name) { public void setDisplayName(String name) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), name, cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
updated.setDisplayName(name); updated.setDisplayName(name);
} }
@ -139,13 +140,13 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override @Override
public Set<String> getUris() { public Set<String> getUris() {
if (isUpdated()) return updated.getUris(); if (isUpdated()) return updated.getUris();
return cached.getUris(); return cached.getUris(modelSupplier);
} }
@Override @Override
public void updateUris(Set<String> uris) { public void updateUris(Set<String> uris) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), uris, cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
updated.updateUris(uris); updated.updateUris(uris);
} }
@ -158,7 +159,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override @Override
public void setType(String type) { public void setType(String type) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), type, cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
updated.setType(type); updated.setType(type);
} }
@ -170,11 +171,10 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
if (isUpdated()) return updated.getScopes(); if (isUpdated()) return updated.getScopes();
if (scopes != null) return scopes; if (scopes != null) return scopes;
scopes = new LinkedList<>(); scopes = new LinkedList<>();
for (String scopeId : cached.getScopesIds()) { for (String scopeId : cached.getScopesIds(modelSupplier)) {
scopes.add(cacheSession.getScopeStore().findById(scopeId, cached.getResourceServerId())); scopes.add(cacheSession.getScopeStore().findById(scopeId, cached.getResourceServerId()));
} }
scopes = Collections.unmodifiableList(scopes); return scopes = Collections.unmodifiableList(scopes);
return scopes;
} }
@Override @Override
@ -192,7 +192,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override @Override
public void setOwnerManagedAccess(boolean ownerManagedAccess) { public void setOwnerManagedAccess(boolean ownerManagedAccess) {
getDelegateForUpdate(); getDelegateForUpdate();
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), cached.getScopesIds(modelSupplier), cached.getResourceServerId(), cached.getOwner());
updated.setOwnerManagedAccess(ownerManagedAccess); updated.setOwnerManagedAccess(ownerManagedAccess);
} }
@ -219,21 +219,21 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
} }
} }
cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner()); cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(modelSupplier), scopes.stream().map(scope1 -> scope1.getId()).collect(Collectors.toSet()), cached.getResourceServerId(), cached.getOwner());
updated.updateScopes(scopes); updated.updateScopes(scopes);
} }
@Override @Override
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes() {
if (updated != null) return updated.getAttributes(); if (updated != null) return updated.getAttributes();
return cached.getAttributes(); return cached.getAttributes(modelSupplier);
} }
@Override @Override
public String getSingleAttribute(String name) { public String getSingleAttribute(String name) {
if (updated != null) return updated.getSingleAttribute(name); if (updated != null) return updated.getSingleAttribute(name);
List<String> values = cached.getAttributes().getOrDefault(name, Collections.emptyList()); List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
if (values.isEmpty()) { if (values.isEmpty()) {
return null; return null;
@ -246,7 +246,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
public List<String> getAttribute(String name) { public List<String> getAttribute(String name) {
if (updated != null) return updated.getAttribute(name); if (updated != null) return updated.getAttribute(name);
List<String> values = cached.getAttributes().getOrDefault(name, Collections.emptyList()); List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
if (values.isEmpty()) { if (values.isEmpty()) {
return null; return null;
@ -267,6 +267,11 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
updated.removeAttribute(name); updated.removeAttribute(name);
} }
@Override
public boolean isFetched(String association) {
return modelSupplier.get().isFetched(association);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -281,4 +286,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
return getId().hashCode(); return getId().hashCode();
} }
private Resource getResourceModel() {
return cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
}
} }

View file

@ -407,7 +407,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
} }
} }
private boolean modelMightExist(String id) { boolean modelMightExist(String id) {
return invalidations.contains(id) || cache.get(id, NonExistentItem.class) == null; return invalidations.contains(id) || cache.get(id, NonExistentItem.class) == null;
} }
@ -712,11 +712,11 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
(revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer); (revision, resources) -> new ResourceListQuery(revision, cacheKey, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
} }
private <R, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) { private <R extends Resource, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
return cacheQuery(cacheKey, queryType, resultSupplier, querySupplier, resourceServerId, null); return cacheQuery(cacheKey, queryType, resultSupplier, querySupplier, resourceServerId, null);
} }
private <R, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId, Consumer<R> consumer) { private <R extends Resource, Q extends ResourceQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId, Consumer<R> consumer) {
Q query = cache.get(cacheKey, queryType); Q query = cache.get(cacheKey, queryType);
if (query != null) { if (query != null) {
logger.tracev("cache hit for key: {0}", cacheKey); logger.tracev("cache hit for key: {0}", cacheKey);
@ -730,7 +730,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
if (consumer != null) { if (consumer != null) {
for (R resource : model) { for (R resource : model) {
consumer.accept(resource); consumer.andThen(r -> cacheResource(resource)).accept(resource);
} }
} }
return model; return model;
@ -799,9 +799,9 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
logger.tracev("by id cache hit: {0}", cached.getId()); logger.tracev("by id cache hit: {0}", cached.getId());
} }
if (cached == null) { if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
if (! modelMightExist(id)) return null; if (! modelMightExist(id)) return null;
Policy model = getPolicyStoreDelegate().findById(id, resourceServerId); Policy model = getPolicyStoreDelegate().findById(id, resourceServerId);
Long loaded = cache.getCurrentRevision(id);
if (model == null) { if (model == null) {
setModelDoesNotExists(id, loaded); setModelDoesNotExists(id, loaded);
return null; return null;
@ -922,7 +922,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId); return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
} }
private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId, Consumer<R> consumer) { private <R extends Policy, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId, Consumer<R> consumer) {
Q query = cache.get(cacheKey, queryType); Q query = cache.get(cacheKey, queryType);
if (query != null) { if (query != null) {
logger.tracev("cache hit for key: {0}", cacheKey); logger.tracev("cache hit for key: {0}", cacheKey);
@ -936,7 +936,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
if (consumer != null) { if (consumer != null) {
for (R policy: model) { for (R policy: model) {
consumer.accept(policy); consumer.andThen(r -> cachePolicy(policy)).accept(policy);
} }
} }
return model; return model;
@ -1080,4 +1080,46 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
} }
} }
void cachePolicy(Policy model) {
String id = model.getId();
if (cache.getCache().containsKey(id)) {
return;
}
if (!modelMightExist(id)) {
return;
}
if (invalidations.contains(id)) return;
cache.addRevisioned(createCachedPolicy(model, id), startupRevision);
}
CachedPolicy createCachedPolicy(Policy model, String id) {
Long loaded = cache.getCurrentRevision(id);
return new CachedPolicy(loaded, model);
}
void cacheResource(Resource model) {
String id = model.getId();
if (cache.getCache().containsKey(id)) {
return;
}
Long loaded = cache.getCurrentRevision(id);
if (!modelMightExist(id)) {
return;
}
if (invalidations.contains(id)) return;
cache.addRevisioned(new CachedResource(loaded, model), startupRevision);
}
void cacheScope(Scope model) {
String id = model.getId();
if (cache.getCache().containsKey(id)) {
return;
}
Long loaded = cache.getCurrentRevision(id);
if (!modelMightExist(id)) {
return;
}
if (invalidations.contains(id)) return;
cache.addRevisioned(new CachedScope(loaded, model), startupRevision);
}
} }

View file

@ -21,13 +21,17 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
import org.keycloak.models.cache.infinispan.LazyLoader;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.Logic;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -35,16 +39,16 @@ import java.util.stream.Collectors;
*/ */
public class CachedPolicy extends AbstractRevisioned implements InResourceServer { public class CachedPolicy extends AbstractRevisioned implements InResourceServer {
private String type; private final String type;
private DecisionStrategy decisionStrategy; private final DecisionStrategy decisionStrategy;
private Logic logic; private final Logic logic;
private Map<String, String> config; private final String name;
private String name; private final String description;
private String description; private final String resourceServerId;
private String resourceServerId; private final LazyLoader<Policy, Set<String>> associatedPoliciesIds;
private Set<String> associatedPoliciesIds; private final LazyLoader<Policy, Set<String>> resourcesIds;
private Set<String> resourcesIds; private final LazyLoader<Policy, Set<String>> scopesIds;
private Set<String> scopesIds; private final LazyLoader<Policy, Map<String, String>> config;
private final String owner; private final String owner;
public CachedPolicy(Long revision, Policy policy) { public CachedPolicy(Long revision, Policy policy) {
@ -52,13 +56,38 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
this.type = policy.getType(); this.type = policy.getType();
this.decisionStrategy = policy.getDecisionStrategy(); this.decisionStrategy = policy.getDecisionStrategy();
this.logic = policy.getLogic(); this.logic = policy.getLogic();
this.config = new HashMap(policy.getConfig());
this.name = policy.getName(); this.name = policy.getName();
this.description = policy.getDescription(); this.description = policy.getDescription();
this.resourceServerId = policy.getResourceServer().getId(); this.resourceServerId = policy.getResourceServer().getId();
this.associatedPoliciesIds = policy.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet());
this.resourcesIds = policy.getResources().stream().map(Resource::getId).collect(Collectors.toSet()); if (policy.isFetched("associatedPolicies")) {
this.scopesIds = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet()); Set<String> data = policy.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet());
this.associatedPoliciesIds = source -> data;
} else {
this.associatedPoliciesIds = new DefaultLazyLoader<>(source -> source.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toSet()), Collections::emptySet);
}
if (policy.isFetched("resources")) {
Set<String> data = policy.getResources().stream().map(Resource::getId).collect(Collectors.toSet());
this.resourcesIds = source -> data;
} else {
this.resourcesIds = new DefaultLazyLoader<>(source -> source.getResources().stream().map(Resource::getId).collect(Collectors.toSet()), Collections::emptySet);
}
if (policy.isFetched("scopes")) {
Set<String> data = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
this.scopesIds = source -> data;
} else {
this.scopesIds = new DefaultLazyLoader<>(source -> source.getScopes().stream().map(Scope::getId).collect(Collectors.toSet()), Collections::emptySet);
}
if (policy.isFetched("config")) {
Map<String, String> data = new HashMap<>(policy.getConfig());
this.config = source -> data;
} else {
this.config = new DefaultLazyLoader<>(source -> new HashMap<>(source.getConfig()), Collections::emptyMap);
}
this.owner = policy.getOwner(); this.owner = policy.getOwner();
} }
@ -74,8 +103,8 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
return this.logic; return this.logic;
} }
public Map<String, String> getConfig() { public Map<String, String> getConfig(Supplier<Policy> policy) {
return this.config; return this.config.get(policy);
} }
public String getName() { public String getName() {
@ -86,16 +115,16 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
return this.description; return this.description;
} }
public Set<String> getAssociatedPoliciesIds() { public Set<String> getAssociatedPoliciesIds(Supplier<Policy> policy) {
return this.associatedPoliciesIds; return this.associatedPoliciesIds.get(policy);
} }
public Set<String> getResourcesIds() { public Set<String> getResourcesIds(Supplier<Policy> policy) {
return this.resourcesIds; return this.resourcesIds.get(policy);
} }
public Set<String> getScopesIds() { public Set<String> getScopesIds(Supplier<Policy> policy) {
return this.scopesIds; return this.scopesIds.get(policy);
} }
public String getResourceServerId() { public String getResourceServerId() {

View file

@ -21,11 +21,16 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
import org.keycloak.models.cache.infinispan.LazyLoader;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned; import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -33,29 +38,47 @@ import java.util.stream.Collectors;
*/ */
public class CachedResource extends AbstractRevisioned implements InResourceServer { public class CachedResource extends AbstractRevisioned implements InResourceServer {
private String resourceServerId; private final String resourceServerId;
private String iconUri; private final String iconUri;
private String owner; private final String owner;
private String type; private final String type;
private String name; private final String name;
private String displayName; private final String displayName;
private Set<String> uris; private final boolean ownerManagedAccess;
private Set<String> scopesIds; private LazyLoader<Resource, Set<String>> scopesIds;
private boolean ownerManagedAccess; private LazyLoader<Resource, Set<String>> uris;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>(); private LazyLoader<Resource, MultivaluedHashMap<String, String>> attributes;
public CachedResource(Long revision, Resource resource) { public CachedResource(Long revision, Resource resource) {
super(revision, resource.getId()); super(revision, resource.getId());
this.name = resource.getName(); this.name = resource.getName();
this.displayName = resource.getDisplayName(); this.displayName = resource.getDisplayName();
this.uris = resource.getUris();
this.type = resource.getType(); this.type = resource.getType();
this.owner = resource.getOwner(); this.owner = resource.getOwner();
this.iconUri = resource.getIconUri(); this.iconUri = resource.getIconUri();
this.resourceServerId = resource.getResourceServer().getId(); this.resourceServerId = resource.getResourceServer().getId();
this.scopesIds = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
ownerManagedAccess = resource.isOwnerManagedAccess(); ownerManagedAccess = resource.isOwnerManagedAccess();
this.attributes.putAll(resource.getAttributes());
if (resource.isFetched("uris")) {
Set<String> data = new HashSet<>(resource.getUris());
this.uris = source -> data;
} else {
this.uris = new DefaultLazyLoader<>(source -> new HashSet<>(source.getUris()), Collections::emptySet);
}
if (resource.isFetched("scopes")) {
Set<String> data = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
this.scopesIds = source -> data;
} else {
this.scopesIds = new DefaultLazyLoader<>(source -> source.getScopes().stream().map(Scope::getId).collect(Collectors.toSet()), Collections::emptySet);
}
if (resource.isFetched("attributes")) {
MultivaluedHashMap<String, String> data = new MultivaluedHashMap<>(resource.getAttributes());
this.attributes = source -> data;
} else {
this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new);
}
} }
@ -67,8 +90,8 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
return this.displayName; return this.displayName;
} }
public Set<String> getUris() { public Set<String> getUris(Supplier<Resource> source) {
return this.uris; return this.uris.get(source);
} }
public String getType() { public String getType() {
@ -91,11 +114,11 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
return this.resourceServerId; return this.resourceServerId;
} }
public Set<String> getScopesIds() { public Set<String> getScopesIds(Supplier<Resource> source) {
return this.scopesIds; return this.scopesIds.get(source);
} }
public Map<String, List<String>> getAttributes() { public Map<String, List<String>> getAttributes(Supplier<Resource> source) {
return attributes; return attributes.get(source);
} }
} }

View file

@ -21,50 +21,51 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
import org.keycloak.models.cache.infinispan.LazyLoader;
import java.util.HashSet; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CachedGroup extends AbstractRevisioned implements InRealm { public class CachedGroup extends AbstractRevisioned implements InRealm {
private String realm;
private String name; private final String realm;
private String parentId; private final String name;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>(); private final String parentId;
private Set<String> roleMappings = new HashSet<>(); private final LazyLoader<GroupModel, MultivaluedHashMap<String, String>> attributes;
private Set<String> subGroups = new HashSet<>(); private final LazyLoader<GroupModel, Set<String>> roleMappings;
private final LazyLoader<GroupModel, Set<String>> subGroups;
public CachedGroup(Long revision, RealmModel realm, GroupModel group) { public CachedGroup(Long revision, RealmModel realm, GroupModel group) {
super(revision, group.getId()); super(revision, group.getId());
this.realm = realm.getId(); this.realm = realm.getId();
this.name = group.getName(); this.name = group.getName();
this.parentId = group.getParentId(); this.parentId = group.getParentId();
this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new);
this.attributes.putAll(group.getAttributes()); this.roleMappings = new DefaultLazyLoader<>(source -> source.getRoleMappings().stream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
for (RoleModel role : group.getRoleMappings()) { this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroups().stream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet);
roleMappings.add(role.getId());
}
Set<GroupModel> subGroups1 = group.getSubGroups();
if (subGroups1 != null) {
for (GroupModel subGroup : subGroups1) {
subGroups.add(subGroup.getId());
}
}
} }
public String getRealm() { public String getRealm() {
return realm; return realm;
} }
public MultivaluedHashMap<String, String> getAttributes() { public MultivaluedHashMap<String, String> getAttributes(Supplier<GroupModel> group) {
return attributes; return attributes.get(group);
} }
public Set<String> getRoleMappings() { public Set<String> getRoleMappings(Supplier<GroupModel> group) {
return roleMappings; // 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);
} }
public String getName() { public String getName() {
@ -75,7 +76,7 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
return parentId; return parentId;
} }
public Set<String> getSubGroups() { public Set<String> getSubGroups(Supplier<GroupModel> group) {
return subGroups; return subGroups.get(group);
} }
} }

View file

@ -22,32 +22,35 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
import org.keycloak.models.cache.infinispan.LazyLoader;
import java.util.HashSet; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class CachedUser extends AbstractExtendableRevisioned implements InRealm { public class CachedUser extends AbstractExtendableRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
private String firstName;
private String lastName;
private String email;
private boolean emailVerified;
private boolean enabled;
private String federationLink;
private String serviceAccountClientLink;
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<>();
private Set<String> groups = new HashSet<>();
private int notBefore;
private final String realm;
private final String username;
private final Long createdTimestamp;
private final String firstName;
private final String lastName;
private final String email;
private final boolean emailVerified;
private final boolean enabled;
private final String federationLink;
private final String serviceAccountClientLink;
private final int notBefore;
private final LazyLoader<UserModel, Set<String>> requiredActions;
private final LazyLoader<UserModel, MultivaluedHashMap<String, String>> attributes;
private final LazyLoader<UserModel, Set<String>> roleMappings;
private final LazyLoader<UserModel, Set<String>> groups;
public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) { public CachedUser(Long revision, RealmModel realm, UserModel user, int notBefore) {
super(revision, user.getId()); super(revision, user.getId());
@ -56,23 +59,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
this.createdTimestamp = user.getCreatedTimestamp(); this.createdTimestamp = user.getCreatedTimestamp();
this.firstName = user.getFirstName(); this.firstName = user.getFirstName();
this.lastName = user.getLastName(); this.lastName = user.getLastName();
this.attributes.putAll(user.getAttributes());
this.email = user.getEmail(); this.email = user.getEmail();
this.emailVerified = user.isEmailVerified(); this.emailVerified = user.isEmailVerified();
this.enabled = user.isEnabled(); this.enabled = user.isEnabled();
this.federationLink = user.getFederationLink(); this.federationLink = user.getFederationLink();
this.serviceAccountClientLink = user.getServiceAccountClientLink(); this.serviceAccountClientLink = user.getServiceAccountClientLink();
this.requiredActions.addAll(user.getRequiredActions());
for (RoleModel role : user.getRoleMappings()) {
roleMappings.add(role.getId());
}
Set<GroupModel> groupMappings = user.getGroups();
if (groupMappings != null) {
for (GroupModel group : groupMappings) {
groups.add(group.getId());
}
}
this.notBefore = notBefore; this.notBefore = notBefore;
this.requiredActions = new DefaultLazyLoader<>(UserModel::getRequiredActions, Collections::emptySet);
this.attributes = new DefaultLazyLoader<>(userModel -> new MultivaluedHashMap<>(userModel.getAttributes()), MultivaluedHashMap::new);
this.roleMappings = new DefaultLazyLoader<>(userModel -> userModel.getRoleMappings().stream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
this.groups = new DefaultLazyLoader<>(userModel -> userModel.getGroups().stream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet);
} }
public String getRealm() { public String getRealm() {
@ -107,16 +103,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return enabled; return enabled;
} }
public MultivaluedHashMap<String, String> getAttributes() { public MultivaluedHashMap<String, String> getAttributes(Supplier<UserModel> userModel) {
return attributes; return attributes.get(userModel);
} }
public Set<String> getRequiredActions() { public Set<String> getRequiredActions(Supplier<UserModel> userModel) {
return requiredActions; return this.requiredActions.get(userModel);
} }
public Set<String> getRoleMappings() { public Set<String> getRoleMappings(Supplier<UserModel> userModel) {
return roleMappings; return roleMappings.get(userModel);
} }
public String getFederationLink() { public String getFederationLink() {
@ -127,8 +123,8 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return serviceAccountClientLink; return serviceAccountClientLink;
} }
public Set<String> getGroups() { public Set<String> getGroups(Supplier<UserModel> userModel) {
return groups; return groups.get(userModel);
} }
public int getNotBefore() { public int getNotBefore() {

View file

@ -44,7 +44,7 @@ public class HasRolePredicate implements Predicate<Map.Entry<String, Revisioned>
} }
if (value instanceof CachedGroup) { if (value instanceof CachedGroup) {
CachedGroup cachedRole = (CachedGroup)value; CachedGroup cachedRole = (CachedGroup)value;
if (cachedRole.getRoleMappings().contains(role)) return true; if (cachedRole.getRoleMappings(null).contains(role)) return true;
} }
if (value instanceof RoleQuery) { if (value instanceof RoleQuery) {
RoleQuery roleQuery = (RoleQuery)value; RoleQuery roleQuery = (RoleQuery)value;

View file

@ -55,13 +55,13 @@ import org.keycloak.representations.idm.authorization.Logic;
@NamedQueries( @NamedQueries(
{ {
@NamedQuery(name="findPolicyIdByServerId", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId "), @NamedQuery(name="findPolicyIdByServerId", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId "),
@NamedQuery(name="findPolicyIdByName", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId and p.name = :name"), @NamedQuery(name="findPolicyIdByName", query="select p from PolicyEntity p left join fetch p.associatedPolicies a where p.resourceServer.id = :serverId and p.name = :name"),
@NamedQuery(name="findPolicyIdByResource", query="select p.id from PolicyEntity p inner join p.resources r where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"), @NamedQuery(name="findPolicyIdByResource", query="select p from PolicyEntity p inner join fetch p.resources r left join fetch p.scopes s inner join fetch p.associatedPolicies a where p.resourceServer.id = :serverId and (r.resourceServer.id = :serverId and r.id = :resourceId)"),
@NamedQuery(name="findPolicyIdByScope", query="select pe.id from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds)))"), @NamedQuery(name="findPolicyIdByScope", query="select pe from PolicyEntity pe left join fetch pe.resources r inner join fetch pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds) and p.id = pe.id))"),
@NamedQuery(name="findPolicyIdByResourceScope", query="select pe.id from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds))) and pe.id IN (select p.id from ResourceEntity r inner join r.policies p where r.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and r.id in (:resourceId))))"), @NamedQuery(name="findPolicyIdByResourceScope", query="select pe from PolicyEntity pe inner join fetch pe.resources r inner join fetch pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds) and p.id = pe.id)) and exists (select p.id from ResourceEntity r inner join r.policies p where r.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.id = pe.id and p.type = 'scope' and r.id in (:resourceId))))"),
@NamedQuery(name="findPolicyIdByNullResourceScope", query="select pe.id from PolicyEntity pe where pe.resourceServer.id = :serverId and pe.id IN (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.type = 'scope' and s.id in (:scopeIds))) and pe.resources is empty"), @NamedQuery(name="findPolicyIdByNullResourceScope", query="select pe from PolicyEntity pe left join fetch pe.resources r inner join fetch pe.scopes s inner join fetch pe.associatedPolicies a where pe.resourceServer.id = :serverId and exists (select p.id from ScopeEntity s inner join s.policies p where s.resourceServer.id = :serverId and (p.resourceServer.id = :serverId and p.id = pe.id and p.type = 'scope' and s.id in (:scopeIds))) and pe.resources is empty"),
@NamedQuery(name="findPolicyIdByType", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"), @NamedQuery(name="findPolicyIdByType", query="select p.id from PolicyEntity p where p.resourceServer.id = :serverId and p.type = :type"),
@NamedQuery(name="findPolicyIdByResourceType", query="select p.id from PolicyEntity p inner join p.config c where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"), @NamedQuery(name="findPolicyIdByResourceType", query="select p from PolicyEntity p inner join p.config c inner join fetch p.associatedPolicies a where p.resourceServer.id = :serverId and KEY(c) = 'defaultResourceType' and c like :type"),
@NamedQuery(name="findPolicyIdByDependentPolices", query="select p.id from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"), @NamedQuery(name="findPolicyIdByDependentPolices", query="select p.id from PolicyEntity p inner join p.associatedPolicies ap where p.resourceServer.id = :serverId and (ap.resourceServer.id = :serverId and ap.id = :policyId)"),
@NamedQuery(name="deletePolicyByResourceServer", query="delete from PolicyEntity p where p.resourceServer.id = :serverId") @NamedQuery(name="deletePolicyByResourceServer", query="delete from PolicyEntity p where p.resourceServer.id = :serverId")
} }

View file

@ -57,13 +57,13 @@ import org.hibernate.annotations.FetchMode;
}) })
@NamedQueries( @NamedQueries(
{ {
@NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :owner"), @NamedQuery(name="findResourceIdByOwner", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :owner"),
@NamedQuery(name="findAnyResourceIdByOwner", query="select r.id from ResourceEntity r where r.owner = :owner"), @NamedQuery(name="findAnyResourceIdByOwner", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.owner = :owner"),
@NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and :uri in elements(r.uris)"), @NamedQuery(name="findResourceIdByUri", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and :uri in elements(r.uris)"),
@NamedQuery(name="findResourceIdByName", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"), @NamedQuery(name="findResourceIdByName", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.name = :name"),
@NamedQuery(name="findResourceIdByType", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"), @NamedQuery(name="findResourceIdByType", query="select distinct(r) from ResourceEntity r left join fetch r.scopes s where r.resourceServer.id = :serverId and r.owner = :ownerId and r.type = :type"),
@NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "), @NamedQuery(name="findResourceIdByServerId", query="select r.id from ResourceEntity r where r.resourceServer.id = :serverId "),
@NamedQuery(name="findResourceIdByScope", query="select r.id from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"), @NamedQuery(name="findResourceIdByScope", query="select r from ResourceEntity r inner join r.scopes s where r.resourceServer.id = :serverId and (s.resourceServer.id = :serverId and s.id in (:scopeIds))"),
@NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId") @NamedQuery(name="deleteResourceByResourceServer", query="delete from ResourceEntity r where r.resourceServer.id = :serverId")
} }
) )
@ -80,7 +80,7 @@ public class ResourceEntity {
@Column(name = "DISPLAY_NAME") @Column(name = "DISPLAY_NAME")
private String displayName; private String displayName;
@ElementCollection(fetch = FetchType.EAGER) @ElementCollection(fetch = FetchType.LAZY)
@Column(name = "VALUE") @Column(name = "VALUE")
@CollectionTable(name = "RESOURCE_URIS", joinColumns = { @JoinColumn(name="RESOURCE_ID") }) @CollectionTable(name = "RESOURCE_URIS", joinColumns = { @JoinColumn(name="RESOURCE_ID") })
private Set<String> uris = new HashSet<>(); private Set<String> uris = new HashSet<>();
@ -101,7 +101,7 @@ public class ResourceEntity {
@JoinColumn(name = "RESOURCE_SERVER_ID") @JoinColumn(name = "RESOURCE_SERVER_ID")
private ResourceServerEntity resourceServer; private ResourceServerEntity resourceServer;
@ManyToMany(fetch = FetchType.LAZY, cascade = {}) @OneToMany(fetch = FetchType.LAZY, cascade = {})
@JoinTable(name = "RESOURCE_SCOPE", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID")) @JoinTable(name = "RESOURCE_SCOPE", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID"))
private List<ScopeEntity> scopes = new LinkedList<>(); private List<ScopeEntity> scopes = new LinkedList<>();
@ -109,7 +109,7 @@ public class ResourceEntity {
@JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID")) @JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "RESOURCE_ID"), inverseJoinColumns = @JoinColumn(name = "POLICY_ID"))
private List<PolicyEntity> policies = new LinkedList<>(); private List<PolicyEntity> policies = new LinkedList<>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="resource") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="resource", fetch = FetchType.LAZY)
@Fetch(FetchMode.SELECT) @Fetch(FetchMode.SELECT)
@BatchSize(size = 20) @BatchSize(size = 20)
private Collection<ResourceAttributeEntity> attributes = new ArrayList<>(); private Collection<ResourceAttributeEntity> attributes = new ArrayList<>();

View file

@ -40,6 +40,7 @@ import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@ -90,23 +91,25 @@ public class JPAPolicyStore implements PolicyStore {
return null; return null;
} }
PolicyEntity entity = entityManager.find(PolicyEntity.class, id); PolicyEntity policyEntity = entityManager.find(PolicyEntity.class, id);
if (entity == null) return null;
return new PolicyAdapter(entity, entityManager, provider.getStoreFactory()); if (policyEntity == null) {
return null;
}
return new PolicyAdapter(policyEntity, entityManager, provider.getStoreFactory());
} }
@Override @Override
public Policy findByName(String name, String resourceServerId) { public Policy findByName(String name, String resourceServerId) {
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByName", String.class); TypedQuery<PolicyEntity> query = entityManager.createNamedQuery("findPolicyIdByName", PolicyEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
query.setParameter("name", name); query.setParameter("name", name);
try { try {
String id = query.getSingleResult(); return new PolicyAdapter(query.getSingleResult(), entityManager, provider.getStoreFactory());
return provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
} catch (NoResultException ex) { } catch (NoResultException ex) {
return null; return null;
} }
@ -203,17 +206,16 @@ public class JPAPolicyStore implements PolicyStore {
@Override @Override
public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) { public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResource", String.class); TypedQuery<PolicyEntity> query = entityManager.createNamedQuery("findPolicyIdByResource", PolicyEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("resourceId", resourceId); query.setParameter("resourceId", resourceId);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream() query.getResultList().stream()
.map(id -> policyStore.findById(id, resourceServerId)) .map(entity -> new PolicyAdapter(entity, entityManager, storeFactory))
.filter(Objects::nonNull)
.forEach(consumer::accept); .forEach(consumer::accept);
} }
@ -228,17 +230,14 @@ public class JPAPolicyStore implements PolicyStore {
@Override @Override
public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) { public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) {
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResourceType", String.class); TypedQuery<PolicyEntity> query = entityManager.createNamedQuery("findPolicyIdByResourceType", PolicyEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", resourceType); query.setParameter("type", resourceType);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
query.getResultList().stream() query.getResultList().stream()
.map(id -> policyStore.findById(id, resourceServerId)) .map(id -> new PolicyAdapter(id, entityManager, provider.getStoreFactory()))
.filter(Objects::nonNull)
.forEach(consumer::accept); .forEach(consumer::accept);
} }
@ -249,20 +248,19 @@ public class JPAPolicyStore implements PolicyStore {
} }
// Use separate subquery to handle DB2 and MSSSQL // Use separate subquery to handle DB2 and MSSSQL
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByScope", String.class); TypedQuery<PolicyEntity> query = entityManager.createNamedQuery("findPolicyIdByScope", PolicyEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("scopeIds", scopeIds); query.setParameter("scopeIds", scopeIds);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
List<String> result = query.getResultList();
List<Policy> list = new LinkedList<>(); List<Policy> list = new LinkedList<>();
for (String id : result) { StoreFactory storeFactory = provider.getStoreFactory();
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
if (Objects.nonNull(policy)) { for (PolicyEntity entity : query.getResultList()) {
list.add(policy); list.add(new PolicyAdapter(entity, entityManager, storeFactory));
}
} }
return list; return list;
} }
@ -278,12 +276,12 @@ public class JPAPolicyStore implements PolicyStore {
@Override @Override
public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) { public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
// Use separate subquery to handle DB2 and MSSSQL // Use separate subquery to handle DB2 and MSSSQL
TypedQuery<String> query; TypedQuery<PolicyEntity> query;
if (resourceId == null) { if (resourceId == null) {
query = entityManager.createNamedQuery("findPolicyIdByNullResourceScope", String.class); query = entityManager.createNamedQuery("findPolicyIdByNullResourceScope", PolicyEntity.class);
} else { } else {
query = entityManager.createNamedQuery("findPolicyIdByResourceScope", String.class); query = entityManager.createNamedQuery("findPolicyIdByResourceScope", PolicyEntity.class);
query.setParameter("resourceId", resourceId); query.setParameter("resourceId", resourceId);
} }
@ -291,11 +289,10 @@ public class JPAPolicyStore implements PolicyStore {
query.setParameter("scopeIds", scopeIds); query.setParameter("scopeIds", scopeIds);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore(); StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream() query.getResultList().stream()
.map(id -> policyStore.findById(id, resourceServerId)) .map(id -> new PolicyAdapter(id, entityManager, storeFactory))
.filter(Objects::nonNull)
.forEach(consumer::accept); .forEach(consumer::accept);
} }

View file

@ -22,6 +22,7 @@ import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
@ -116,7 +117,7 @@ public class JPAResourceStore implements ResourceStore {
queryName = "findAnyResourceIdByOwner"; queryName = "findAnyResourceIdByOwner";
} }
TypedQuery<String> query = entityManager.createNamedQuery(queryName, String.class); TypedQuery<ResourceEntity> query = entityManager.createNamedQuery(queryName, ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("owner", ownerId); query.setParameter("owner", ownerId);
@ -125,11 +126,10 @@ public class JPAResourceStore implements ResourceStore {
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
} }
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore(); StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream() query.getResultList().stream()
.map(id -> resourceStore.findById(id, resourceServerId)) .map(id -> new ResourceAdapter(id, entityManager, storeFactory))
.filter(Objects::nonNull)
.forEach(consumer); .forEach(consumer);
} }
@ -247,17 +247,16 @@ public class JPAResourceStore implements ResourceStore {
@Override @Override
public void findByScope(List<String> scopes, String resourceServerId, Consumer<Resource> consumer) { public void findByScope(List<String> scopes, String resourceServerId, Consumer<Resource> consumer) {
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByScope", String.class); TypedQuery<ResourceEntity> query = entityManager.createNamedQuery("findResourceIdByScope", ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("scopeIds", scopes); query.setParameter("scopeIds", scopes);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore(); StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream() query.getResultList().stream()
.map(id -> resourceStore.findById(id, resourceServerId)) .map(id -> new ResourceAdapter(id, entityManager, storeFactory))
.filter(Objects::nonNull)
.forEach(consumer); .forEach(consumer);
} }
@ -268,16 +267,14 @@ public class JPAResourceStore implements ResourceStore {
@Override @Override
public Resource findByName(String name, String ownerId, String resourceServerId) { public Resource findByName(String name, String ownerId, String resourceServerId) {
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByName", String.class); TypedQuery<ResourceEntity> query = entityManager.createNamedQuery("findResourceIdByName", ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
query.setParameter("name", name); query.setParameter("name", name);
query.setParameter("ownerId", ownerId); query.setParameter("ownerId", ownerId);
try { try {
String id = query.getSingleResult(); return new ResourceAdapter(query.getSingleResult(), entityManager, provider.getStoreFactory());
return provider.getStoreFactory().getResourceStore().findById(id, resourceServerId);
} catch (NoResultException ex) { } catch (NoResultException ex) {
return null; return null;
} }
@ -294,18 +291,17 @@ public class JPAResourceStore implements ResourceStore {
@Override @Override
public void findByType(String type, String resourceServerId, Consumer<Resource> consumer) { public void findByType(String type, String resourceServerId, Consumer<Resource> consumer) {
TypedQuery<String> query = entityManager.createNamedQuery("findResourceIdByType", String.class); TypedQuery<ResourceEntity> query = entityManager.createNamedQuery("findResourceIdByType", ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", type); query.setParameter("type", type);
query.setParameter("ownerId", resourceServerId); query.setParameter("ownerId", resourceServerId);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
ResourceStore resourceStore = provider.getStoreFactory().getResourceStore(); StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream() query.getResultList().stream()
.map(id -> resourceStore.findById(id, resourceServerId)) .map(entity -> new ResourceAdapter(entity, entityManager, storeFactory))
.filter(Objects::nonNull)
.forEach(consumer); .forEach(consumer);
} }
} }

View file

@ -151,8 +151,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
public Set<Policy> getAssociatedPolicies() { public Set<Policy> getAssociatedPolicies() {
Set<Policy> result = new HashSet<>(); Set<Policy> result = new HashSet<>();
for (PolicyEntity policy : entity.getAssociatedPolicies()) { for (PolicyEntity policy : entity.getAssociatedPolicies()) {
Policy p = storeFactory.getPolicyStore().findById(policy.getId(), entity.getResourceServer().getId()); result.add(new PolicyAdapter(policy, em, storeFactory));
result.add(p);
} }
return Collections.unmodifiableSet(result); return Collections.unmodifiableSet(result);
} }
@ -239,6 +238,8 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
} }
} }
@Override
public boolean isFetched(String association) {
return em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity, association);
}
} }

View file

@ -231,6 +231,11 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
entity.getAttributes().removeAll(toRemove); entity.getAttributes().removeAll(toRemove);
} }
@Override
public boolean isFetched(String association) {
return em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(this, association);
}
public static ResourceEntity toEntity(EntityManager em, Resource resource) { public static ResourceEntity toEntity(EntityManager em, Resource resource) {
if (resource instanceof ResourceAdapter) { if (resource instanceof ResourceAdapter) {

View file

@ -17,6 +17,7 @@
package org.keycloak.models.jpa; package org.keycloak.models.jpa;
import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
@ -42,6 +43,7 @@ import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserConsentClientScopeEntity; import org.keycloak.models.jpa.entities.UserConsentClientScopeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity; import org.keycloak.models.jpa.entities.UserConsentEntity;
import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.DefaultRoles;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.StorageId; import org.keycloak.storage.StorageId;
@ -50,6 +52,11 @@ import org.keycloak.storage.client.ClientStorageProvider;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@ -510,12 +517,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override @Override
public UserModel getUserById(String id, RealmModel realm) { public UserModel getUserById(String id, RealmModel realm) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class); UserEntity userEntity = em.find(UserEntity.class, id);
query.setParameter("id", id); if (userEntity == null) return null;
query.setParameter("realmId", realm.getId()); return new UserAdapter(session, realm, em, userEntity);
List<UserEntity> entities = query.getResultList();
if (entities.size() == 0) return null;
return new UserAdapter(session, realm, em, entities.get(0));
} }
@Override @Override
@ -700,55 +704,86 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override @Override
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) { public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
StringBuilder builder = new StringBuilder("select u from UserEntity u where u.realmId = :realmId"); CriteriaBuilder builder = em.getCriteriaBuilder();
for (Map.Entry<String, String> entry : attributes.entrySet()) { CriteriaQuery<UserEntity> queryBuilder = builder.createQuery(UserEntity.class);
String attribute = null; Root<UserEntity> root = queryBuilder.from(UserEntity.class);
String parameterName = null;
if (entry.getKey().equals(UserModel.USERNAME)) { List<Predicate> predicates = new ArrayList();
attribute = "lower(u.username)";
parameterName = JpaUserProvider.USERNAME; predicates.add(builder.equal(root.get("realmId"), realm.getId()));
} else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
attribute = "lower(u.firstName)"; if (!session.getAttributeOrDefault(UserModel.INCLUDE_SERVICE_ACCOUNT, true)) {
parameterName = JpaUserProvider.FIRST_NAME; predicates.add(root.get("serviceAccountClientLink").isNull());
} else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) {
attribute = "lower(u.lastName)";
parameterName = JpaUserProvider.LAST_NAME;
} else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) {
attribute = "lower(u.email)";
parameterName = JpaUserProvider.EMAIL;
}
if (attribute == null) continue;
builder.append(" and ");
builder.append(attribute).append(" like :").append(parameterName);
} }
builder.append(" order by u.username");
String q = builder.toString();
TypedQuery<UserEntity> query = em.createQuery(q, UserEntity.class);
query.setParameter("realmId", realm.getId());
for (Map.Entry<String, String> entry : attributes.entrySet()) { for (Map.Entry<String, String> entry : attributes.entrySet()) {
String parameterName = null; String key = entry.getKey();
if (entry.getKey().equals(UserModel.USERNAME)) { String value = entry.getValue();
parameterName = JpaUserProvider.USERNAME;
} else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) { if (value == null) {
parameterName = JpaUserProvider.FIRST_NAME; continue;
} else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) { }
parameterName = JpaUserProvider.LAST_NAME;
} else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) { switch (key) {
parameterName = JpaUserProvider.EMAIL; case UserModel.USERNAME:
case UserModel.FIRST_NAME:
case UserModel.LAST_NAME:
case UserModel.EMAIL:
predicates.add(builder.like(builder.lower(root.get(key)), "%" + value.toLowerCase() + "%"));
} }
if (parameterName == null) continue;
query.setParameter(parameterName, "%" + entry.getValue().toLowerCase() + "%");
} }
Set<String> userGroups = (Set<String>) session.getAttribute(UserModel.GROUPS);
if (userGroups != null) {
Subquery subquery = queryBuilder.subquery(String.class);
Root<UserGroupMembershipEntity> from = subquery.from(UserGroupMembershipEntity.class);
subquery.select(builder.literal(1));
List<Predicate> subPredicates = new ArrayList<>();
subPredicates.add(from.get("groupId").in(userGroups));
subPredicates.add(builder.equal(from.get("user").get("id"), root.get("id")));
Subquery subquery1 = queryBuilder.subquery(String.class);
subquery1.select(builder.literal(1));
Root from1 = subquery1.from(ResourceEntity.class);
List<Predicate> subs = new ArrayList<>();
subs.add(builder.like(from1.get("name"), builder.concat("group.resource.", from.get("groupId"))));
subquery1.where(subs.toArray(new Predicate[subs.size()]));
subPredicates.add(builder.exists(subquery1));
subquery.where(subPredicates.toArray(new Predicate[subPredicates.size()]));
predicates.add(builder.exists(subquery));
}
queryBuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get(UserModel.USERNAME)));
TypedQuery<UserEntity> query = em.createQuery(queryBuilder);
if (firstResult != -1) { if (firstResult != -1) {
query.setFirstResult(firstResult); query.setFirstResult(firstResult);
} }
if (maxResults != -1) { if (maxResults != -1) {
query.setMaxResults(maxResults); query.setMaxResults(maxResults);
} }
List<UserEntity> results = query.getResultList();
List<UserModel> users = new ArrayList<UserModel>(); List<UserModel> results = new ArrayList<>();
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity)); UserProvider users = session.users();
return users;
for (UserEntity entity : query.getResultList()) {
results.add(users.getUserById(entity.getId(), realm));
}
return results;
} }
@Override @Override

View file

@ -46,7 +46,6 @@ import java.util.Collection;
@NamedQuery(name="getAllUsersByRealmExcludeServiceAccount", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) order by u.username"), @NamedQuery(name="getAllUsersByRealmExcludeServiceAccount", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) order by u.username"),
@NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " + @NamedQuery(name="searchForUser", query="select u from UserEntity u where u.realmId = :realmId and (u.serviceAccountClientLink is null) and " +
"( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"), "( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search ) order by u.username"),
@NamedQuery(name="getRealmUserById", query="select u from UserEntity u where u.id = :id and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realmId = :realmId"), @NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realmId = :realmId"),

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * Copyright 2018 Red Hat, Inc. and/or its affiliates
~ * and other contributors as indicated by the @author tags.
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="psilva@redhat.com" id="4.6.0-KEYCLOAK-7950">
<update tableName="RESOURCE_SERVER_RESOURCE">
<column name="TYPE" value="Group"/>
<where>NAME LIKE 'group.resource.%'</where>
</update>
</changeSet>
</databaseChangeLog>

View file

@ -59,4 +59,5 @@
<include file="META-INF/jpa-changelog-authz-4.2.0.Final.xml"/> <include file="META-INF/jpa-changelog-authz-4.2.0.Final.xml"/>
<include file="META-INF/jpa-changelog-4.2.0.xml"/> <include file="META-INF/jpa-changelog-4.2.0.xml"/>
<include file="META-INF/jpa-changelog-4.3.0.xml"/> <include file="META-INF/jpa-changelog-4.3.0.xml"/>
<include file="META-INF/jpa-changelog-4.6.0.xml"/>
</databaseChangeLog> </databaseChangeLog>

View file

@ -163,4 +163,6 @@ public interface Policy {
void addResource(Resource resource); void addResource(Resource resource);
void removeResource(Resource resource); void removeResource(Resource resource);
boolean isFetched(String association);
} }

View file

@ -180,4 +180,6 @@ public interface Resource {
void setAttribute(String name, List<String> values); void setAttribute(String name, List<String> values);
void removeAttribute(String name); void removeAttribute(String name);
boolean isFetched(String association);
} }

View file

@ -77,6 +77,15 @@ public interface KeycloakSession {
Object getAttribute(String attribute); Object getAttribute(String attribute);
<T> T getAttribute(String attribute, Class<T> clazz); <T> T getAttribute(String attribute, Class<T> clazz);
default <T> T getAttributeOrDefault(String attribute, T defaultValue) {
T value = (T) getAttribute(attribute);
if (value == null) {
return defaultValue;
}
return value;
}
Object removeAttribute(String attribute); Object removeAttribute(String attribute);
void setAttribute(String name, Object value); void setAttribute(String name, Object value);

View file

@ -33,6 +33,8 @@ public interface UserModel extends RoleMapperModel {
String FIRST_NAME = "firstName"; String FIRST_NAME = "firstName";
String EMAIL = "email"; String EMAIL = "email";
String LOCALE = "locale"; String LOCALE = "locale";
String INCLUDE_SERVICE_ACCOUNT = "keycloak.session.realm.users.query.include_service_account";
String GROUPS = "keycloak.session.realm.users.query.groups";
interface UserRemovedEvent extends ProviderEvent { interface UserRemovedEvent extends ProviderEvent {
RealmModel getRealm(); RealmModel getRealm();

View file

@ -35,6 +35,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.UserPermissionEvaluator;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
@ -47,7 +49,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -55,6 +57,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Base resource for managing users * Base resource for managing users
@ -181,12 +184,13 @@ public class UsersResource {
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults, @QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") Boolean briefRepresentation) { @QueryParam("briefRepresentation") Boolean briefRepresentation) {
auth.users().requireQuery(); UserPermissionEvaluator userPermissionEvaluator = auth.users();
userPermissionEvaluator.requireQuery();
firstResult = firstResult != null ? firstResult : -1; firstResult = firstResult != null ? firstResult : -1;
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS; maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
List<UserRepresentation> results = new ArrayList<UserRepresentation>();
List<UserModel> userModels = Collections.emptyList(); List<UserModel> userModels = Collections.emptyList();
if (search != null) { if (search != null) {
if (search.startsWith(SEARCH_ID_PARAMETER)) { if (search.startsWith(SEARCH_ID_PARAMETER)) {
@ -198,7 +202,7 @@ public class UsersResource {
userModels = session.users().searchForUser(search.trim(), realm, firstResult, maxResults); userModels = session.users().searchForUser(search.trim(), realm, firstResult, maxResults);
} }
} else if (last != null || first != null || email != null || username != null) { } else if (last != null || first != null || email != null || username != null) {
Map<String, String> attributes = new HashMap<String, String>(); Map<String, String> attributes = new HashMap<>();
if (last != null) { if (last != null) {
attributes.put(UserModel.LAST_NAME, last); attributes.put(UserModel.LAST_NAME, last);
} }
@ -211,22 +215,12 @@ public class UsersResource {
if (username != null) { if (username != null) {
attributes.put(UserModel.USERNAME, username); attributes.put(UserModel.USERNAME, username);
} }
userModels = session.users().searchForUser(attributes, realm, firstResult, maxResults); return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, true);
} else { } else {
userModels = session.users().getUsers(realm, firstResult, maxResults, false); return searchForUser(new HashMap<>(), realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
} }
boolean canViewGlobal = auth.users().canView(); return toRepresentation(realm, userPermissionEvaluator, briefRepresentation, userModels);
boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
for (UserModel user : userModels) {
if (!canViewGlobal && !auth.users().canView(user)) continue;
UserRepresentation userRep = briefRepresentationB
? ModelToRepresentation.toBriefRepresentation(user)
: ModelToRepresentation.toRepresentation(session, realm, user);
userRep.setAccess(auth.users().getAccess(user));
results.add(userRep);
}
return results;
} }
@Path("count") @Path("count")
@ -238,4 +232,42 @@ public class UsersResource {
return session.users().getUsersCount(realm); return session.users().getUsersCount(realm);
} }
private List<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts);
if (!auth.users().canView()) {
Set<String> groupModels = auth.groups().getGroupsWithViewPermission();
if (!groupModels.isEmpty()) {
session.setAttribute(UserModel.GROUPS, groupModels);
}
}
List<UserModel> userModels = session.users().searchForUser(attributes, realm, firstResult, maxResults);
return toRepresentation(realm, usersEvaluator, briefRepresentation, userModels);
}
private List<UserRepresentation> toRepresentation(RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, List<UserModel> userModels) {
boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
List<UserRepresentation> results = new ArrayList<>();
boolean canViewGlobal = usersEvaluator.canView();
usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
for (UserModel user : userModels) {
if (!canViewGlobal) {
if (!usersEvaluator.canView(user)) {
continue;
}
}
UserRepresentation userRep = briefRepresentationB
? ModelToRepresentation.toBriefRepresentation(user)
: ModelToRepresentation.toRepresentation(session, realm, user);
userRep.setAccess(usersEvaluator.getAccess(user));
results.add(userRep);
}
return results;
}
} }

View file

@ -342,7 +342,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
}; };
return root.evaluatePermission(resource, scope, server, context); return root.evaluatePermission(resource, server, context, scope);
} }
return true; return true;
} }
@ -376,7 +376,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = manageScope(server); Scope scope = manageScope(server);
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override
@ -404,7 +404,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = configureScope(server); Scope scope = configureScope(server);
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override
public void requireConfigure(ClientModel client) { public void requireConfigure(ClientModel client) {
@ -450,7 +450,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = viewScope(server); Scope scope = viewScope(server);
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override
@ -529,7 +529,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = mapRolesScope(server); Scope scope = mapRolesScope(server);
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override
@ -606,7 +606,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId()); Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_COMPOSITE_SCOPE, server.getId());
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override
public boolean canMapClientScopeRoles(ClientModel client) { public boolean canMapClientScopeRoles(ClientModel client) {
@ -628,7 +628,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
} }
Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId()); Scope scope = authz.getStoreFactory().getScopeStore().findByName(MAP_ROLES_CLIENT_SCOPE, server.getId());
return root.evaluatePermission(resource, scope, server); return root.evaluatePermission(resource, server, scope);
} }
@Override @Override

View file

@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin.permissions;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -45,17 +46,17 @@ public interface GroupPermissionEvaluator {
void requireView(); void requireView();
boolean canViewMembers(GroupModel group); boolean getGroupsWithViewPermission(GroupModel group);
void requireViewMembers(GroupModel group); void requireViewMembers(GroupModel group);
boolean canManageMembers(GroupModel group); boolean canManageMembers(GroupModel group);
void requireManageMembers(GroupModel group);
boolean canManageMembership(GroupModel group); boolean canManageMembership(GroupModel group);
void requireManageMembership(GroupModel group); void requireManageMembership(GroupModel group);
Map<String, Boolean> getAccess(GroupModel group); Map<String, Boolean> getAccess(GroupModel group);
Set<String> getGroupsWithViewPermission();
} }

View file

@ -16,21 +16,27 @@
*/ */
package org.keycloak.services.resources.admin.permissions; package org.keycloak.services.resources.admin.permissions;
import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.models.RealmModel;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -39,45 +45,46 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement { class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManagement {
private static final Logger logger = Logger.getLogger(GroupPermissions.class);
public static final String MAP_ROLE_SCOPE = "map-role";
public static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership";
public static final String MANAGE_MEMBERS_SCOPE = "manage-members";
public static final String VIEW_MEMBERS_SCOPE = "view-members";
protected final KeycloakSession session;
protected final RealmModel realm;
protected final AuthorizationProvider authz;
protected final MgmtPermissions root;
public GroupPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { private static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership";
this.session = session; private static final String MANAGE_MEMBERS_SCOPE = "manage-members";
this.realm = realm; private static final String VIEW_MEMBERS_SCOPE = "view-members";
private static final String RESOURCE_NAME_PREFIX = "group.resource.";
private final AuthorizationProvider authz;
private final MgmtPermissions root;
private final ResourceStore resourceStore;
private final PolicyStore policyStore;
GroupPermissions(AuthorizationProvider authz, MgmtPermissions root) {
this.authz = authz; this.authz = authz;
this.root = root; this.root = root;
resourceStore = authz.getStoreFactory().getResourceStore();
policyStore = authz.getStoreFactory().getPolicyStore();
} }
private static String getGroupResourceName(GroupModel group) { private static String getGroupResourceName(GroupModel group) {
return "group.resource." + group.getId(); return RESOURCE_NAME_PREFIX + group.getId();
} }
public static String getManagePermissionGroup(GroupModel group) { private static String getManagePermissionGroup(GroupModel group) {
return "manage.permission.group." + group.getId(); return "manage.permission.group." + group.getId();
} }
public static String getManageMembersPermissionGroup(GroupModel group) { private static String getManageMembersPermissionGroup(GroupModel group) {
return "manage.members.permission.group." + group.getId(); return "manage.members.permission.group." + group.getId();
} }
public static String getManageMembershipPermissionGroup(GroupModel group) { private static String getManageMembershipPermissionGroup(GroupModel group) {
return "manage.membership.permission.group." + group.getId(); return "manage.membership.permission.group." + group.getId();
} }
public static String getViewPermissionGroup(GroupModel group) { private static String getViewPermissionGroup(GroupModel group) {
return "view.permission.group." + group.getId(); return "view.permission.group." + group.getId();
} }
public static String getViewMembersPermissionGroup(GroupModel group) { private static String getViewMembersPermissionGroup(GroupModel group) {
return "view.members.permission.group." + group.getId(); return "view.members.permission.group." + group.getId();
} }
@ -92,9 +99,9 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
Scope manageMembershipScope = root.initializeRealmScope(MANAGE_MEMBERSHIP_SCOPE); Scope manageMembershipScope = root.initializeRealmScope(MANAGE_MEMBERSHIP_SCOPE);
String groupResourceName = getGroupResourceName(group); String groupResourceName = getGroupResourceName(group);
Resource groupResource = authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId()); Resource groupResource = resourceStore.findByName(groupResourceName, server.getId());
if (groupResource == null) { if (groupResource == null) {
groupResource = authz.getStoreFactory().getResourceStore().create(groupResourceName, server, server.getId()); groupResource = resourceStore.create(groupResourceName, server, server.getId());
Set<Scope> scopeset = new HashSet<>(); Set<Scope> scopeset = new HashSet<>();
scopeset.add(manageScope); scopeset.add(manageScope);
scopeset.add(viewScope); scopeset.add(viewScope);
@ -102,29 +109,30 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
scopeset.add(manageMembershipScope); scopeset.add(manageMembershipScope);
scopeset.add(manageMembersScope); scopeset.add(manageMembersScope);
groupResource.updateScopes(scopeset); groupResource.updateScopes(scopeset);
groupResource.setType("Group");
} }
String managePermissionName = getManagePermissionGroup(group); String managePermissionName = getManagePermissionGroup(group);
Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId()); Policy managePermission = policyStore.findByName(managePermissionName, server.getId());
if (managePermission == null) { if (managePermission == null) {
Helper.addEmptyScopePermission(authz, server, managePermissionName, groupResource, manageScope); Helper.addEmptyScopePermission(authz, server, managePermissionName, groupResource, manageScope);
} }
String viewPermissionName = getViewPermissionGroup(group); String viewPermissionName = getViewPermissionGroup(group);
Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId()); Policy viewPermission = policyStore.findByName(viewPermissionName, server.getId());
if (viewPermission == null) { if (viewPermission == null) {
Helper.addEmptyScopePermission(authz, server, viewPermissionName, groupResource, viewScope); Helper.addEmptyScopePermission(authz, server, viewPermissionName, groupResource, viewScope);
} }
String manageMembersPermissionName = getManageMembersPermissionGroup(group); String manageMembersPermissionName = getManageMembersPermissionGroup(group);
Policy manageMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId()); Policy manageMembersPermission = policyStore.findByName(manageMembersPermissionName, server.getId());
if (manageMembersPermission == null) { if (manageMembersPermission == null) {
Helper.addEmptyScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope); Helper.addEmptyScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope);
} }
String viewMembersPermissionName = getViewMembersPermissionGroup(group); String viewMembersPermissionName = getViewMembersPermissionGroup(group);
Policy viewMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId()); Policy viewMembersPermission = policyStore.findByName(viewMembersPermissionName, server.getId());
if (viewMembersPermission == null) { if (viewMembersPermission == null) {
Helper.addEmptyScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope); Helper.addEmptyScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope);
} }
String manageMembershipPermissionName = getManageMembershipPermissionGroup(group); String manageMembershipPermissionName = getManageMembershipPermissionGroup(group);
Policy manageMembershipPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId()); Policy manageMembershipPermission = policyStore.findByName(manageMembershipPermissionName, server.getId());
if (manageMembershipPermission == null) { if (manageMembershipPermission == null) {
Helper.addEmptyScopePermission(authz, server, manageMembershipPermissionName, groupResource, manageMembershipScope); Helper.addEmptyScopePermission(authz, server, manageMembershipPermissionName, groupResource, manageMembershipScope);
} }
@ -143,21 +151,12 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
} }
} }
@Override @Override
public boolean isPermissionsEnabled(GroupModel group) { public boolean isPermissionsEnabled(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return false; if (server == null) return false;
return authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()) != null; return resourceStore.findByName(getGroupResourceName(group), server.getId()) != null;
}
private Resource groupResource(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
String groupResourceName = getGroupResourceName(group);
return authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId());
} }
@Override @Override
@ -169,78 +168,46 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
} }
} }
private void deletePermissions(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return;
Policy managePermission = managePermission(group);
if (managePermission != null) {
authz.getStoreFactory().getPolicyStore().delete(managePermission.getId());
}
Policy viewPermission = viewPermission(group);
if (viewPermission != null) {
authz.getStoreFactory().getPolicyStore().delete(viewPermission.getId());
}
Policy manageMembersPermission = manageMembersPermission(group);
if (manageMembersPermission != null) {
authz.getStoreFactory().getPolicyStore().delete(manageMembersPermission.getId());
}
Policy viewMembersPermission = viewMembersPermission(group);
if (viewMembersPermission != null) {
authz.getStoreFactory().getPolicyStore().delete(viewMembersPermission.getId());
}
Policy manageMembershipPermission = manageMembershipPermission(group);
if (manageMembershipPermission != null) {
authz.getStoreFactory().getPolicyStore().delete(manageMembershipPermission.getId());
}
Resource resource = groupResource(group);
if (resource != null) authz.getStoreFactory().getResourceStore().delete(resource.getId());
}
@Override @Override
public Policy viewMembersPermission(GroupModel group) { public Policy viewMembersPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
String viewMembersPermissionName = getViewMembersPermissionGroup(group); return policyStore.findByName(getViewMembersPermissionGroup(group), server.getId());
return authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
} }
@Override @Override
public Policy manageMembersPermission(GroupModel group) { public Policy manageMembersPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
String manageMembersPermissionName = getManageMembersPermissionGroup(group); return policyStore.findByName(getManageMembersPermissionGroup(group), server.getId());
return authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
} }
@Override @Override
public Policy manageMembershipPermission(GroupModel group) { public Policy manageMembershipPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
String manageMembershipPermissionName = getManageMembershipPermissionGroup(group); return policyStore.findByName(getManageMembershipPermissionGroup(group), server.getId());
return authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId());
} }
@Override @Override
public Policy viewPermission(GroupModel group) { public Policy viewPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
String viewPermissionName = getViewPermissionGroup(group); return policyStore.findByName(getViewPermissionGroup(group), server.getId());
return authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
} }
@Override @Override
public Policy managePermission(GroupModel group) { public Policy managePermission(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
String managePermissionName = getManagePermissionGroup(group); return policyStore.findByName(getManagePermissionGroup(group), server.getId());
return authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
} }
@Override @Override
public Resource resource(GroupModel group) { public Resource resource(GroupModel group) {
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); Resource resource = resourceStore.findByName(getGroupResourceName(group), server.getId());
if (resource == null) return null; if (resource == null) return null;
return resource; return resource;
} }
@ -257,35 +224,17 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return scopes; return scopes;
} }
@Override @Override
public boolean canManage(GroupModel group) { public boolean canManage(GroupModel group) {
if (canManage()) return true; if (canManage()) {
return true;
}
if (!root.isAdminSameRealm()) { if (!root.isAdminSameRealm()) {
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(group, MgmtPermissions.MANAGE_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
if (resource == null) return false;
Policy policy = managePermission(group);
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmManageScope();
return root.evaluatePermission(resource, scope, server);
} }
@Override @Override
@ -294,37 +243,18 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
throw new ForbiddenException(); throw new ForbiddenException();
} }
} }
@Override @Override
public boolean canView(GroupModel group) { public boolean canView(GroupModel group) {
return hasView(group) || canManage(group); if (canView() || canManage()) {
} return true;
}
private boolean hasView(GroupModel group) {
if (canView()) return true;
if (!root.isAdminSameRealm()) { if (!root.isAdminSameRealm()) {
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(group, MgmtPermissions.VIEW_SCOPE, MgmtPermissions.MANAGE_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
if (resource == null) return false;
Policy policy = viewPermission(group);
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then abort
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmViewScope();
return root.evaluatePermission(resource, scope, server);
} }
@Override @Override
@ -357,15 +287,11 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
} }
} }
@Override @Override
public boolean canViewMembers(GroupModel group) { public boolean getGroupsWithViewPermission(GroupModel group) {
return canViewMembersEvaluation(group) || canManageMembers(group); if (root.users().canView() || root.users().canManage()) {
} return true;
}
private boolean canViewMembersEvaluation(GroupModel group) {
if (root.users().canView()) return true;
if (!root.isAdminSameRealm()) { if (!root.isAdminSameRealm()) {
return false; return false;
@ -374,34 +300,41 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return false; if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); return hasPermission(group, VIEW_MEMBERS_SCOPE, MANAGE_MEMBERS_SCOPE);
if (resource == null) return false;
Policy policy = viewMembersPermission(group);
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = authz.getStoreFactory().getScopeStore().findByName(VIEW_MEMBERS_SCOPE, server.getId());
return root.evaluatePermission(resource, scope, server);
} }
@Override
public Set<String> getGroupsWithViewPermission() {
if (root.users().canView() || root.users().canManage()) return Collections.emptySet();
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}
ResourceServer server = root.realmResourceServer();
if (server == null) {
return Collections.emptySet();
}
Set<String> granted = new HashSet<>();
resourceStore.findByType("Group", server.getId(), resource -> {
if (hasPermission(resource, null, VIEW_MEMBERS_SCOPE, MANAGE_MEMBERS_SCOPE)) {
granted.add(resource.getName().substring(RESOURCE_NAME_PREFIX.length()));
}
});
return granted;
}
@Override @Override
public void requireViewMembers(GroupModel group) { public void requireViewMembers(GroupModel group) {
if (!canViewMembers(group)) { if (!getGroupsWithViewPermission(group)) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
} }
@Override @Override
public boolean canManageMembers(GroupModel group) { public boolean canManageMembers(GroupModel group) {
if (root.users().canManage()) return true; if (root.users().canManage()) return true;
@ -413,29 +346,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return false; if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId()); return hasPermission(group, MANAGE_MEMBERS_SCOPE);
if (resource == null) return false;
Policy policy = manageMembersPermission(group);
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = authz.getStoreFactory().getScopeStore().findByName(MANAGE_MEMBERS_SCOPE, server.getId());
return root.evaluatePermission(resource, scope, server);
}
@Override
public void requireManageMembers(GroupModel group) {
if (!canManageMembers(group)) {
throw new ForbiddenException();
}
} }
@Override @Override
@ -446,25 +357,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(group, MANAGE_MEMBERSHIP_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
if (resource == null) return false;
Policy policy = manageMembershipPermission(group);
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = authz.getStoreFactory().getScopeStore().findByName(MANAGE_MEMBERSHIP_SCOPE, server.getId());
return root.evaluatePermission(resource, scope, server);
} }
@Override @Override
@ -483,7 +376,81 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return map; return map;
} }
private boolean hasPermission(GroupModel group, String... scopes) {
return hasPermission(group, null, scopes);
}
private boolean hasPermission(GroupModel group, EvaluationContext context, String... scopes) {
ResourceServer server = root.realmResourceServer();
if (server == null) {
return false;
}
Resource resource = resourceStore.findByName(getGroupResourceName(group), server.getId());
if (resource == null) {
return false;
}
return hasPermission(resource, context, scopes);
}
private boolean hasPermission(Resource resource, EvaluationContext context, String... scopes) {
ResourceServer server = root.realmResourceServer();
Collection<Permission> permissions;
if (context == null) {
permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
} else {
permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server, context);
}
List<String> expectedScopes = Arrays.asList(scopes);
for (Permission permission : permissions) {
for (String scope : permission.getScopes()) {
if (expectedScopes.contains(scope)) {
return true;
}
}
}
return false;
}
private Resource groupResource(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
String groupResourceName = getGroupResourceName(group);
return resourceStore.findByName(groupResourceName, server.getId());
}
private void deletePermissions(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return;
Policy managePermission = managePermission(group);
if (managePermission != null) {
policyStore.delete(managePermission.getId());
}
Policy viewPermission = viewPermission(group);
if (viewPermission != null) {
policyStore.delete(viewPermission.getId());
}
Policy manageMembersPermission = manageMembersPermission(group);
if (manageMembersPermission != null) {
policyStore.delete(manageMembersPermission.getId());
}
Policy viewMembersPermission = viewMembersPermission(group);
if (viewMembersPermission != null) {
policyStore.delete(viewMembersPermission.getId());
}
Policy manageMembershipPermission = manageMembershipPermission(group);
if (manageMembershipPermission != null) {
policyStore.delete(manageMembershipPermission.getId());
}
Resource resource = groupResource(group);
if (resource != null) resourceStore.delete(resource.getId());
}
} }

View file

@ -189,7 +189,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
} }
}; };
return root.evaluatePermission(resource, scope, server, context); return root.evaluatePermission(resource, server, context, scope);
} }
return true; return true;
} }

View file

@ -28,10 +28,8 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
@ -39,11 +37,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.AdminAuth; import org.keycloak.services.resources.admin.AdminAuth;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -205,7 +206,7 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
@Override @Override
public UserPermissions users() { public UserPermissions users() {
if (users != null) return users; if (users != null) return users;
users = new UserPermissions(session, realm, authz, this); users = new UserPermissions(session, authz, this);
return users; return users;
} }
@ -233,7 +234,7 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
@Override @Override
public GroupPermissions groups() { public GroupPermissions groups() {
if (groups != null) return groups; if (groups != null) return groups;
groups = new GroupPermissions(session, realm, authz, this); groups = new GroupPermissions(authz, this);
return groups; return groups;
} }
@ -313,25 +314,36 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
return authz.getStoreFactory().getScopeStore().findByName(scope, server.getId()); return authz.getStoreFactory().getScopeStore().findByName(scope, server.getId());
} }
public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer) { public boolean evaluatePermission(Resource resource, ResourceServer resourceServer, Scope... scope) {
Identity identity = identity(); Identity identity = identity();
if (identity == null) { if (identity == null) {
throw new RuntimeException("Identity of admin is not set for permission query"); throw new RuntimeException("Identity of admin is not set for permission query");
} }
return evaluatePermission(resource, scope, resourceServer, identity); return evaluatePermission(resource, resourceServer, identity, scope);
} }
public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, Identity identity) { public Collection<Permission> evaluatePermission(ResourcePermission permission, ResourceServer resourceServer) {
return evaluatePermission(permission, resourceServer, new DefaultEvaluationContext(identity, session));
}
public Collection<Permission> evaluatePermission(ResourcePermission permission, ResourceServer resourceServer, EvaluationContext context) {
return evaluatePermission(Arrays.asList(permission), resourceServer, context);
}
public boolean evaluatePermission(Resource resource, ResourceServer resourceServer, Identity identity, Scope... scope) {
EvaluationContext context = new DefaultEvaluationContext(identity, session); EvaluationContext context = new DefaultEvaluationContext(identity, session);
return evaluatePermission(resource, scope, resourceServer, context); return evaluatePermission(resource, resourceServer, context, scope);
} }
public boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, EvaluationContext context) { public boolean evaluatePermission(Resource resource, ResourceServer resourceServer, EvaluationContext context, Scope... scope) {
return !evaluatePermission(Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), resourceServer)), resourceServer, context).isEmpty();
}
public Collection<Permission> evaluatePermission(List<ResourcePermission> permissions, ResourceServer resourceServer, EvaluationContext context) {
RealmModel oldRealm = session.getContext().getRealm(); RealmModel oldRealm = session.getContext().getRealm();
try { try {
session.getContext().setRealm(realm); session.getContext().setRealm(realm);
ResourcePermission permission = Permissions.permission(resourceServer, resource, scope); return authz.evaluators().from(permissions, context).evaluate(resourceServer, null);
return !authz.evaluators().from(Arrays.asList(permission), context).evaluate(resourceServer, null).isEmpty();
} finally { } finally {
session.getContext().setRealm(oldRealm); session.getContext().setRealm(oldRealm);
} }

View file

@ -307,7 +307,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role); Resource roleResource = resource(role);
Scope mapRoleScope = mapRoleScope(resourceServer); Scope mapRoleScope = mapRoleScope(resourceServer);
if (root.evaluatePermission(roleResource, mapRoleScope, resourceServer)) { if (root.evaluatePermission(roleResource, resourceServer, mapRoleScope)) {
return checkAdminRoles(role); return checkAdminRoles(role);
} else { } else {
return false; return false;
@ -391,7 +391,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role); Resource roleResource = resource(role);
Scope scope = mapCompositeScope(resourceServer); Scope scope = mapCompositeScope(resourceServer);
if (root.evaluatePermission(roleResource, scope, resourceServer)) { if (root.evaluatePermission(roleResource, resourceServer, scope)) {
return checkAdminRoles(role); return checkAdminRoles(role);
} else { } else {
return false; return false;
@ -430,7 +430,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role); Resource roleResource = resource(role);
Scope scope = mapClientScope(resourceServer); Scope scope = mapClientScope(resourceServer);
return root.evaluatePermission(roleResource, scope, resourceServer); return root.evaluatePermission(roleResource, resourceServer, scope);
} }
@Override @Override

View file

@ -16,7 +16,6 @@
*/ */
package org.keycloak.services.resources.admin.permissions; package org.keycloak.services.resources.admin.permissions;
import org.keycloak.authorization.model.Policy;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import java.util.Map; import java.util.Map;
@ -26,42 +25,30 @@ import java.util.Map;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface UserPermissionEvaluator { public interface UserPermissionEvaluator {
boolean canManage();
void requireManage(); void requireManage();
boolean canManage(UserModel user);
void requireManage(UserModel user); void requireManage(UserModel user);
boolean canManage();
boolean canQuery(); boolean canManage(UserModel user);
void requireQuery(); void requireQuery();
boolean canQuery();
boolean canQuery(UserModel user);
void requireQuery(UserModel user);
boolean canView();
boolean canView(UserModel user);
void requireView(UserModel user);
void requireView(); void requireView();
void requireView(UserModel user);
boolean canImpersonate(UserModel user); boolean canView();
boolean canView(UserModel user);
boolean isImpersonatable(UserModel user);
boolean canImpersonate();
void requireImpersonate(UserModel user); void requireImpersonate(UserModel user);
boolean canImpersonate();
boolean canImpersonate(UserModel user);
boolean isImpersonatable(UserModel user);
Map<String, Boolean> getAccess(UserModel user); Map<String, Boolean> getAccess(UserModel user);
void requireMapRoles(UserModel user);
boolean canMapRoles(UserModel user); boolean canMapRoles(UserModel user);
void requireMapRoles(UserModel user);
boolean canManageGroupMembership(UserModel user);
void requireManageGroupMembership(UserModel user); void requireManageGroupMembership(UserModel user);
boolean canManageGroupMembership(UserModel user);
void grantIfNoPermission(boolean grantIfNoPermission);
} }

View file

@ -16,7 +16,6 @@
*/ */
package org.keycloak.services.resources.admin.permissions; package org.keycloak.services.resources.admin.permissions;
import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.ClientModelIdentity; import org.keycloak.authorization.common.ClientModelIdentity;
import org.keycloak.authorization.common.DefaultEvaluationContext; import org.keycloak.authorization.common.DefaultEvaluationContext;
@ -26,23 +25,30 @@ import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.PolicyStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.models.ImpersonationConstants; import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
/** /**
* Manages default policies for all users. * Manages default policies for all users.
@ -52,28 +58,32 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
class UserPermissions implements UserPermissionEvaluator, UserPermissionManagement { class UserPermissions implements UserPermissionEvaluator, UserPermissionManagement {
private static final Logger logger = Logger.getLogger(UserPermissions.class);
public static final String MAP_ROLES_SCOPE="map-roles";
public static final String IMPERSONATE_SCOPE="impersonate";
public static final String USER_IMPERSONATED_SCOPE="user-impersonated";
public static final String MANAGE_GROUP_MEMBERSHIP_SCOPE="manage-group-membership";
public static final String MAP_ROLES_PERMISSION_USERS = "map-roles.permission.users";
public static final String ADMIN_IMPERSONATING_PERMISSION = "admin-impersonating.permission.users";
public static final String USER_IMPERSONATED_PERMISSION = "user-impersonated.permission.users";
public static final String MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS = "manage-group-membership.permission.users";
public static final String MANAGE_PERMISSION_USERS = "manage.permission.users";
public static final String VIEW_PERMISSION_USERS = "view.permission.users";
public static final String USERS_RESOURCE = "Users";
protected final KeycloakSession session;
protected final RealmModel realm;
protected final AuthorizationProvider authz;
protected final MgmtPermissions root;
public UserPermissions(KeycloakSession session, RealmModel realm, AuthorizationProvider authz, MgmtPermissions root) { private static final String MAP_ROLES_SCOPE="map-roles";
private static final String IMPERSONATE_SCOPE="impersonate";
private static final String USER_IMPERSONATED_SCOPE="user-impersonated";
private static final String MANAGE_GROUP_MEMBERSHIP_SCOPE="manage-group-membership";
private static final String MAP_ROLES_PERMISSION_USERS = "map-roles.permission.users";
private static final String ADMIN_IMPERSONATING_PERMISSION = "admin-impersonating.permission.users";
private static final String USER_IMPERSONATED_PERMISSION = "user-impersonated.permission.users";
private static final String MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS = "manage-group-membership.permission.users";
private static final String MANAGE_PERMISSION_USERS = "manage.permission.users";
private static final String VIEW_PERMISSION_USERS = "view.permission.users";
private static final String USERS_RESOURCE = "Users";
private final KeycloakSession session;
private final AuthorizationProvider authz;
private final MgmtPermissions root;
private final PolicyStore policyStore;
private final ResourceStore resourceStore;
private boolean grantIfNoPermission = false;
UserPermissions(KeycloakSession session, AuthorizationProvider authz, MgmtPermissions root) {
this.session = session; this.session = session;
this.realm = realm;
this.authz = authz; this.authz = authz;
this.root = root; this.root = root;
policyStore = authz.getStoreFactory().getPolicyStore();
resourceStore = authz.getStoreFactory().getResourceStore();
} }
@ -88,9 +98,9 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
Scope userImpersonatedScope = root.initializeRealmScope(USER_IMPERSONATED_SCOPE); Scope userImpersonatedScope = root.initializeRealmScope(USER_IMPERSONATED_SCOPE);
Scope manageGroupMembershipScope = root.initializeRealmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE); Scope manageGroupMembershipScope = root.initializeRealmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE);
Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); Resource usersResource = resourceStore.findByName(USERS_RESOURCE, server.getId());
if (usersResource == null) { if (usersResource == null) {
usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getId()); usersResource = resourceStore.create(USERS_RESOURCE, server, server.getId());
Set<Scope> scopeset = new HashSet<>(); Set<Scope> scopeset = new HashSet<>();
scopeset.add(manageScope); scopeset.add(manageScope);
scopeset.add(viewScope); scopeset.add(viewScope);
@ -100,27 +110,27 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
scopeset.add(userImpersonatedScope); scopeset.add(userImpersonatedScope);
usersResource.updateScopes(scopeset); usersResource.updateScopes(scopeset);
} }
Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId()); Policy managePermission = policyStore.findByName(MANAGE_PERMISSION_USERS, server.getId());
if (managePermission == null) { if (managePermission == null) {
Helper.addEmptyScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope); Helper.addEmptyScopePermission(authz, server, MANAGE_PERMISSION_USERS, usersResource, manageScope);
} }
Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId()); Policy viewPermission = policyStore.findByName(VIEW_PERMISSION_USERS, server.getId());
if (viewPermission == null) { if (viewPermission == null) {
Helper.addEmptyScopePermission(authz, server, VIEW_PERMISSION_USERS, usersResource, viewScope); Helper.addEmptyScopePermission(authz, server, VIEW_PERMISSION_USERS, usersResource, viewScope);
} }
Policy mapRolesPermission = authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId()); Policy mapRolesPermission = policyStore.findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
if (mapRolesPermission == null) { if (mapRolesPermission == null) {
Helper.addEmptyScopePermission(authz, server, MAP_ROLES_PERMISSION_USERS, usersResource, mapRolesScope); Helper.addEmptyScopePermission(authz, server, MAP_ROLES_PERMISSION_USERS, usersResource, mapRolesScope);
} }
Policy membershipPermission = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId()); Policy membershipPermission = policyStore.findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
if (membershipPermission == null) { if (membershipPermission == null) {
Helper.addEmptyScopePermission(authz, server, MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, usersResource, manageGroupMembershipScope); Helper.addEmptyScopePermission(authz, server, MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, usersResource, manageGroupMembershipScope);
} }
Policy impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId()); Policy impersonatePermission = policyStore.findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
if (impersonatePermission == null) { if (impersonatePermission == null) {
Helper.addEmptyScopePermission(authz, server, ADMIN_IMPERSONATING_PERMISSION, usersResource, impersonateScope); Helper.addEmptyScopePermission(authz, server, ADMIN_IMPERSONATING_PERMISSION, usersResource, impersonateScope);
} }
impersonatePermission = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId()); impersonatePermission = policyStore.findByName(USER_IMPERSONATED_PERMISSION, server.getId());
if (impersonatePermission == null) { if (impersonatePermission == null) {
Helper.addEmptyScopePermission(authz, server, USER_IMPERSONATED_PERMISSION, usersResource, userImpersonatedScope); Helper.addEmptyScopePermission(authz, server, USER_IMPERSONATED_PERMISSION, usersResource, userImpersonatedScope);
} }
@ -144,7 +154,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return false; if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); Resource resource = resourceStore.findByName(USERS_RESOURCE, server.getId());
if (resource == null) return false; if (resource == null) return false;
Policy policy = managePermission(); Policy policy = managePermission();
@ -161,45 +171,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
} }
} }
private void deletePermissionSetup() {
ResourceServer server = root.realmResourceServer();
if (server == null) return;
Policy policy = managePermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
policy = viewPermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
policy = mapRolesPermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
policy = manageGroupMembershipPermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
policy = adminImpersonatingPermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
policy = userImpersonatedPermission();
if (policy != null) {
authz.getStoreFactory().getPolicyStore().delete(policy.getId());
}
Resource usersResource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (usersResource != null) {
authz.getStoreFactory().getResourceStore().delete(usersResource.getId());
}
}
public boolean canManageDefault() { public boolean canManageDefault() {
return root.hasOneAdminRole(AdminRoles.MANAGE_USERS); return root.hasOneAdminRole(AdminRoles.MANAGE_USERS);
} }
@ -209,48 +180,40 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return null; if (server == null) return null;
return authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); return resourceStore.findByName(USERS_RESOURCE, server.getId());
} }
@Override @Override
public Policy managePermission() { public Policy managePermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(MANAGE_PERMISSION_USERS, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
} }
@Override @Override
public Policy viewPermission() { public Policy viewPermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(VIEW_PERMISSION_USERS, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
} }
@Override @Override
public Policy manageGroupMembershipPermission() { public Policy manageGroupMembershipPermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
} }
@Override @Override
public Policy mapRolesPermission() { public Policy mapRolesPermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(MAP_ROLES_PERMISSION_USERS, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
} }
@Override @Override
public Policy adminImpersonatingPermission() { public Policy adminImpersonatingPermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(ADMIN_IMPERSONATING_PERMISSION, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
} }
@Override @Override
public Policy userImpersonatedPermission() { public Policy userImpersonatedPermission() {
ResourceServer server = root.realmResourceServer(); return policyStore.findByName(USER_IMPERSONATED_PERMISSION, root.realmResourceServer().getId());
return authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
} }
/** /**
* Is admin allowed to manage all users? In Authz terms, does the admin have the "manage" scope for the Users Authz resource? * Is admin allowed to manage all users? In Authz terms, does the admin have the "manage" scope for the Users Authz resource?
* *
@ -266,31 +229,15 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
*/ */
@Override @Override
public boolean canManage() { public boolean canManage() {
if (canManageDefault()) return true; if (canManageDefault()) {
return true;
}
if (!root.isAdminSameRealm()) { if (!root.isAdminSameRealm()) {
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(MgmtPermissions.MANAGE_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (resource == null) return false;
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmManageScope();
return root.evaluatePermission(resource, scope, server);
} }
@Override @Override
@ -319,63 +266,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
} }
} }
private interface EvaluateGroup {
boolean evaluate(GroupModel group);
}
private boolean evaluateGroups(UserModel user, EvaluateGroup eval) {
for (GroupModel group : user.getGroups()) {
if (eval.evaluate(group)) return true;
}
return false;
}
private boolean evaluateHierarchy(UserModel user, EvaluateGroup eval) {
Set<GroupModel> visited = new HashSet<>();
for (GroupModel group : user.getGroups()) {
if (evaluateHierarchy(eval, group, visited)) return true;
}
return false;
}
private boolean evaluateHierarchy(EvaluateGroup eval, GroupModel group, Set<GroupModel> visited) {
if (visited.contains(group)) return false;
if (eval.evaluate(group)) {
return true;
}
visited.add(group);
if (group.getParent() == null) return false;
return evaluateHierarchy(eval, group.getParent(), visited);
}
private boolean canManageByGroup(UserModel user) {
/* no inheritance
return evaluateGroups(user,
(group) -> root.groups().canViewMembers(group)
);
*/
/* inheritance
*/
return evaluateHierarchy(user, (group) -> root.groups().canManageMembers(group));
}
private boolean canViewByGroup(UserModel user) {
/* no inheritance
return evaluateGroups(user,
(group) -> root.groups().canViewMembers(group)
);
*/
/* inheritance
*/
return evaluateHierarchy(user, (group) -> root.groups().canViewMembers(group));
}
public boolean canViewDefault() {
return root.hasOneAdminRole(AdminRoles.MANAGE_USERS, AdminRoles.VIEW_USERS);
}
@Override @Override
public boolean canQuery() { public boolean canQuery() {
return canView() || root.hasOneAdminRole(AdminRoles.QUERY_USERS); return canView() || root.hasOneAdminRole(AdminRoles.QUERY_USERS);
@ -388,21 +278,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
} }
} }
@Override
public boolean canQuery(UserModel user) {
return canView(user);
}
@Override
public void requireQuery(UserModel user) {
if (!canQuery(user)) {
throw new ForbiddenException();
}
}
/** /**
* Is admin allowed to view all users? In Authz terms, does the admin have the "view" scope for the Users Authz resource? * Is admin allowed to view all users? In Authz terms, does the admin have the "view" scope for the Users Authz resource?
* *
@ -418,34 +293,15 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
*/ */
@Override @Override
public boolean canView() { public boolean canView() {
if (canViewDefault()) return true; if (canViewDefault() || canManageDefault()) {
return true;
}
if (!root.isAdminSameRealm()) { if (!root.isAdminSameRealm()) {
return false; return false;
} }
return hasViewPermission() || canManage(); return hasPermission(MgmtPermissions.VIEW_SCOPE, MgmtPermissions.MANAGE_SCOPE);
}
private boolean hasViewPermission() {
ResourceServer server = root.realmResourceServer();
if (server == null) return canViewDefault();
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (resource == null) return canViewDefault();
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
if (policy == null) {
return canViewDefault();
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return canViewDefault();
}
Scope scope = root.realmViewScope();
return root.evaluatePermission(resource, scope, server);
} }
/** /**
@ -505,15 +361,20 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override @Override
public boolean isImpersonatable(UserModel user) { public boolean isImpersonatable(UserModel user) {
Identity userIdentity = new UserModelIdentity(root.realm, user);
ResourceServer server = root.realmResourceServer(); ResourceServer server = root.realmResourceServer();
if (server == null) return true;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); if (server == null) {
if (resource == null) return true; return true;
}
Resource resource = resourceStore.findByName(USERS_RESOURCE, server.getId());
if (resource == null) {
return true;
}
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId()); Policy policy = authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
if (policy == null) { if (policy == null) {
return true; return true;
} }
@ -524,8 +385,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return true; return true;
} }
Scope scope = root.realmScope(USER_IMPERSONATED_SCOPE); return hasPermission(new DefaultEvaluationContext(new UserModelIdentity(root.realm, user), session), USER_IMPERSONATED_SCOPE);
return root.evaluatePermission(resource, scope, server, userIdentity);
} }
@Override @Override
@ -538,31 +398,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false; return false;
} }
EvaluationContext context = new DefaultEvaluationContext(identity, session); return canImpersonate(new DefaultEvaluationContext(identity, session));
return canImpersonate(context);
}
protected boolean canImpersonate(EvaluationContext context) {
ResourceServer server = root.realmResourceServer();
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (resource == null) return false;
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmScope(IMPERSONATE_SCOPE);
return root.evaluatePermission(resource, scope, server, context);
} }
@Override @Override
@ -591,25 +427,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(MAP_ROLES_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
if (resource == null) return false;
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmScope(MAP_ROLES_SCOPE);
return root.evaluatePermission(resource, scope, server);
} }
@ -621,7 +439,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
} }
@Override @Override
public boolean canManageGroupMembership(UserModel user) { public boolean canManageGroupMembership(UserModel user) {
if (canManage(user)) return true; if (canManage(user)) return true;
@ -630,26 +447,13 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false; return false;
} }
ResourceServer server = root.realmResourceServer(); return hasPermission(MANAGE_GROUP_MEMBERSHIP_SCOPE);
if (server == null) return false;
Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId()); }
if (resource == null) return false;
Policy policy = authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
if (policy == null) {
return false;
}
Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
// if no policies attached to permission then just do default behavior
if (associatedPolicies == null || associatedPolicies.isEmpty()) {
return false;
}
Scope scope = root.realmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE);
return root.evaluatePermission(resource, scope, server);
@Override
public void grantIfNoPermission(boolean grantIfNoPermission) {
this.grantIfNoPermission = grantIfNoPermission;
} }
@Override @Override
@ -660,9 +464,113 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
} }
private boolean hasPermission(String... scopes) {
return hasPermission(null, scopes);
}
private boolean hasPermission(EvaluationContext context, String... scopes) {
ResourceServer server = root.realmResourceServer();
if (server == null) {
return false;
}
Resource resource = resourceStore.findByName(USERS_RESOURCE, server.getId());
List<String> expectedScopes = Arrays.asList(scopes);
if (resource == null) {
return grantIfNoPermission && expectedScopes.contains(MgmtPermissions.MANAGE_SCOPE) && expectedScopes.contains(MgmtPermissions.VIEW_SCOPE);
}
Collection<Permission> permissions;
if (context == null) {
permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server);
} else {
permissions = root.evaluatePermission(new ResourcePermission(resource, resource.getScopes(), server), server, context);
}
for (Permission permission : permissions) {
for (String scope : permission.getScopes()) {
if (expectedScopes.contains(scope)) {
return true;
}
}
}
return false;
}
private void deletePermissionSetup() {
ResourceServer server = root.realmResourceServer();
if (server == null) return;
Policy policy = managePermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
policy = viewPermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
policy = mapRolesPermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
policy = manageGroupMembershipPermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
policy = adminImpersonatingPermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
policy = userImpersonatedPermission();
if (policy != null) {
policyStore.delete(policy.getId());
}
Resource usersResource = resourceStore.findByName(USERS_RESOURCE, server.getId());
if (usersResource != null) {
resourceStore.delete(usersResource.getId());
}
}
private boolean canImpersonate(EvaluationContext context) {
return hasPermission(context, IMPERSONATE_SCOPE);
}
private boolean evaluateHierarchy(UserModel user, Predicate<GroupModel> eval) {
Set<GroupModel> visited = new HashSet<>();
for (GroupModel group : user.getGroups()) {
if (evaluateHierarchy(eval, group, visited)) return true;
}
return false;
}
private boolean evaluateHierarchy(Predicate<GroupModel> eval, GroupModel group, Set<GroupModel> visited) {
if (visited.contains(group)) return false;
if (eval.test(group)) {
return true;
}
visited.add(group);
if (group.getParent() == null) return false;
return evaluateHierarchy(eval, group.getParent(), visited);
}
private boolean canManageByGroup(UserModel user) {
return evaluateHierarchy(user, (group) -> root.groups().canManageMembers(group));
}
private boolean canViewByGroup(UserModel user) {
return evaluateHierarchy(user, (group) -> root.groups().getGroupsWithViewPermission(group));
}
public boolean canViewDefault() {
return root.hasOneAdminRole(AdminRoles.MANAGE_USERS, AdminRoles.VIEW_USERS);
}
} }

View file

@ -190,7 +190,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
@Override @Override
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) { public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
if (attributes.size() != 1) return Collections.EMPTY_LIST;
String username = attributes.get(UserModel.USERNAME); String username = attributes.get(UserModel.USERNAME);
if (username == null) return Collections.EMPTY_LIST; if (username == null) return Collections.EMPTY_LIST;
return searchForUser(username, realm, firstResult, maxResults); return searchForUser(username, realm, firstResult, maxResults);

View file

@ -16,16 +16,19 @@
*/ */
package org.keycloak.testsuite.admin; package org.keycloak.testsuite.admin;
import org.hamcrest.Matchers;
import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.Keycloak;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.client.admin.cli.util.ConfigUtil; import org.keycloak.client.admin.cli.util.ConfigUtil;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.models.*; import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.models.GroupModel; import org.keycloak.models.GroupModel;
import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation;
@ -41,6 +44,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.services.resources.admin.permissions.GroupPermissionManagement;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume; import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
@ -898,6 +902,92 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
Assert.assertNotNull(client.realm("master").roles().get("offline_access")); Assert.assertNotNull(client.realm("master").roles().get("offline_access"));
} }
@Test
public void testUserPagination() {
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test");
session.getContext().setRealm(realm);
GroupModel customerAGroup = session.realms().createGroup(realm, "Customer A");
UserModel customerAManager = session.users().addUser(realm, "customer-a-manager");
session.userCredentialManager().updateCredential(realm, customerAManager, UserCredentialModel.password("password"));
customerAManager.joinGroup(customerAGroup);
ClientModel realmAdminClient = realm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID);
customerAManager.grantRole(realmAdminClient.getRole(AdminRoles.QUERY_USERS));
customerAManager.setEnabled(true);
UserModel regularAdminUser = session.users().addUser(realm, "regular-admin-user");
session.userCredentialManager().updateCredential(realm, regularAdminUser, UserCredentialModel.password("password"));
regularAdminUser.grantRole(realmAdminClient.getRole(AdminRoles.VIEW_USERS));
regularAdminUser.setEnabled(true);
AdminPermissionManagement management = AdminPermissions.management(session, realm);
GroupPermissionManagement groupPermission = management.groups();
groupPermission.setPermissionsEnabled(customerAGroup, true);
UserPolicyRepresentation userPolicyRepresentation = new UserPolicyRepresentation();
userPolicyRepresentation.setName("Only " + customerAManager.getUsername());
userPolicyRepresentation.addUser(customerAManager.getId());
Policy policy = groupPermission.viewMembersPermission(customerAGroup);
AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class);
Policy userPolicy = provider.getStoreFactory().getPolicyStore().create(userPolicyRepresentation, management.realmResourceServer());
policy.addAssociatedPolicy(RepresentationToModel.toModel(userPolicyRepresentation, provider, userPolicy));
for (int i = 0; i < 20; i++) {
UserModel userModel = session.users().addUser(realm, "a" + i);
userModel.setFirstName("test");
}
for (int i = 20; i < 40; i++) {
UserModel userModel = session.users().addUser(realm, "b" + i);
userModel.setFirstName("test");
userModel.joinGroup(customerAGroup);
}
});
Keycloak client = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
"test", "customer-a-manager", "password", Constants.ADMIN_CLI_CLIENT_ID);
List<UserRepresentation> result = client.realm("test").users().search(null, "test", null, null, -1, 20);
Assert.assertEquals(20, result.size());
Assert.assertThat(result, Matchers.everyItem(Matchers.hasProperty("username", Matchers.startsWith("b"))));
result = client.realm("test").users().search(null, "test", null, null, 20, 40);
Assert.assertEquals(0, result.size());
client = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
"test", "regular-admin-user", "password", Constants.ADMIN_CLI_CLIENT_ID);
result = client.realm("test").users().search(null, "test", null, null, -1, 20);
Assert.assertEquals(20, result.size());
Assert.assertThat(result, Matchers.everyItem(Matchers.hasProperty("username", Matchers.startsWith("a"))));
client.realm("test").users().search(null, null, null, null, -1, -1);
Assert.assertEquals(20, result.size());
Assert.assertThat(result, Matchers.everyItem(Matchers.hasProperty("username", Matchers.startsWith("a"))));
client = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
"test", "customer-a-manager", "password", Constants.ADMIN_CLI_CLIENT_ID);
result = client.realm("test").users().search(null, null, null, null, -1, 20);
Assert.assertEquals(20, result.size());
Assert.assertThat(result, Matchers.everyItem(Matchers.hasProperty("username", Matchers.startsWith("b"))));
result = client.realm("test").users().search("a", -1, 20, false);
Assert.assertEquals(0, result.size());
}
} }

View file

@ -223,6 +223,24 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
} }
} }
protected void testMigrationTo4_6_0(boolean supportsAuthzService, boolean checkMigrationData) {
if (supportsAuthzService && checkMigrationData) {
testGroupPolicyTypeFineGrainedAdminPermission();
}
}
private void testGroupPolicyTypeFineGrainedAdminPermission() {
ClientsResource clients = migrationRealm.clients();
ClientRepresentation clientRepresentation = clients.findByClientId("realm-management").get(0);
List<ResourceRepresentation> resources = clients.get(clientRepresentation.getId()).authorization().resources().resources();
assertEquals(5, resources.size());
for (ResourceRepresentation resource : resources) {
assertEquals("Group", resource.getType());
}
}
private void testCliConsoleScopeSize(RealmResource realm) { private void testCliConsoleScopeSize(RealmResource realm) {
ClientRepresentation cli = realm.clients().findByClientId(Constants.ADMIN_CLI_CLIENT_ID).get(0); ClientRepresentation cli = realm.clients().findByClientId(Constants.ADMIN_CLI_CLIENT_ID).get(0);
ClientRepresentation console = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0); ClientRepresentation console = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
@ -543,12 +561,13 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
testMigrationTo3_4_2(); testMigrationTo3_4_2();
} }
protected void testMigrationTo4_x(boolean supportsAuthzServices) { protected void testMigrationTo4_x(boolean supportsAuthzServices, boolean checkMigrationData) {
testMigrationTo4_0_0(); testMigrationTo4_0_0();
testMigrationTo4_2_0(supportsAuthzServices); testMigrationTo4_2_0(supportsAuthzServices);
testMigrationTo4_6_0(supportsAuthzServices, checkMigrationData);
} }
protected void testMigrationTo4_x() { protected void testMigrationTo4_x() {
testMigrationTo4_x(true); testMigrationTo4_x(true, true);
} }
} }

View file

@ -71,7 +71,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo2_5_0(); testMigrationTo2_5_0();
//testMigrationTo2_5_1(); // Offline tokens migration is skipped for JSON //testMigrationTo2_5_1(); // Offline tokens migration is skipped for JSON
testMigrationTo3_x(); testMigrationTo3_x();
testMigrationTo4_x(false); testMigrationTo4_x(false, false);
} }
@Override @Override

View file

@ -65,7 +65,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat
@Test @Test
public void migration2_5_5Test() throws Exception { public void migration2_5_5Test() throws Exception {
testMigrationTo3_x(); testMigrationTo3_x();
testMigrationTo4_x(); testMigrationTo4_x(true, false);
} }
} }

View file

@ -63,7 +63,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat
@Test @Test
public void migration3_4_3Test() throws Exception { public void migration3_4_3Test() throws Exception {
testMigrationTo4_x(); testMigrationTo4_x(true, false);
} }
} }

View file

@ -88,7 +88,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigratedData(false); testMigratedData(false);
testMigrationTo2_x(); testMigrationTo2_x();
testMigrationTo3_x(); testMigrationTo3_x();
testMigrationTo4_x(false); testMigrationTo4_x(false, false);
} }
@Test @Test

View file

@ -858,7 +858,33 @@
} ], } ],
"useTemplateConfig" : false, "useTemplateConfig" : false,
"useTemplateScope" : false, "useTemplateScope" : false,
"useTemplateMappers" : false "useTemplateMappers" : false,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"authorizationSettings": {
"resources": [
{
"name": "group.resource.a",
"scopes": ["view-members"]
},
{
"name": "group.resource.b",
"scopes": ["view-members"]
},
{
"name": "group.resource.c",
"scopes": ["view-members"]
},
{
"name": "group.resource.d",
"scopes": ["view-members"]
},
{
"name": "group.resource.e",
"scopes": ["view-members"]
}
]
}
}, { }, {
"id" : "35d39054-e7e5-4dbb-8948-1738094679e8", "id" : "35d39054-e7e5-4dbb-8948-1738094679e8",
"clientId" : "security-admin-console", "clientId" : "security-admin-console",

View file

@ -235,12 +235,8 @@ public abstract class AbstractIdentityProviderTest {
UserSessionStatus userSessionStatus = retrieveSessionStatus(); UserSessionStatus userSessionStatus = retrieveSessionStatus();
IDToken idToken = userSessionStatus.getIdToken(); IDToken idToken = userSessionStatus.getIdToken();
KeycloakSession samlServerSession = brokerServerRule.startSession(); KeycloakSession samlServerSession = brokerServerRule.startSession();
try { RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker");
RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker"); return samlServerSession.users().getUserById(idToken.getSubject(), brokerRealm);
return samlServerSession.users().getUserById(idToken.getSubject(), brokerRealm);
} finally {
brokerServerRule.stopSession(samlServerSession, false);
}
} }
protected void doAfterProviderAuthentication() { protected void doAfterProviderAuthentication() {