keycloak-memoizeit
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java 73(+51 -22)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java 61(+42 -19)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java 91(+58 -33)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java 46(+27 -19)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 56(+49 -7)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java 47(+47 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java 49(+25 -24)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java 68(+32 -36)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java 14(+7 -7)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java 7(+4 -3)
services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java 341(+154 -187)
services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java 34(+23 -11)
services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java 6(+3 -3)
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java 33(+10 -23)
services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java 444(+176 -268)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java 1(+0 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java 90(+90 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java 23(+21 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java 2(+1 -1)
Details
diff --git a/common/src/main/java/org/keycloak/common/util/MultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/MultivaluedHashMap.java
index 82fede2..f0b3f08 100755
--- a/common/src/main/java/org/keycloak/common/util/MultivaluedHashMap.java
+++ b/common/src/main/java/org/keycloak/common/util/MultivaluedHashMap.java
@@ -33,6 +33,14 @@ public class MultivaluedHashMap<K, V> extends HashMap<K, List<V>>
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) {
addAll(config);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
index 643c868..062668e 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedPolicy.java
@@ -21,13 +21,17 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope;
+import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
+import org.keycloak.models.cache.infinispan.LazyLoader;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -35,16 +39,16 @@ import java.util.stream.Collectors;
*/
public class CachedPolicy extends AbstractRevisioned implements InResourceServer {
- private String type;
- private DecisionStrategy decisionStrategy;
- private Logic logic;
- private Map<String, String> config;
- private String name;
- private String description;
- private String resourceServerId;
- private Set<String> associatedPoliciesIds;
- private Set<String> resourcesIds;
- private Set<String> scopesIds;
+ private final String type;
+ private final DecisionStrategy decisionStrategy;
+ private final Logic logic;
+ private final String name;
+ private final String description;
+ private final String resourceServerId;
+ private final LazyLoader<Policy, Set<String>> associatedPoliciesIds;
+ private final LazyLoader<Policy, Set<String>> resourcesIds;
+ private final LazyLoader<Policy, Set<String>> scopesIds;
+ private final LazyLoader<Policy, Map<String, String>> config;
private final String owner;
public CachedPolicy(Long revision, Policy policy) {
@@ -52,13 +56,38 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
this.type = policy.getType();
this.decisionStrategy = policy.getDecisionStrategy();
this.logic = policy.getLogic();
- this.config = new HashMap(policy.getConfig());
this.name = policy.getName();
this.description = policy.getDescription();
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());
- this.scopesIds = policy.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
+
+ if (policy.isFetched("associatedPolicies")) {
+ 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();
}
@@ -74,8 +103,8 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
return this.logic;
}
- public Map<String, String> getConfig() {
- return this.config;
+ public Map<String, String> getConfig(Supplier<Policy> policy) {
+ return this.config.get(policy);
}
public String getName() {
@@ -86,16 +115,16 @@ public class CachedPolicy extends AbstractRevisioned implements InResourceServer
return this.description;
}
- public Set<String> getAssociatedPoliciesIds() {
- return this.associatedPoliciesIds;
+ public Set<String> getAssociatedPoliciesIds(Supplier<Policy> policy) {
+ return this.associatedPoliciesIds.get(policy);
}
- public Set<String> getResourcesIds() {
- return this.resourcesIds;
+ public Set<String> getResourcesIds(Supplier<Policy> policy) {
+ return this.resourcesIds.get(policy);
}
- public Set<String> getScopesIds() {
- return this.scopesIds;
+ public Set<String> getScopesIds(Supplier<Policy> policy) {
+ return this.scopesIds.get(policy);
}
public String getResourceServerId() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java
index c631279..0205550 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/entities/CachedResource.java
@@ -21,11 +21,16 @@ package org.keycloak.models.cache.infinispan.authorization.entities;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.Scope;
import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.models.cache.infinispan.DefaultLazyLoader;
+import org.keycloak.models.cache.infinispan.LazyLoader;
import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -33,29 +38,47 @@ import java.util.stream.Collectors;
*/
public class CachedResource extends AbstractRevisioned implements InResourceServer {
- private String resourceServerId;
- private String iconUri;
- private String owner;
- private String type;
- private String name;
- private String displayName;
- private Set<String> uris;
- private Set<String> scopesIds;
- private boolean ownerManagedAccess;
- private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+ private final String resourceServerId;
+ private final String iconUri;
+ private final String owner;
+ private final String type;
+ private final String name;
+ private final String displayName;
+ private final boolean ownerManagedAccess;
+ private LazyLoader<Resource, Set<String>> scopesIds;
+ private LazyLoader<Resource, Set<String>> uris;
+ private LazyLoader<Resource, MultivaluedHashMap<String, String>> attributes;
public CachedResource(Long revision, Resource resource) {
super(revision, resource.getId());
this.name = resource.getName();
this.displayName = resource.getDisplayName();
- this.uris = resource.getUris();
this.type = resource.getType();
this.owner = resource.getOwner();
this.iconUri = resource.getIconUri();
this.resourceServerId = resource.getResourceServer().getId();
- this.scopesIds = resource.getScopes().stream().map(Scope::getId).collect(Collectors.toSet());
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;
}
- public Set<String> getUris() {
- return this.uris;
+ public Set<String> getUris(Supplier<Resource> source) {
+ return this.uris.get(source);
}
public String getType() {
@@ -91,11 +114,11 @@ public class CachedResource extends AbstractRevisioned implements InResourceServ
return this.resourceServerId;
}
- public Set<String> getScopesIds() {
- return this.scopesIds;
+ public Set<String> getScopesIds(Supplier<Resource> source) {
+ return this.scopesIds.get(source);
}
- public Map<String, List<String>> getAttributes() {
- return attributes;
+ public Map<String, List<String>> getAttributes(Supplier<Resource> source) {
+ return attributes.get(source);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
index 4b34cf9..34785cc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PolicyAdapter.java
@@ -21,6 +21,9 @@ import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
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.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
@@ -30,27 +33,32 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
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;
public PolicyAdapter(CachedPolicy cached, StoreFactoryCacheSession cacheSession) {
this.cached = cached;
this.cacheSession = cacheSession;
+ this.modelSupplier = this::getPolicyModel;
}
@Override
public Policy getDelegateForUpdate() {
if (updated == null) {
- updated = cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
+ updated = modelSupplier.get();
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");
}
return updated;
@@ -98,7 +106,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public void setName(String name) {
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);
}
@@ -142,15 +150,15 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public Map<String, String> getConfig() {
if (isUpdated()) return updated.getConfig();
- return cached.getConfig();
+ return cached.getConfig(modelSupplier);
}
@Override
public void setConfig(Map<String, String> config) {
getDelegateForUpdate();
- if (config.containsKey("defaultResourceType") || cached.getConfig().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(), cached.getScopesIds(), config.get("defaultResourceType"), cached.getResourceServerId());
+ if (config.containsKey("defaultResourceType") || cached.getConfig(modelSupplier).containsKey("defaultResourceType")) {
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), config.get("defaultResourceType"), cached.getResourceServerId());
}
updated.setConfig(config);
@@ -160,7 +168,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
public void removeConfig(String name) {
getDelegateForUpdate();
if (name.equals("defaultResourceType")) {
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), 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);
@@ -170,8 +178,8 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
public void putConfig(String name, String value) {
getDelegateForUpdate();
if (name.equals("defaultResourceType")) {
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(), cached.getScopesIds(), cached.getConfig().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), cached.getConfig(modelSupplier).get("defaultResourceType"), cached.getResourceServerId());
+ cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), cached.getResourcesIds(modelSupplier), cached.getScopesIds(modelSupplier), value, cached.getResourceServerId());
}
updated.putConfig(name, value);
}
@@ -192,40 +200,49 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
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;
associatedPolicies = new HashSet<>();
- for (String scopeId : cached.getAssociatedPoliciesIds()) {
- associatedPolicies.add(cacheSession.getPolicyStore().findById(scopeId, cached.getResourceServerId()));
+ PolicyStore policyStore = cacheSession.getPolicyStore();
+ 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;
+ return associatedPolicies = Collections.unmodifiableSet(associatedPolicies);
}
protected Set<Resource> resources;
+
@Override
public Set<Resource> getResources() {
if (isUpdated()) return updated.getResources();
if (resources != null) return resources;
resources = new HashSet<>();
- for (String resourceId : cached.getResourcesIds()) {
- resources.add(cacheSession.getResourceStore().findById(resourceId, cached.getResourceServerId()));
+ ResourceStore resourceStore = cacheSession.getResourceStore();
+ 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;
+ return resources = Collections.unmodifiableSet(resources);
}
@Override
public void addScope(Scope scope) {
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);
}
@Override
public void removeScope(Scope scope) {
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);
}
@@ -248,7 +265,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate();
HashSet<String> resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), 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);
}
@@ -258,11 +275,16 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
getDelegateForUpdate();
HashSet<String> resources = new HashSet<>();
resources.add(resource.getId());
- cacheSession.registerPolicyInvalidation(cached.getId(), cached.getName(), resources, cached.getScopesIds(), 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);
}
+ @Override
+ public boolean isFetched(String association) {
+ return modelSupplier.get().isFetched(association);
+ }
+
protected Set<Scope> scopes;
@Override
@@ -270,11 +292,14 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
if (isUpdated()) return updated.getScopes();
if (scopes != null) return scopes;
scopes = new HashSet<>();
- for (String scopeId : cached.getScopesIds()) {
- scopes.add(cacheSession.getScopeStore().findById(scopeId, cached.getResourceServerId()));
+ ScopeStore scopeStore = cacheSession.getScopeStore();
+ 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;
+ return scopes = Collections.unmodifiableSet(scopes);
}
@Override
@@ -286,7 +311,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
@Override
public void setOwner(String owner) {
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);
}
@@ -304,7 +329,7 @@ public class PolicyAdapter implements Policy, CachedModel<Policy> {
return getId().hashCode();
}
-
-
-
+ private Policy getPolicyModel() {
+ return cacheSession.getPolicyStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
index 97ab830..a22fa19 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java
@@ -18,7 +18,6 @@ package org.keycloak.models.cache.infinispan.authorization;
import org.keycloak.authorization.model.CachedModel;
import org.keycloak.authorization.model.PermissionTicket;
-import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
@@ -31,7 +30,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -40,20 +39,22 @@ import java.util.stream.Collectors;
*/
public class ResourceAdapter implements Resource, CachedModel<Resource> {
- protected CachedResource cached;
- protected StoreFactoryCacheSession cacheSession;
+ private final Supplier<Resource> modelSupplier;
+ protected final CachedResource cached;
+ protected final StoreFactoryCacheSession cacheSession;
protected Resource updated;
public ResourceAdapter(CachedResource cached, StoreFactoryCacheSession cacheSession) {
this.cached = cached;
this.cacheSession = cacheSession;
+ this.modelSupplier = this::getResourceModel;
}
@Override
public Resource getDelegateForUpdate() {
if (updated == null) {
- cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), cached.getScopesIds(), cached.getResourceServerId(), cached.getOwner());
- updated = cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
+ updated = modelSupplier.get();
+ 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");
}
return updated;
@@ -101,7 +102,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setName(String name) {
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);
}
@@ -114,7 +115,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setDisplayName(String name) {
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);
}
@@ -139,13 +140,13 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public Set<String> getUris() {
if (isUpdated()) return updated.getUris();
- return cached.getUris();
+ return cached.getUris(modelSupplier);
}
@Override
public void updateUris(Set<String> uris) {
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);
}
@@ -158,7 +159,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setType(String type) {
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);
}
@@ -170,11 +171,10 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
if (isUpdated()) return updated.getScopes();
if (scopes != null) return scopes;
scopes = new LinkedList<>();
- for (String scopeId : cached.getScopesIds()) {
+ for (String scopeId : cached.getScopesIds(modelSupplier)) {
scopes.add(cacheSession.getScopeStore().findById(scopeId, cached.getResourceServerId()));
}
- scopes = Collections.unmodifiableList(scopes);
- return scopes;
+ return scopes = Collections.unmodifiableList(scopes);
}
@Override
@@ -192,7 +192,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
@Override
public void setOwnerManagedAccess(boolean ownerManagedAccess) {
getDelegateForUpdate();
- cacheSession.registerResourceInvalidation(cached.getId(), cached.getName(), cached.getType(), cached.getUris(), 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);
}
@@ -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);
}
@Override
public Map<String, List<String>> getAttributes() {
if (updated != null) return updated.getAttributes();
- return cached.getAttributes();
+ return cached.getAttributes(modelSupplier);
}
@Override
public String getSingleAttribute(String 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()) {
return null;
@@ -246,7 +246,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
public List<String> getAttribute(String name) {
if (updated != null) return updated.getAttribute(name);
- List<String> values = cached.getAttributes().getOrDefault(name, Collections.emptyList());
+ List<String> values = cached.getAttributes(modelSupplier).getOrDefault(name, Collections.emptyList());
if (values.isEmpty()) {
return null;
@@ -268,6 +268,11 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
}
@Override
+ public boolean isFetched(String association) {
+ return modelSupplier.get().isFetched(association);
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Resource)) return false;
@@ -281,4 +286,7 @@ public class ResourceAdapter implements Resource, CachedModel<Resource> {
return getId().hashCode();
}
+ private Resource getResourceModel() {
+ return cacheSession.getResourceStoreDelegate().findById(cached.getId(), cached.getResourceServerId());
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
index c2b60c7..04e6eb7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java
@@ -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;
}
@@ -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);
}
- 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);
}
- 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);
if (query != null) {
logger.tracev("cache hit for key: {0}", cacheKey);
@@ -730,7 +730,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
cache.addRevisioned(query, startupRevision);
if (consumer != null) {
for (R resource : model) {
- consumer.accept(resource);
+ consumer.andThen(r -> cacheResource(resource)).accept(resource);
}
}
return model;
@@ -799,9 +799,9 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
logger.tracev("by id cache hit: {0}", cached.getId());
}
if (cached == null) {
- Long loaded = cache.getCurrentRevision(id);
if (! modelMightExist(id)) return null;
Policy model = getPolicyStoreDelegate().findById(id, resourceServerId);
+ Long loaded = cache.getCurrentRevision(id);
if (model == null) {
setModelDoesNotExists(id, loaded);
return null;
@@ -922,7 +922,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
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);
if (query != null) {
logger.tracev("cache hit for key: {0}", cacheKey);
@@ -936,7 +936,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
cache.addRevisioned(query, startupRevision);
if (consumer != null) {
for (R policy: model) {
- consumer.accept(policy);
+ consumer.andThen(r -> cachePolicy(policy)).accept(policy);
}
}
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);
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java
new file mode 100644
index 0000000..44b97b0
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/DefaultLazyLoader.java
@@ -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;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java
index 01ba0cd..bf83d27 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedGroup.java
@@ -21,50 +21,51 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
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.function.Supplier;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CachedGroup extends AbstractRevisioned implements InRealm {
- private String realm;
- private String name;
- private String parentId;
- private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
- private Set<String> roleMappings = new HashSet<>();
- private Set<String> subGroups = new HashSet<>();
+
+ private final String realm;
+ private final String name;
+ private final String parentId;
+ private final LazyLoader<GroupModel, MultivaluedHashMap<String, String>> attributes;
+ private final LazyLoader<GroupModel, Set<String>> roleMappings;
+ private final LazyLoader<GroupModel, Set<String>> subGroups;
public CachedGroup(Long revision, RealmModel realm, GroupModel group) {
super(revision, group.getId());
this.realm = realm.getId();
this.name = group.getName();
this.parentId = group.getParentId();
-
- this.attributes.putAll(group.getAttributes());
- for (RoleModel role : group.getRoleMappings()) {
- roleMappings.add(role.getId());
- }
- Set<GroupModel> subGroups1 = group.getSubGroups();
- if (subGroups1 != null) {
- for (GroupModel subGroup : subGroups1) {
- subGroups.add(subGroup.getId());
- }
- }
+ this.attributes = new DefaultLazyLoader<>(source -> new MultivaluedHashMap<>(source.getAttributes()), MultivaluedHashMap::new);
+ this.roleMappings = new DefaultLazyLoader<>(source -> source.getRoleMappings().stream().map(RoleModel::getId).collect(Collectors.toSet()), Collections::emptySet);
+ this.subGroups = new DefaultLazyLoader<>(source -> source.getSubGroups().stream().map(GroupModel::getId).collect(Collectors.toSet()), Collections::emptySet);
}
public String getRealm() {
return realm;
}
- public MultivaluedHashMap<String, String> getAttributes() {
- return attributes;
+ public MultivaluedHashMap<String, String> getAttributes(Supplier<GroupModel> group) {
+ return attributes.get(group);
}
- public Set<String> getRoleMappings() {
- return roleMappings;
+ public Set<String> getRoleMappings(Supplier<GroupModel> group) {
+ // it may happen that groups were not loaded before so we don't actually need to invalidate entries in the cache
+ if (group == null) {
+ return Collections.emptySet();
+ }
+ return roleMappings.get(group);
}
public String getName() {
@@ -75,7 +76,7 @@ public class CachedGroup extends AbstractRevisioned implements InRealm {
return parentId;
}
- public Set<String> getSubGroups() {
- return subGroups;
+ public Set<String> getSubGroups(Supplier<GroupModel> group) {
+ return subGroups.get(group);
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index 68dfc37..3a70b67 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -22,32 +22,35 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
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.function.Supplier;
+import java.util.stream.Collectors;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
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) {
super(revision, user.getId());
@@ -56,23 +59,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
this.createdTimestamp = user.getCreatedTimestamp();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
- this.attributes.putAll(user.getAttributes());
this.email = user.getEmail();
this.emailVerified = user.isEmailVerified();
this.enabled = user.isEnabled();
this.federationLink = user.getFederationLink();
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.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() {
@@ -107,16 +103,16 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return enabled;
}
- public MultivaluedHashMap<String, String> getAttributes() {
- return attributes;
+ public MultivaluedHashMap<String, String> getAttributes(Supplier<UserModel> userModel) {
+ return attributes.get(userModel);
}
- public Set<String> getRequiredActions() {
- return requiredActions;
+ public Set<String> getRequiredActions(Supplier<UserModel> userModel) {
+ return this.requiredActions.get(userModel);
}
- public Set<String> getRoleMappings() {
- return roleMappings;
+ public Set<String> getRoleMappings(Supplier<UserModel> userModel) {
+ return roleMappings.get(userModel);
}
public String getFederationLink() {
@@ -127,8 +123,8 @@ public class CachedUser extends AbstractExtendableRevisioned implements InRealm
return serviceAccountClientLink;
}
- public Set<String> getGroups() {
- return groups;
+ public Set<String> getGroups(Supplier<UserModel> userModel) {
+ return groups.get(userModel);
}
public int getNotBefore() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
index 0e69c68..6a35951 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/GroupAdapter.java
@@ -29,29 +29,33 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
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 CachedGroup cached;
- protected RealmCacheSession cacheSession;
- protected KeycloakSession keycloakSession;
- protected RealmModel realm;
public GroupAdapter(CachedGroup cached, RealmCacheSession cacheSession, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
this.cacheSession = cacheSession;
this.keycloakSession = keycloakSession;
this.realm = realm;
+ modelSupplier = this::getGroupModel;
}
protected void getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerGroupInvalidation(cached.getId());
- updated = cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm);
+ updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
@@ -128,19 +132,19 @@ public class GroupAdapter implements GroupModel {
@Override
public String getFirstAttribute(String name) {
if (isUpdated()) return updated.getFirstAttribute(name);
- return cached.getAttributes().getFirst(name);
+ return cached.getAttributes(modelSupplier).getFirst(name);
}
@Override
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;
return values;
}
@Override
public Map<String, List<String>> getAttributes() {
- return cached.getAttributes();
+ return cached.getAttributes(modelSupplier);
}
@Override
@@ -178,7 +182,7 @@ public class GroupAdapter implements GroupModel {
@Override
public boolean hasRole(RoleModel 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();
for (RoleModel mapping: mappings) {
@@ -197,7 +201,7 @@ public class GroupAdapter implements GroupModel {
public Set<RoleModel> getRoleMappings() {
if (isUpdated()) return updated.getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>();
- for (String id : cached.getRoleMappings()) {
+ for (String id : cached.getRoleMappings(modelSupplier)) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
if (roleById == null) {
// 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() {
if (isUpdated()) return updated.getSubGroups();
Set<GroupModel> subGroups = new HashSet<>();
- for (String id : cached.getSubGroups()) {
+ for (String id : cached.getSubGroups(modelSupplier)) {
GroupModel subGroup = keycloakSession.realms().getGroupById(id, realm);
if (subGroup == null) {
// chance that role was removed, so just delegate to persistence and get user invalidated
@@ -267,4 +271,8 @@ public class GroupAdapter implements GroupModel {
getDelegateForUpdate();
updated.removeChild(subGroup);
}
+
+ private GroupModel getGroupModel() {
+ return cacheSession.getRealmDelegate().getGroupById(cached.getId(), realm);
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java
new file mode 100644
index 0000000..b37cde7
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/LazyLoader.java
@@ -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);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java
index 3b548b9..2523dc9 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/stream/HasRolePredicate.java
@@ -44,7 +44,7 @@ public class HasRolePredicate implements Predicate<Map.Entry<String, Revisioned>
}
if (value instanceof CachedGroup) {
CachedGroup cachedRole = (CachedGroup)value;
- if (cachedRole.getRoleMappings().contains(role)) return true;
+ if (cachedRole.getRoleMappings(null).contains(role)) return true;
}
if (value instanceof RoleQuery) {
RoleQuery roleQuery = (RoleQuery)value;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 0056edf..63cfb65 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -35,30 +35,34 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
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 CachedUser cached;
- protected UserCacheSession userProviderCache;
- protected KeycloakSession keycloakSession;
- protected RealmModel realm;
public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
this.userProviderCache = userProvider;
this.keycloakSession = keycloakSession;
this.realm = realm;
+ this.modelSupplier = this::getUserModel;
}
@Override
public UserModel getDelegateForUpdate() {
if (updated == null) {
userProviderCache.registerUserInvalidation(realm, cached);
- updated = userProviderCache.getDelegate().getUserById(getId(), realm);
+ updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database");
}
return updated;
@@ -147,26 +151,26 @@ public class UserAdapter implements CachedUserModel {
@Override
public String getFirstAttribute(String name) {
if (updated != null) return updated.getFirstAttribute(name);
- return cached.getAttributes().getFirst(name);
+ return cached.getAttributes(modelSupplier).getFirst(name);
}
@Override
public List<String> getAttribute(String 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;
}
@Override
public Map<String, List<String>> getAttributes() {
if (updated != null) return updated.getAttributes();
- return cached.getAttributes();
+ return cached.getAttributes(modelSupplier);
}
@Override
public Set<String> getRequiredActions() {
if (updated != null) return updated.getRequiredActions();
- return cached.getRequiredActions();
+ return cached.getRequiredActions(modelSupplier);
}
@Override
@@ -301,7 +305,7 @@ public class UserAdapter implements CachedUserModel {
@Override
public boolean hasRole(RoleModel 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();
for (RoleModel mapping: mappings) {
@@ -320,7 +324,7 @@ public class UserAdapter implements CachedUserModel {
public Set<RoleModel> getRoleMappings() {
if (updated != null) return updated.getRoleMappings();
Set<RoleModel> roles = new HashSet<RoleModel>();
- for (String id : cached.getRoleMappings()) {
+ for (String id : cached.getRoleMappings(modelSupplier)) {
RoleModel roleById = keycloakSession.realms().getRoleById(id, realm);
if (roleById == null) {
// 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() {
if (updated != null) return updated.getGroups();
Set<GroupModel> groups = new HashSet<GroupModel>();
- for (String id : cached.getGroups()) {
+ for (String id : cached.getGroups(modelSupplier)) {
GroupModel groupModel = keycloakSession.realms().getGroupById(id, realm);
if (groupModel == null) {
// chance that role was removed, so just delete to persistence and get user invalidated
@@ -372,7 +376,7 @@ public class UserAdapter implements CachedUserModel {
@Override
public boolean isMemberOf(GroupModel 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();
return RoleUtils.isMember(roles, group);
}
@@ -391,7 +395,7 @@ public class UserAdapter implements CachedUserModel {
return getId().hashCode();
}
-
-
-
+ private UserModel getUserModel() {
+ return userProviderCache.getDelegate().getUserById(cached.getId(), realm);
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
index e235f58..96408a6 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java
@@ -55,13 +55,13 @@ import org.keycloak.representations.idm.authorization.Logic;
@NamedQueries(
{
@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="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="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="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="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="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 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 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 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 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="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="deletePolicyByResourceServer", query="delete from PolicyEntity p where p.resourceServer.id = :serverId")
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
index ab3ba82..4f92154 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/ResourceEntity.java
@@ -57,13 +57,13 @@ import org.hibernate.annotations.FetchMode;
})
@NamedQueries(
{
- @NamedQuery(name="findResourceIdByOwner", query="select r.id from ResourceEntity r 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="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 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="findResourceIdByName", query="select r.id from ResourceEntity r 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="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 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="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")
}
)
@@ -80,7 +80,7 @@ public class ResourceEntity {
@Column(name = "DISPLAY_NAME")
private String displayName;
- @ElementCollection(fetch = FetchType.EAGER)
+ @ElementCollection(fetch = FetchType.LAZY)
@Column(name = "VALUE")
@CollectionTable(name = "RESOURCE_URIS", joinColumns = { @JoinColumn(name="RESOURCE_ID") })
private Set<String> uris = new HashSet<>();
@@ -101,7 +101,7 @@ public class ResourceEntity {
@JoinColumn(name = "RESOURCE_SERVER_ID")
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"))
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"))
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)
@BatchSize(size = 20)
private Collection<ResourceAttributeEntity> attributes = new ArrayList<>();
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
index 73c7c7d..0dcccb9 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java
@@ -40,6 +40,7 @@ import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.PolicyStore;
+import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
@@ -90,23 +91,25 @@ public class JPAPolicyStore implements PolicyStore {
return null;
}
- PolicyEntity entity = entityManager.find(PolicyEntity.class, id);
- if (entity == null) return null;
+ PolicyEntity policyEntity = entityManager.find(PolicyEntity.class, id);
- return new PolicyAdapter(entity, entityManager, provider.getStoreFactory());
+ if (policyEntity == null) {
+ return null;
+ }
+
+ return new PolicyAdapter(policyEntity, entityManager, provider.getStoreFactory());
}
@Override
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.setParameter("serverId", resourceServerId);
query.setParameter("name", name);
try {
- String id = query.getSingleResult();
- return provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
+ return new PolicyAdapter(query.getSingleResult(), entityManager, provider.getStoreFactory());
} catch (NoResultException ex) {
return null;
}
@@ -203,17 +206,16 @@ public class JPAPolicyStore implements PolicyStore {
@Override
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.setParameter("resourceId", resourceId);
query.setParameter("serverId", resourceServerId);
- PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
+ StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
- .map(id -> policyStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(entity -> new PolicyAdapter(entity, entityManager, storeFactory))
.forEach(consumer::accept);
}
@@ -228,17 +230,14 @@ public class JPAPolicyStore implements PolicyStore {
@Override
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.setParameter("type", resourceType);
query.setParameter("serverId", resourceServerId);
- PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
-
query.getResultList().stream()
- .map(id -> policyStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(id -> new PolicyAdapter(id, entityManager, provider.getStoreFactory()))
.forEach(consumer::accept);
}
@@ -249,20 +248,19 @@ public class JPAPolicyStore implements PolicyStore {
}
// 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.setParameter("scopeIds", scopeIds);
query.setParameter("serverId", resourceServerId);
- List<String> result = query.getResultList();
List<Policy> list = new LinkedList<>();
- for (String id : result) {
- Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
- if (Objects.nonNull(policy)) {
- list.add(policy);
- }
+ StoreFactory storeFactory = provider.getStoreFactory();
+
+ for (PolicyEntity entity : query.getResultList()) {
+ list.add(new PolicyAdapter(entity, entityManager, storeFactory));
}
+
return list;
}
@@ -278,12 +276,12 @@ public class JPAPolicyStore implements PolicyStore {
@Override
public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
// Use separate subquery to handle DB2 and MSSSQL
- TypedQuery<String> query;
+ TypedQuery<PolicyEntity> query;
if (resourceId == null) {
- query = entityManager.createNamedQuery("findPolicyIdByNullResourceScope", String.class);
+ query = entityManager.createNamedQuery("findPolicyIdByNullResourceScope", PolicyEntity.class);
} else {
- query = entityManager.createNamedQuery("findPolicyIdByResourceScope", String.class);
+ query = entityManager.createNamedQuery("findPolicyIdByResourceScope", PolicyEntity.class);
query.setParameter("resourceId", resourceId);
}
@@ -291,11 +289,10 @@ public class JPAPolicyStore implements PolicyStore {
query.setParameter("scopeIds", scopeIds);
query.setParameter("serverId", resourceServerId);
- PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
+ StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
- .map(id -> policyStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(id -> new PolicyAdapter(id, entityManager, storeFactory))
.forEach(consumer::accept);
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
index 034838e..7a95f7d 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java
@@ -22,6 +22,7 @@ import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.ResourceStore;
+import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import javax.persistence.EntityManager;
@@ -116,7 +117,7 @@ public class JPAResourceStore implements ResourceStore {
queryName = "findAnyResourceIdByOwner";
}
- TypedQuery<String> query = entityManager.createNamedQuery(queryName, String.class);
+ TypedQuery<ResourceEntity> query = entityManager.createNamedQuery(queryName, ResourceEntity.class);
query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("owner", ownerId);
@@ -125,11 +126,10 @@ public class JPAResourceStore implements ResourceStore {
query.setParameter("serverId", resourceServerId);
}
- ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
+ StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
- .map(id -> resourceStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(id -> new ResourceAdapter(id, entityManager, storeFactory))
.forEach(consumer);
}
@@ -247,17 +247,16 @@ public class JPAResourceStore implements ResourceStore {
@Override
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.setParameter("scopeIds", scopes);
query.setParameter("serverId", resourceServerId);
- ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
+ StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
- .map(id -> resourceStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(id -> new ResourceAdapter(id, entityManager, storeFactory))
.forEach(consumer);
}
@@ -268,16 +267,14 @@ public class JPAResourceStore implements ResourceStore {
@Override
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("name", name);
query.setParameter("ownerId", ownerId);
try {
- String id = query.getSingleResult();
- return provider.getStoreFactory().getResourceStore().findById(id, resourceServerId);
+ return new ResourceAdapter(query.getSingleResult(), entityManager, provider.getStoreFactory());
} catch (NoResultException ex) {
return null;
}
@@ -294,18 +291,17 @@ public class JPAResourceStore implements ResourceStore {
@Override
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.setParameter("type", type);
query.setParameter("ownerId", resourceServerId);
query.setParameter("serverId", resourceServerId);
- ResourceStore resourceStore = provider.getStoreFactory().getResourceStore();
+ StoreFactory storeFactory = provider.getStoreFactory();
query.getResultList().stream()
- .map(id -> resourceStore.findById(id, resourceServerId))
- .filter(Objects::nonNull)
+ .map(entity -> new ResourceAdapter(entity, entityManager, storeFactory))
.forEach(consumer);
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
index 98651da..a761370 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PolicyAdapter.java
@@ -151,8 +151,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
public Set<Policy> getAssociatedPolicies() {
Set<Policy> result = new HashSet<>();
for (PolicyEntity policy : entity.getAssociatedPolicies()) {
- Policy p = storeFactory.getPolicyStore().findById(policy.getId(), entity.getResourceServer().getId());
- result.add(p);
+ result.add(new PolicyAdapter(policy, em, storeFactory));
}
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);
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/ResourceAdapter.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/ResourceAdapter.java
index 64f66db..82735ed 100644
--- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/ResourceAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/ResourceAdapter.java
@@ -231,6 +231,11 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
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) {
if (resource instanceof ResourceAdapter) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index f858161..cd12fc9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -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="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"),
- @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="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"),
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 2ee9ea5..f9d51b8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -17,6 +17,7 @@
package org.keycloak.models.jpa;
+import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
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.UserConsentEntity;
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.KeycloakModelUtils;
import org.keycloak.storage.StorageId;
@@ -50,6 +52,11 @@ import org.keycloak.storage.client.ClientStorageProvider;
import javax.persistence.EntityManager;
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.Collection;
import java.util.HashSet;
@@ -510,12 +517,9 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override
public UserModel getUserById(String id, RealmModel realm) {
- TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class);
- query.setParameter("id", id);
- query.setParameter("realmId", realm.getId());
- List<UserEntity> entities = query.getResultList();
- if (entities.size() == 0) return null;
- return new UserAdapter(session, realm, em, entities.get(0));
+ UserEntity userEntity = em.find(UserEntity.class, id);
+ if (userEntity == null) return null;
+ return new UserAdapter(session, realm, em, userEntity);
}
@Override
@@ -700,55 +704,86 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override
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");
- for (Map.Entry<String, String> entry : attributes.entrySet()) {
- String attribute = null;
- String parameterName = null;
- if (entry.getKey().equals(UserModel.USERNAME)) {
- attribute = "lower(u.username)";
- parameterName = JpaUserProvider.USERNAME;
- } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
- attribute = "lower(u.firstName)";
- parameterName = JpaUserProvider.FIRST_NAME;
- } 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);
+ CriteriaBuilder builder = em.getCriteriaBuilder();
+ CriteriaQuery<UserEntity> queryBuilder = builder.createQuery(UserEntity.class);
+ Root<UserEntity> root = queryBuilder.from(UserEntity.class);
+
+ List<Predicate> predicates = new ArrayList();
+
+ predicates.add(builder.equal(root.get("realmId"), realm.getId()));
+
+ if (!session.getAttributeOrDefault(UserModel.INCLUDE_SERVICE_ACCOUNT, true)) {
+ predicates.add(root.get("serviceAccountClientLink").isNull());
}
- 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()) {
- String parameterName = null;
- if (entry.getKey().equals(UserModel.USERNAME)) {
- parameterName = JpaUserProvider.USERNAME;
- } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) {
- parameterName = JpaUserProvider.FIRST_NAME;
- } else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) {
- parameterName = JpaUserProvider.LAST_NAME;
- } else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) {
- parameterName = JpaUserProvider.EMAIL;
+ String key = entry.getKey();
+ String value = entry.getValue();
+
+ if (value == null) {
+ continue;
+ }
+
+ switch (key) {
+ 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) {
query.setFirstResult(firstResult);
}
+
if (maxResults != -1) {
query.setMaxResults(maxResults);
}
- List<UserEntity> results = query.getResultList();
- List<UserModel> users = new ArrayList<UserModel>();
- for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
- return users;
+
+ List<UserModel> results = new ArrayList<>();
+ UserProvider users = session.users();
+
+ for (UserEntity entity : query.getResultList()) {
+ results.add(users.getUserById(entity.getId(), realm));
+ }
+
+ return results;
}
@Override
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml
new file mode 100644
index 0000000..3569849
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-4.6.0.xml
@@ -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>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index f4f2003..9a3e021 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -59,4 +59,5 @@
<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.3.0.xml"/>
+ <include file="META-INF/jpa-changelog-4.6.0.xml"/>
</databaseChangeLog>
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 42f5d65..19773dc 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -77,6 +77,15 @@ public interface KeycloakSession {
Object getAttribute(String attribute);
<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);
void setAttribute(String name, Object value);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java
index 91b3769..3da6525 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java
@@ -33,6 +33,8 @@ public interface UserModel extends RoleMapperModel {
String FIRST_NAME = "firstName";
String EMAIL = "email";
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 {
RealmModel getRealm();
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
index 0dafea9..d9e1a0a 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java
@@ -163,4 +163,6 @@ public interface Policy {
void addResource(Resource resource);
void removeResource(Resource resource);
+
+ boolean isFetched(String association);
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java
index 2cf2ea1..af8062b 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java
@@ -180,4 +180,6 @@ public interface Resource {
void setAttribute(String name, List<String> values);
void removeAttribute(String name);
+
+ boolean isFetched(String association);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
index 26ec3b5..f6ab47e 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/ClientPermissions.java
@@ -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;
}
@@ -376,7 +376,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
Scope scope = manageScope(server);
- return root.evaluatePermission(resource, scope, server);
+ return root.evaluatePermission(resource, server, scope);
}
@Override
@@ -404,7 +404,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
Scope scope = configureScope(server);
- return root.evaluatePermission(resource, scope, server);
+ return root.evaluatePermission(resource, server, scope);
}
@Override
public void requireConfigure(ClientModel client) {
@@ -450,7 +450,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
Scope scope = viewScope(server);
- return root.evaluatePermission(resource, scope, server);
+ return root.evaluatePermission(resource, server, scope);
}
@Override
@@ -529,7 +529,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
Scope scope = mapRolesScope(server);
- return root.evaluatePermission(resource, scope, server);
+ return root.evaluatePermission(resource, server, scope);
}
@Override
@@ -606,7 +606,7 @@ class ClientPermissions implements ClientPermissionEvaluator, ClientPermissionM
}
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
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());
- return root.evaluatePermission(resource, scope, server);
+ return root.evaluatePermission(resource, server, scope);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
index d3a5213..831e9f0 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissionEvaluator.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources.admin.permissions;
import org.keycloak.models.GroupModel;
import java.util.Map;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,17 +46,17 @@ public interface GroupPermissionEvaluator {
void requireView();
- boolean canViewMembers(GroupModel group);
+ boolean getGroupsWithViewPermission(GroupModel group);
void requireViewMembers(GroupModel group);
boolean canManageMembers(GroupModel group);
- void requireManageMembers(GroupModel group);
-
boolean canManageMembership(GroupModel group);
void requireManageMembership(GroupModel group);
Map<String, Boolean> getAccess(GroupModel group);
+
+ Set<String> getGroupsWithViewPermission();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
index c2a4689..dabc02e 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/GroupPermissions.java
@@ -16,21 +16,27 @@
*/
package org.keycloak.services.resources.admin.permissions;
-import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
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.GroupModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ForbiddenException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,45 +45,46 @@ import java.util.Set;
* @version $Revision: 1 $
*/
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) {
- this.session = session;
- this.realm = realm;
+
+ private static final String MANAGE_MEMBERSHIP_SCOPE = "manage-membership";
+ private static final String MANAGE_MEMBERS_SCOPE = "manage-members";
+ 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.root = root;
+ resourceStore = authz.getStoreFactory().getResourceStore();
+ policyStore = authz.getStoreFactory().getPolicyStore();
}
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();
}
- public static String getManageMembersPermissionGroup(GroupModel group) {
+ private static String getManageMembersPermissionGroup(GroupModel group) {
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();
}
- public static String getViewPermissionGroup(GroupModel group) {
+ private static String getViewPermissionGroup(GroupModel group) {
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();
}
@@ -92,9 +99,9 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
Scope manageMembershipScope = root.initializeRealmScope(MANAGE_MEMBERSHIP_SCOPE);
String groupResourceName = getGroupResourceName(group);
- Resource groupResource = authz.getStoreFactory().getResourceStore().findByName(groupResourceName, server.getId());
+ Resource groupResource = resourceStore.findByName(groupResourceName, server.getId());
if (groupResource == null) {
- groupResource = authz.getStoreFactory().getResourceStore().create(groupResourceName, server, server.getId());
+ groupResource = resourceStore.create(groupResourceName, server, server.getId());
Set<Scope> scopeset = new HashSet<>();
scopeset.add(manageScope);
scopeset.add(viewScope);
@@ -102,29 +109,30 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
scopeset.add(manageMembershipScope);
scopeset.add(manageMembersScope);
groupResource.updateScopes(scopeset);
+ groupResource.setType("Group");
}
String managePermissionName = getManagePermissionGroup(group);
- Policy managePermission = authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
+ Policy managePermission = policyStore.findByName(managePermissionName, server.getId());
if (managePermission == null) {
Helper.addEmptyScopePermission(authz, server, managePermissionName, groupResource, manageScope);
}
String viewPermissionName = getViewPermissionGroup(group);
- Policy viewPermission = authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
+ Policy viewPermission = policyStore.findByName(viewPermissionName, server.getId());
if (viewPermission == null) {
Helper.addEmptyScopePermission(authz, server, viewPermissionName, groupResource, viewScope);
}
String manageMembersPermissionName = getManageMembersPermissionGroup(group);
- Policy manageMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
+ Policy manageMembersPermission = policyStore.findByName(manageMembersPermissionName, server.getId());
if (manageMembersPermission == null) {
Helper.addEmptyScopePermission(authz, server, manageMembersPermissionName, groupResource, manageMembersScope);
}
String viewMembersPermissionName = getViewMembersPermissionGroup(group);
- Policy viewMembersPermission = authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
+ Policy viewMembersPermission = policyStore.findByName(viewMembersPermissionName, server.getId());
if (viewMembersPermission == null) {
Helper.addEmptyScopePermission(authz, server, viewMembersPermissionName, groupResource, viewMembersScope);
}
String manageMembershipPermissionName = getManageMembershipPermissionGroup(group);
- Policy manageMembershipPermission = authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId());
+ Policy manageMembershipPermission = policyStore.findByName(manageMembershipPermissionName, server.getId());
if (manageMembershipPermission == null) {
Helper.addEmptyScopePermission(authz, server, manageMembershipPermissionName, groupResource, manageMembershipScope);
}
@@ -143,21 +151,12 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
}
}
-
-
@Override
public boolean isPermissionsEnabled(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return false;
- return authz.getStoreFactory().getResourceStore().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());
+ return resourceStore.findByName(getGroupResourceName(group), server.getId()) != null;
}
@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
public Policy viewMembersPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- String viewMembersPermissionName = getViewMembersPermissionGroup(group);
- return authz.getStoreFactory().getPolicyStore().findByName(viewMembersPermissionName, server.getId());
+ return policyStore.findByName(getViewMembersPermissionGroup(group), server.getId());
}
@Override
public Policy manageMembersPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- String manageMembersPermissionName = getManageMembersPermissionGroup(group);
- return authz.getStoreFactory().getPolicyStore().findByName(manageMembersPermissionName, server.getId());
+ return policyStore.findByName(getManageMembersPermissionGroup(group), server.getId());
}
@Override
public Policy manageMembershipPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- String manageMembershipPermissionName = getManageMembershipPermissionGroup(group);
- return authz.getStoreFactory().getPolicyStore().findByName(manageMembershipPermissionName, server.getId());
+ return policyStore.findByName(getManageMembershipPermissionGroup(group), server.getId());
}
@Override
public Policy viewPermission(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- String viewPermissionName = getViewPermissionGroup(group);
- return authz.getStoreFactory().getPolicyStore().findByName(viewPermissionName, server.getId());
+ return policyStore.findByName(getViewPermissionGroup(group), server.getId());
}
@Override
public Policy managePermission(GroupModel group) {
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- String managePermissionName = getManagePermissionGroup(group);
- return authz.getStoreFactory().getPolicyStore().findByName(managePermissionName, server.getId());
+ return policyStore.findByName(getManagePermissionGroup(group), server.getId());
}
@Override
public Resource resource(GroupModel group) {
ResourceServer server = root.realmResourceServer();
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;
return resource;
}
@@ -257,35 +224,17 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return scopes;
}
-
-
-
@Override
public boolean canManage(GroupModel group) {
- if (canManage()) return true;
- if (!root.isAdminSameRealm()) {
- return false;
+ if (canManage()) {
+ return true;
}
- ResourceServer server = root.realmResourceServer();
- 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()) {
+ if (!root.isAdminSameRealm()) {
return false;
}
- Scope scope = root.realmManageScope();
- return root.evaluatePermission(resource, scope, server);
+ return hasPermission(group, MgmtPermissions.MANAGE_SCOPE);
}
@Override
@@ -294,37 +243,18 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
throw new ForbiddenException();
}
}
+
@Override
public boolean canView(GroupModel group) {
- return hasView(group) || canManage(group);
- }
-
- private boolean hasView(GroupModel group) {
- if (canView()) return true;
-
- if (!root.isAdminSameRealm()) {
- return false;
+ if (canView() || canManage()) {
+ return true;
}
- ResourceServer server = root.realmResourceServer();
- 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()) {
+ if (!root.isAdminSameRealm()) {
return false;
}
- Scope scope = root.realmViewScope();
- return root.evaluatePermission(resource, scope, server);
+ return hasPermission(group, MgmtPermissions.VIEW_SCOPE, MgmtPermissions.MANAGE_SCOPE);
}
@Override
@@ -357,15 +287,11 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
}
}
-
-
@Override
- public boolean canViewMembers(GroupModel group) {
- return canViewMembersEvaluation(group) || canManageMembers(group);
- }
-
- private boolean canViewMembersEvaluation(GroupModel group) {
- if (root.users().canView()) return true;
+ public boolean getGroupsWithViewPermission(GroupModel group) {
+ if (root.users().canView() || root.users().canManage()) {
+ return true;
+ }
if (!root.isAdminSameRealm()) {
return false;
@@ -374,34 +300,41 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
ResourceServer server = root.realmResourceServer();
if (server == null) return false;
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
- if (resource == null) return false;
+ return hasPermission(group, VIEW_MEMBERS_SCOPE, MANAGE_MEMBERS_SCOPE);
+ }
- Policy policy = viewMembersPermission(group);
- if (policy == null) {
- return false;
+ @Override
+ public Set<String> getGroupsWithViewPermission() {
+ if (root.users().canView() || root.users().canManage()) return Collections.emptySet();
+
+ if (!root.isAdminSameRealm()) {
+ return Collections.emptySet();
}
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- // if no policies attached to permission then just do default behavior
- if (associatedPolicies == null || associatedPolicies.isEmpty()) {
- return false;
+ ResourceServer server = root.realmResourceServer();
+
+ if (server == null) {
+ return Collections.emptySet();
}
- Scope scope = authz.getStoreFactory().getScopeStore().findByName(VIEW_MEMBERS_SCOPE, server.getId());
+ Set<String> granted = new HashSet<>();
- return root.evaluatePermission(resource, scope, server);
- }
+ 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
public void requireViewMembers(GroupModel group) {
- if (!canViewMembers(group)) {
+ if (!getGroupsWithViewPermission(group)) {
throw new ForbiddenException();
}
}
-
@Override
public boolean canManageMembers(GroupModel group) {
if (root.users().canManage()) return true;
@@ -413,29 +346,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
ResourceServer server = root.realmResourceServer();
if (server == null) return false;
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(getGroupResourceName(group), server.getId());
- 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();
- }
+ return hasPermission(group, MANAGE_MEMBERS_SCOPE);
}
@Override
@@ -446,25 +357,7 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
return false;
}
- ResourceServer server = root.realmResourceServer();
- 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);
+ return hasPermission(group, MANAGE_MEMBERSHIP_SCOPE);
}
@Override
@@ -483,7 +376,81 @@ class GroupPermissions implements GroupPermissionEvaluator, GroupPermissionManag
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());
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
index a0e2e44..db1a96a 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/IdentityProviderPermissions.java
@@ -189,7 +189,7 @@ class IdentityProviderPermissions implements IdentityProviderPermissionManageme
}
};
- return root.evaluatePermission(resource, scope, server, context);
+ return root.evaluatePermission(resource, server, context, scope);
}
return true;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
index 735dd48..56e5f53 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java
@@ -28,10 +28,8 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.ResourceServerStore;
-import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@@ -39,11 +37,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.AdminAuth;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -205,7 +206,7 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
@Override
public UserPermissions users() {
if (users != null) return users;
- users = new UserPermissions(session, realm, authz, this);
+ users = new UserPermissions(session, authz, this);
return users;
}
@@ -233,7 +234,7 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
@Override
public GroupPermissions groups() {
if (groups != null) return groups;
- groups = new GroupPermissions(session, realm, authz, this);
+ groups = new GroupPermissions(authz, this);
return groups;
}
@@ -313,25 +314,36 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
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();
if (identity == null) {
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);
- return evaluatePermission(resource, scope, resourceServer, context);
+ return evaluatePermission(resource, resourceServer, context, scope);
+ }
+
+ 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 boolean evaluatePermission(Resource resource, Scope scope, ResourceServer resourceServer, EvaluationContext context) {
+ public Collection<Permission> evaluatePermission(List<ResourcePermission> permissions, ResourceServer resourceServer, EvaluationContext context) {
RealmModel oldRealm = session.getContext().getRealm();
try {
session.getContext().setRealm(realm);
- ResourcePermission permission = Permissions.permission(resourceServer, resource, scope);
- return !authz.evaluators().from(Arrays.asList(permission), context).evaluate(resourceServer, null).isEmpty();
+ return authz.evaluators().from(permissions, context).evaluate(resourceServer, null);
} finally {
session.getContext().setRealm(oldRealm);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
index 464f512..5ef8100 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/RolePermissions.java
@@ -307,7 +307,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role);
Scope mapRoleScope = mapRoleScope(resourceServer);
- if (root.evaluatePermission(roleResource, mapRoleScope, resourceServer)) {
+ if (root.evaluatePermission(roleResource, resourceServer, mapRoleScope)) {
return checkAdminRoles(role);
} else {
return false;
@@ -391,7 +391,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role);
Scope scope = mapCompositeScope(resourceServer);
- if (root.evaluatePermission(roleResource, scope, resourceServer)) {
+ if (root.evaluatePermission(roleResource, resourceServer, scope)) {
return checkAdminRoles(role);
} else {
return false;
@@ -430,7 +430,7 @@ class RolePermissions implements RolePermissionEvaluator, RolePermissionManageme
Resource roleResource = resource(role);
Scope scope = mapClientScope(resourceServer);
- return root.evaluatePermission(roleResource, scope, resourceServer);
+ return root.evaluatePermission(roleResource, resourceServer, scope);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
index f0e2a86..c9a7e48 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissionEvaluator.java
@@ -16,7 +16,6 @@
*/
package org.keycloak.services.resources.admin.permissions;
-import org.keycloak.authorization.model.Policy;
import org.keycloak.models.UserModel;
import java.util.Map;
@@ -26,42 +25,30 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public interface UserPermissionEvaluator {
- boolean canManage();
-
void requireManage();
-
- boolean canManage(UserModel user);
void requireManage(UserModel user);
-
- boolean canQuery();
+ boolean canManage();
+ boolean canManage(UserModel user);
void requireQuery();
+ boolean canQuery();
- boolean canQuery(UserModel user);
-
- void requireQuery(UserModel user);
-
+ void requireView();
+ void requireView(UserModel user);
boolean canView();
boolean canView(UserModel user);
- void requireView(UserModel user);
-
- void requireView();
+ void requireImpersonate(UserModel user);
+ boolean canImpersonate();
boolean canImpersonate(UserModel user);
-
boolean isImpersonatable(UserModel user);
- boolean canImpersonate();
-
- void requireImpersonate(UserModel user);
-
Map<String, Boolean> getAccess(UserModel user);
- boolean canMapRoles(UserModel user);
-
void requireMapRoles(UserModel user);
-
- boolean canManageGroupMembership(UserModel user);
+ boolean canMapRoles(UserModel user);
void requireManageGroupMembership(UserModel user);
+ boolean canManageGroupMembership(UserModel user);
+ void grantIfNoPermission(boolean grantIfNoPermission);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
index a8d9c8b..92d9aa5 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/UserPermissions.java
@@ -16,7 +16,6 @@
*/
package org.keycloak.services.resources.admin.permissions;
-import org.jboss.logging.Logger;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.ClientModelIdentity;
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.ResourceServer;
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.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ForbiddenException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Manages default policies for all users.
@@ -52,28 +58,32 @@ import java.util.Set;
* @version $Revision: 1 $
*/
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.realm = realm;
this.authz = authz;
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 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) {
- usersResource = authz.getStoreFactory().getResourceStore().create(USERS_RESOURCE, server, server.getId());
+ usersResource = resourceStore.create(USERS_RESOURCE, server, server.getId());
Set<Scope> scopeset = new HashSet<>();
scopeset.add(manageScope);
scopeset.add(viewScope);
@@ -100,27 +110,27 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
scopeset.add(userImpersonatedScope);
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) {
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) {
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) {
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) {
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) {
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) {
Helper.addEmptyScopePermission(authz, server, USER_IMPERSONATED_PERMISSION, usersResource, userImpersonatedScope);
}
@@ -144,7 +154,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
ResourceServer server = root.realmResourceServer();
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;
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() {
return root.hasOneAdminRole(AdminRoles.MANAGE_USERS);
}
@@ -209,48 +180,40 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
ResourceServer server = root.realmResourceServer();
if (server == null) return null;
- return authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
+ return resourceStore.findByName(USERS_RESOURCE, server.getId());
}
@Override
public Policy managePermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_PERMISSION_USERS, server.getId());
+ return policyStore.findByName(MANAGE_PERMISSION_USERS, root.realmResourceServer().getId());
}
@Override
public Policy viewPermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(VIEW_PERMISSION_USERS, server.getId());
+ return policyStore.findByName(VIEW_PERMISSION_USERS, root.realmResourceServer().getId());
}
@Override
public Policy manageGroupMembershipPermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, server.getId());
+ return policyStore.findByName(MANAGE_GROUP_MEMBERSHIP_PERMISSION_USERS, root.realmResourceServer().getId());
}
@Override
public Policy mapRolesPermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(MAP_ROLES_PERMISSION_USERS, server.getId());
+ return policyStore.findByName(MAP_ROLES_PERMISSION_USERS, root.realmResourceServer().getId());
}
@Override
public Policy adminImpersonatingPermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(ADMIN_IMPERSONATING_PERMISSION, server.getId());
+ return policyStore.findByName(ADMIN_IMPERSONATING_PERMISSION, root.realmResourceServer().getId());
}
@Override
public Policy userImpersonatedPermission() {
- ResourceServer server = root.realmResourceServer();
- return authz.getStoreFactory().getPolicyStore().findByName(USER_IMPERSONATED_PERMISSION, server.getId());
+ return policyStore.findByName(USER_IMPERSONATED_PERMISSION, root.realmResourceServer().getId());
}
-
-
/**
* 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
public boolean canManage() {
- if (canManageDefault()) return true;
- if (!root.isAdminSameRealm()) {
- return false;
- }
-
- 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(MANAGE_PERMISSION_USERS, server.getId());
- if (policy == null) {
- return false;
+ if (canManageDefault()) {
+ return true;
}
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- // if no policies attached to permission then just do default behavior
- if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+ if (!root.isAdminSameRealm()) {
return false;
}
- Scope scope = root.realmManageScope();
- return root.evaluatePermission(resource, scope, server);
-
+ return hasPermission(MgmtPermissions.MANAGE_SCOPE);
}
@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
public boolean canQuery() {
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?
*
@@ -418,34 +293,15 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
*/
@Override
public boolean canView() {
- if (canViewDefault()) return true;
- if (!root.isAdminSameRealm()) {
- return false;
- }
-
- return hasViewPermission() || canManage();
- }
-
- 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();
+ if (canViewDefault() || canManageDefault()) {
+ return true;
}
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- // if no policies attached to permission then just do default behavior
- if (associatedPolicies == null || associatedPolicies.isEmpty()) {
- return canViewDefault();
+ if (!root.isAdminSameRealm()) {
+ return false;
}
- Scope scope = root.realmViewScope();
- return root.evaluatePermission(resource, scope, server);
+ return hasPermission(MgmtPermissions.VIEW_SCOPE, MgmtPermissions.MANAGE_SCOPE);
}
/**
@@ -505,15 +361,20 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
@Override
public boolean isImpersonatable(UserModel user) {
- Identity userIdentity = new UserModelIdentity(root.realm, user);
-
ResourceServer server = root.realmResourceServer();
- if (server == null) return true;
- Resource resource = authz.getStoreFactory().getResourceStore().findByName(USERS_RESOURCE, server.getId());
- if (resource == null) return true;
+ if (server == null) {
+ 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());
+
if (policy == null) {
return true;
}
@@ -524,8 +385,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return true;
}
- Scope scope = root.realmScope(USER_IMPERSONATED_SCOPE);
- return root.evaluatePermission(resource, scope, server, userIdentity);
+ return hasPermission(new DefaultEvaluationContext(new UserModelIdentity(root.realm, user), session), USER_IMPERSONATED_SCOPE);
}
@Override
@@ -538,31 +398,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false;
}
- EvaluationContext context = 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);
+ return canImpersonate(new DefaultEvaluationContext(identity, session));
}
@Override
@@ -591,25 +427,7 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false;
}
- 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(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);
+ return hasPermission(MAP_ROLES_SCOPE);
}
@@ -621,7 +439,6 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
}
-
@Override
public boolean canManageGroupMembership(UserModel user) {
if (canManage(user)) return true;
@@ -630,39 +447,130 @@ class UserPermissions implements UserPermissionEvaluator, UserPermissionManageme
return false;
}
- ResourceServer server = root.realmResourceServer();
- if (server == null) return false;
+ return hasPermission(MANAGE_GROUP_MEMBERSHIP_SCOPE);
- 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;
+ @Override
+ public void grantIfNoPermission(boolean grantIfNoPermission) {
+ this.grantIfNoPermission = grantIfNoPermission;
+ }
+
+ @Override
+ public void requireManageGroupMembership(UserModel user) {
+ if (!canManageGroupMembership(user)) {
+ throw new ForbiddenException();
}
- Set<Policy> associatedPolicies = policy.getAssociatedPolicies();
- // if no policies attached to permission then just do default behavior
- if (associatedPolicies == null || associatedPolicies.isEmpty()) {
+ }
+
+ 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;
}
- Scope scope = root.realmScope(MANAGE_GROUP_MEMBERSHIP_SCOPE);
- return root.evaluatePermission(resource, scope, server);
+ 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;
}
- @Override
- public void requireManageGroupMembership(UserModel user) {
- if (!canManageGroupMembership(user)) {
- throw new ForbiddenException();
+ 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);
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e8a57c7..cc2389a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -35,6 +35,8 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException;
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.GET;
@@ -47,7 +49,7 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -55,6 +57,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Base resource for managing users
@@ -181,12 +184,13 @@ public class UsersResource {
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") Boolean briefRepresentation) {
- auth.users().requireQuery();
+ UserPermissionEvaluator userPermissionEvaluator = auth.users();
+
+ userPermissionEvaluator.requireQuery();
firstResult = firstResult != null ? firstResult : -1;
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
- List<UserRepresentation> results = new ArrayList<UserRepresentation>();
List<UserModel> userModels = Collections.emptyList();
if (search != null) {
if (search.startsWith(SEARCH_ID_PARAMETER)) {
@@ -198,7 +202,7 @@ public class UsersResource {
userModels = session.users().searchForUser(search.trim(), realm, firstResult, maxResults);
}
} 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) {
attributes.put(UserModel.LAST_NAME, last);
}
@@ -211,22 +215,12 @@ public class UsersResource {
if (username != null) {
attributes.put(UserModel.USERNAME, username);
}
- userModels = session.users().searchForUser(attributes, realm, firstResult, maxResults);
+ return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, true);
} else {
- userModels = session.users().getUsers(realm, firstResult, maxResults, false);
+ return searchForUser(new HashMap<>(), realm, userPermissionEvaluator, briefRepresentation, firstResult, maxResults, false);
}
- boolean canViewGlobal = auth.users().canView();
- 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;
+ return toRepresentation(realm, userPermissionEvaluator, briefRepresentation, userModels);
}
@Path("count")
@@ -238,4 +232,42 @@ public class UsersResource {
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;
+ }
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java
index 0d01c41..b4ec024 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java
@@ -190,7 +190,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
@Override
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);
if (username == null) return Collections.EMPTY_LIST;
return searchForUser(username, realm, firstResult, maxResults);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
index 4b144fe..9c18c28 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java
@@ -16,16 +16,19 @@
*/
package org.keycloak.testsuite.admin;
+import org.hamcrest.Matchers;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Resource;
import org.keycloak.client.admin.cli.util.ConfigUtil;
import org.keycloak.common.Profile;
import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.models.GroupModel;
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.UserRepresentation;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
+import org.keycloak.services.resources.admin.permissions.GroupPermissionManagement;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
@@ -898,6 +902,92 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest {
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());
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
index 52b42fc..4017b2d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java
@@ -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) {
ClientRepresentation cli = realm.clients().findByClientId(Constants.ADMIN_CLI_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();
}
- protected void testMigrationTo4_x(boolean supportsAuthzServices) {
+ protected void testMigrationTo4_x(boolean supportsAuthzServices, boolean checkMigrationData) {
testMigrationTo4_0_0();
testMigrationTo4_2_0(supportsAuthzServices);
+ testMigrationTo4_6_0(supportsAuthzServices, checkMigrationData);
}
protected void testMigrationTo4_x() {
- testMigrationTo4_x(true);
+ testMigrationTo4_x(true, true);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java
index 5a219e2..c8dabda 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport198MigrationTest.java
@@ -71,7 +71,7 @@ public class JsonFileImport198MigrationTest extends AbstractJsonFileImportMigrat
testMigrationTo2_5_0();
//testMigrationTo2_5_1(); // Offline tokens migration is skipped for JSON
testMigrationTo3_x();
- testMigrationTo4_x(false);
+ testMigrationTo4_x(false, false);
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java
index 0b79e00..61afd71 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport255MigrationTest.java
@@ -65,7 +65,7 @@ public class JsonFileImport255MigrationTest extends AbstractJsonFileImportMigrat
@Test
public void migration2_5_5Test() throws Exception {
testMigrationTo3_x();
- testMigrationTo4_x();
+ testMigrationTo4_x(true, false);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java
index ae25bce..835f1f5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/JsonFileImport343MigrationTest.java
@@ -63,7 +63,7 @@ public class JsonFileImport343MigrationTest extends AbstractJsonFileImportMigrat
@Test
public void migration3_4_3Test() throws Exception {
- testMigrationTo4_x();
+ testMigrationTo4_x(true, false);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
index da82d6e..1a35d53 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -88,7 +88,7 @@ public class MigrationTest extends AbstractMigrationTest {
testMigratedData(false);
testMigrationTo2_x();
testMigrationTo3_x();
- testMigrationTo4_x(false);
+ testMigrationTo4_x(false, false);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json
index 0f72b38..fd39026 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-3.4.3.Final.json
@@ -858,7 +858,33 @@
} ],
"useTemplateConfig" : 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",
"clientId" : "security-admin-console",
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 0cf03aa..54004de 100755
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -235,12 +235,8 @@ public abstract class AbstractIdentityProviderTest {
UserSessionStatus userSessionStatus = retrieveSessionStatus();
IDToken idToken = userSessionStatus.getIdToken();
KeycloakSession samlServerSession = brokerServerRule.startSession();
- try {
- RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker");
- return samlServerSession.users().getUserById(idToken.getSubject(), brokerRealm);
- } finally {
- brokerServerRule.stopSession(samlServerSession, false);
- }
+ RealmModel brokerRealm = samlServerSession.realms().getRealm("realm-with-broker");
+ return samlServerSession.users().getUserById(idToken.getSubject(), brokerRealm);
}
protected void doAfterProviderAuthentication() {