keycloak-memoizeit

[KEYCLOAK-7950] - Fixes user pagination when using filtering

9/28/2018 4:53:23 PM

Changes

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() {