keycloak-uncached

[KEYCLOAK-4902] - Refactoring and improvements to processing

7/27/2018 11:22:26 PM

Changes

server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java 51(+0 -51)

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
index bc90b65..2ce7a49 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java
@@ -17,6 +17,7 @@
  */
 package org.keycloak.adapters.authorization;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -151,7 +152,7 @@ public abstract class AbstractPolicyEnforcer {
         }
 
         boolean hasPermission = false;
-        List<Permission> grantedPermissions = authorization.getPermissions();
+        Collection<Permission> grantedPermissions = authorization.getPermissions();
 
         for (Permission permission : grantedPermissions) {
             if (permission.getResourceId() != null) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index 632aa66..e7211a9 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -18,7 +18,7 @@
 package org.keycloak.adapters.authorization;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -78,8 +78,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
         AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
 
         if (newAuthorization != null) {
-            List<Permission> grantedPermissions = authorization.getPermissions();
-            List<Permission> newPermissions = newAuthorization.getPermissions();
+            Collection<Permission> grantedPermissions = authorization.getPermissions();
+            Collection<Permission> newPermissions = newAuthorization.getPermissions();
 
             for (Permission newPermission : newPermissions) {
                 if (!grantedPermissions.contains(newPermission)) {
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java
index e001cef..c647342 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java
@@ -17,10 +17,15 @@
  */
 package org.keycloak.authorization.policy.provider.aggregated;
 
-import java.util.List;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.Decision;
 import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
 import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
@@ -34,31 +39,40 @@ public class AggregatePolicyProvider implements PolicyProvider {
 
     @Override
     public void evaluate(Evaluation evaluation) {
-        //TODO: need to detect deep recursions
         DecisionResultCollector decision = new DecisionResultCollector() {
             @Override
-            protected void onComplete(List<Result> results) {
-                if (results.isEmpty()) {
-                    evaluation.deny();
+            protected void onComplete(Result result) {
+                if (isGranted(result.getResults().iterator().next())) {
+                    evaluation.grant();
                 } else {
-                    Result result = results.iterator().next();
-
-                    if (Effect.PERMIT.equals(result.getEffect())) {
-                        evaluation.grant();
-                    }
+                    evaluation.deny();
                 }
             }
         };
-
-        Policy policy = evaluation.getPolicy();
         AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
+        Policy policy = evaluation.getPolicy();
+        DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
+        Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
+        ResourcePermission permission = evaluation.getPermission();
+
+        for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
+            Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
+            Decision.Effect effect = decisions.get(permission);
+            DefaultEvaluation eval = new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization, decisionCache);
+
+            if (effect == null) {
+                PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
 
-        policy.getAssociatedPolicies().forEach(associatedPolicy -> {
-            PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
-            policyProvider.evaluate(new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization));
-        });
+                policyProvider.evaluate(eval);
+
+                eval.denyIfNoEffect();
+                decisions.put(permission, eval.getEffect());
+            } else {
+                eval.setEffect(effect);
+            }
+        }
 
-        decision.onComplete();
+        decision.onComplete(permission);
     }
 
     @Override
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
index 944ae02..9457836 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java
@@ -19,6 +19,9 @@ package org.keycloak.authorization.policy.provider.js;
 
 import java.util.function.BiFunction;
 
+import javax.script.ScriptContext;
+import javax.script.SimpleScriptContext;
+
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
@@ -40,14 +43,14 @@ class JSPolicyProvider implements PolicyProvider {
     public void evaluate(Evaluation evaluation) {
         Policy policy = evaluation.getPolicy();
         AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
-        final EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
+        EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
 
         try {
-            //how to deal with long running scripts -> timeout?
-            adapter.eval(bindings -> {
-                bindings.put("script", adapter.getScriptModel());
-                bindings.put("$evaluation", evaluation);
-            });
+            SimpleScriptContext context = new SimpleScriptContext();
+
+            context.setAttribute("$evaluation", evaluation, ScriptContext.ENGINE_SCOPE);
+
+            adapter.eval(context);
         }
         catch (Exception e) {
             throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e);
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
index 4180db6..aa0b009 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java
@@ -67,6 +67,12 @@ public class ScriptCache {
 
     public EvaluatableScriptAdapter computeIfAbsent(String id, Function<String, EvaluatableScriptAdapter> function) {
         try {
+            EvaluatableScriptAdapter adapter = removeIfExpired(cache.get(id));
+
+            if (adapter != null) {
+                return adapter;
+            }
+
             if (parkForWriteAndCheckInterrupt()) {
                 return null;
             }
@@ -83,20 +89,6 @@ public class ScriptCache {
         }
     }
 
-    public EvaluatableScriptAdapter get(String uri) {
-        if (parkForReadAndCheckInterrupt()) {
-            return null;
-        }
-
-        CacheEntry cached = cache.get(uri);
-
-        if (cached != null) {
-            return removeIfExpired(cached);
-        }
-
-        return null;
-    }
-
     public void remove(String key) {
         try {
             if (parkForWriteAndCheckInterrupt()) {
@@ -132,16 +124,6 @@ public class ScriptCache {
         return false;
     }
 
-    private boolean parkForReadAndCheckInterrupt() {
-        while (writing.get()) {
-            LockSupport.parkNanos(1L);
-            if (Thread.interrupted()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private static final class CacheEntry {
 
         final String key;
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java
index 5f20043..bd18379 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java
@@ -17,34 +17,42 @@
 package org.keycloak.authorization.policy.provider.permission;
 
 import org.keycloak.authorization.AuthorizationProvider;
+import org.keycloak.authorization.Decision;
 import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class AbstractPermissionProvider implements PolicyProvider {
-
-    public AbstractPermissionProvider() {
-
-    }
+public abstract class AbstractPermissionProvider implements PolicyProvider {
 
     @Override
     public void evaluate(Evaluation evaluation) {
-        if (!(evaluation instanceof DefaultEvaluation)) {
-            throw new IllegalArgumentException("Unexpected evaluation instance type [" + evaluation.getClass() + "]");
-        }
-
-        Policy policy = evaluation.getPolicy();
         AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
+        DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
+        Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
+        Policy policy = evaluation.getPolicy();
+        ResourcePermission permission = evaluation.getPermission();
 
         policy.getAssociatedPolicies().forEach(associatedPolicy -> {
-            PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
-            DefaultEvaluation.class.cast(evaluation).setPolicy(associatedPolicy);
-            policyProvider.evaluate(evaluation);
-            evaluation.denyIfNoEffect();
+            Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
+            Decision.Effect effect = decisions.get(permission);
+
+            if (effect == null) {
+                PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
+                defaultEvaluation.setPolicy(associatedPolicy);
+                policyProvider.evaluate(defaultEvaluation);
+                evaluation.denyIfNoEffect();
+                decisions.put(permission, defaultEvaluation.getEffect());
+            } else {
+                defaultEvaluation.setEffect(effect);
+            }
         });
     }
 
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java
index 272ab2c..348dd8b 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java
@@ -16,9 +16,36 @@
  */
 package org.keycloak.authorization.policy.provider.permission;
 
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class ResourcePolicyProvider extends AbstractPermissionProvider {
 
+    @Override
+    public void evaluate(Evaluation evaluation) {
+        DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
+        Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
+        Policy policy = defaultEvaluation.getParentPolicy();
+        Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(policy, p -> new HashMap<>());
+        ResourcePermission permission = evaluation.getPermission();
+        Decision.Effect effect = decisions.get(permission.getResource());
+
+        if (effect != null) {
+            defaultEvaluation.setEffect(effect);
+            return;
+        }
+
+        super.evaluate(evaluation);
+
+        decisions.put(permission.getResource(), defaultEvaluation.getEffect());
+    }
 }
diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java
index 3f4f9d9..930cbbc 100644
--- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java
+++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java
@@ -16,9 +16,45 @@
  */
 package org.keycloak.authorization.policy.provider.permission;
 
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.Scope;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
+import org.keycloak.authorization.policy.evaluation.Evaluation;
+
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
 public class ScopePolicyProvider extends AbstractPermissionProvider {
 
+    @Override
+    public void evaluate(Evaluation evaluation) {
+        DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
+        Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
+        Policy policy = defaultEvaluation.getParentPolicy();
+        Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(policy, p -> new HashMap<>());
+        ResourcePermission permission = evaluation.getPermission();
+
+        for (Scope scope : permission.getScopes()) {
+            Decision.Effect effect = decisions.get(scope);
+
+            if (effect != null) {
+                defaultEvaluation.setEffect(effect);
+            }
+        }
+
+        Decision.Effect decision = defaultEvaluation.getEffect();
+
+        if (decision == null) {
+            super.evaluate(evaluation);
+
+            for (Scope scope : policy.getScopes()) {
+                decisions.put(scope, defaultEvaluation.getEffect());
+            }
+        }
+    }
 }
diff --git a/core/src/main/java/org/keycloak/AuthorizationContext.java b/core/src/main/java/org/keycloak/AuthorizationContext.java
index 0bc7b44..a78bd63 100644
--- a/core/src/main/java/org/keycloak/AuthorizationContext.java
+++ b/core/src/main/java/org/keycloak/AuthorizationContext.java
@@ -22,9 +22,9 @@ import org.keycloak.representations.AccessToken.Authorization;
 import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
 import org.keycloak.representations.idm.authorization.Permission;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -113,7 +113,7 @@ public class AuthorizationContext {
             return Collections.emptyList();
         }
 
-        return Collections.unmodifiableList(authorization.getPermissions());
+        return Collections.unmodifiableList(new ArrayList<>(authorization.getPermissions()));
     }
 
     public boolean isGranted() {
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 880b949..aa8fbb4 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -22,9 +22,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import org.keycloak.representations.idm.authorization.Permission;
 
 import java.io.Serializable;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -86,13 +86,13 @@ public class AccessToken extends IDToken {
     public static class Authorization implements Serializable {
 
         @JsonProperty("permissions")
-        private List<Permission> permissions;
+        private Collection<Permission> permissions;
 
-        public List<Permission> getPermissions() {
+        public Collection<Permission> getPermissions() {
             return permissions;
         }
 
-        public void setPermissions(List<Permission> permissions) {
+        public void setPermissions(Collection<Permission> permissions) {
             this.permissions = permissions;
         }
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
index a50bf2d..a305cf4 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java
@@ -186,6 +186,7 @@ public class AuthorizationRequest {
 
         private Boolean includeResourceName;
         private Integer limit;
+        private String responseMode;
 
         public Boolean getIncludeResourceName() {
             if (includeResourceName == null) {
@@ -205,5 +206,13 @@ public class AuthorizationRequest {
         public void setLimit(Integer limit) {
             this.limit = limit;
         }
+
+        public void setResponseMode(String responseMode) {
+            this.responseMode = responseMode;
+        }
+
+        public String getResponseMode() {
+            return responseMode;
+        }
     }
 }
diff --git a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
index c1afa01..4635613 100644
--- a/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
+++ b/core/src/main/java/org/keycloak/representations/idm/authorization/Permission.java
@@ -61,6 +61,9 @@ public class Permission {
     }
 
     public String getResourceId() {
+        if (resourceId == null || "".equals(resourceId.trim())) {
+            return null;
+        }
         return this.resourceId;
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
index 8ee94ea..9b92678 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java
@@ -35,7 +35,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class StoreFactoryCacheManager extends CacheManager {
-    private static final Logger logger = Logger.getLogger(RealmCacheManager.class);
+    private static final Logger logger = Logger.getLogger(StoreFactoryCacheManager.class);
 
     public StoreFactoryCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
         super(cache, revisions);
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 95e1d32..9d07523 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
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -756,8 +757,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
                 }
 
                 return Arrays.asList(policy);
-            },
-                    (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+            }, (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
 
             if (result.isEmpty()) {
                 return null;
@@ -780,40 +780,62 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
         public List<Policy> findByResource(String resourceId, String resourceServerId) {
             String cacheKey = getPolicyByResource(resourceId, resourceServerId);
             return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
-                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
+        }
+
+        @Override
+        public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
+            String cacheKey = getPolicyByResource(resourceId, resourceServerId);
+            cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
+                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
         }
 
         @Override
         public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
             String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
             return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
-                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
+                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
+        }
+
+        @Override
+        public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) {
+            String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
+            cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
+                    (revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
         }
 
         @Override
         public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
             if (scopeIds == null) return null;
-            List<Policy> result = new ArrayList<>();
+            Set<Policy> result = new HashSet<>();
 
             for (String id : scopeIds) {
                 String cacheKey = getPolicyByScope(id, resourceServerId);
-                result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
+                result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null));
             }
 
-            return result;
+            return new ArrayList<>(result);
         }
 
         @Override
         public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
             if (scopeIds == null) return null;
-            List<Policy> result = new ArrayList<>();
+            Set<Policy> result = new HashSet<>();
 
             for (String id : scopeIds) {
                 String cacheKey = getPolicyByResourceScope(id, resourceId, resourceServerId);
-                result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
+                result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null));
             }
 
-            return result;
+            return new ArrayList<>(result);
+        }
+
+        @Override
+        public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
+            for (String id : scopeIds) {
+                String cacheKey = getPolicyByResourceScope(id, resourceId, resourceServerId);
+                cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
+            }
         }
 
         @Override
@@ -826,7 +848,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) {
+        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) {
             Q query = cache.get(cacheKey, queryType);
             if (query != null) {
                 logger.tracev("cache hit for key: {0}", cacheKey);
@@ -838,11 +860,34 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
                 if (invalidations.contains(cacheKey)) return model;
                 query = querySupplier.apply(loaded, model);
                 cache.addRevisioned(query, startupRevision);
+                if (consumer != null) {
+                    for (R policy: model) {
+                        consumer.accept(policy);
+                    }
+                }
                 return model;
             } else if (query.isInvalid(invalidations)) {
-                return resultSupplier.get();
+                List<R> policies = resultSupplier.get();
+
+                if (consumer != null) {
+                    for (R policy : policies) {
+                        consumer.accept(policy);
+                    }
+                }
+
+                return policies;
             } else {
-                return query.getPolicies().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
+                Set<String> policies = query.getPolicies();
+
+                if (consumer != null) {
+                    for (String id : policies) {
+                        consumer.accept((R) findById(id, resourceServerId));
+                    }
+
+                    return null;
+                }
+
+                return policies.stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
             }
         }
     }
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 66a31d0..2dadfc5 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
@@ -23,6 +23,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 import javax.persistence.EntityManager;
 import javax.persistence.FlushModeType;
@@ -188,40 +189,52 @@ public class JPAPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByResource(final String resourceId, String resourceServerId) {
+        List<Policy> result = new LinkedList<>();
+
+        findByResource(resourceId, resourceServerId, result::add);
+
+        return result;
+    }
+
+    @Override
+    public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
         TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResource", String.class);
 
         query.setFlushMode(FlushModeType.COMMIT);
         query.setParameter("resourceId", resourceId);
         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);
-            }
-        }
-        return list;
+        PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
+
+        query.getResultList().stream()
+                .map(id -> policyStore.findById(id, resourceServerId))
+                .filter(Objects::nonNull)
+                .forEach(consumer::accept);
     }
 
     @Override
     public List<Policy> findByResourceType(final String resourceType, String resourceServerId) {
+        List<Policy> result = new LinkedList<>();
+
+        findByResourceType(resourceType, resourceServerId, result::add);
+
+        return result;
+    }
+
+    @Override
+    public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) {
         TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResourceType", String.class);
 
         query.setFlushMode(FlushModeType.COMMIT);
         query.setParameter("type", resourceType);
         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);
-            }
-        }
-        return list;
+        PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
+
+        query.getResultList().stream()
+                .map(id -> policyStore.findById(id, resourceServerId))
+                .filter(Objects::nonNull)
+                .forEach(consumer::accept);
     }
 
     @Override
@@ -250,10 +263,15 @@ public class JPAPolicyStore implements PolicyStore {
 
     @Override
     public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
-        if (scopeIds==null || scopeIds.isEmpty()) {
-            return Collections.emptyList();
-        }
+        List<Policy> result = new LinkedList<>();
+
+        findByScopeIds(scopeIds, resourceId, resourceServerId, result::add);
+
+        return result;
+    }
 
+    @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;
 
@@ -268,15 +286,12 @@ public class JPAPolicyStore implements PolicyStore {
         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);
-            }
-        }
-        return list;
+        PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
+
+        query.getResultList().stream()
+                .map(id -> policyStore.findById(id, resourceServerId))
+                .filter(Objects::nonNull)
+                .forEach(consumer::accept);
     }
 
     @Override
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
index 25888b8..4614fdd 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/AuthorizationProvider.java
@@ -22,6 +22,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import org.keycloak.authorization.model.PermissionTicket;
@@ -30,7 +31,7 @@ import org.keycloak.authorization.model.Resource;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.permission.evaluator.Evaluators;
-import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
+import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
 import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
 import org.keycloak.authorization.store.PermissionTicketStore;
@@ -74,18 +75,18 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati
  */
 public final class AuthorizationProvider implements Provider {
 
-    private final DefaultPolicyEvaluator policyEvaluator;
+    private final PolicyEvaluator policyEvaluator;
     private StoreFactory storeFactory;
     private StoreFactory storeFactoryDelegate;
     private final Map<String, PolicyProviderFactory> policyProviderFactories;
     private final KeycloakSession keycloakSession;
     private final RealmModel realm;
 
-    public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map<String, PolicyProviderFactory> policyProviderFactories) {
+    public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map<String, PolicyProviderFactory> policyProviderFactories, PolicyEvaluator policyEvaluator) {
         this.keycloakSession = session;
         this.realm = realm;
         this.policyProviderFactories = policyProviderFactories;
-        this.policyEvaluator = new DefaultPolicyEvaluator(this);
+        this.policyEvaluator = policyEvaluator;
     }
 
     /**
@@ -95,7 +96,7 @@ public final class AuthorizationProvider implements Provider {
      * @return a {@link Evaluators} instance
      */
     public Evaluators evaluators() {
-        return new Evaluators(policyEvaluator);
+        return new Evaluators(this);
     }
 
     /**
@@ -169,6 +170,10 @@ public final class AuthorizationProvider implements Provider {
         return realm;
     }
 
+    public PolicyEvaluator getPolicyEvaluator() {
+        return policyEvaluator;
+    }
+
     @Override
     public void close() {
 
@@ -381,6 +386,11 @@ public final class AuthorizationProvider implements Provider {
             }
 
             @Override
+            public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
+                policyStore.findByResource(resourceId, resourceServerId, consumer);
+            }
+
+            @Override
             public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
                 return policyStore.findByResourceType(resourceType, resourceServerId);
             }
@@ -396,6 +406,11 @@ public final class AuthorizationProvider implements Provider {
             }
 
             @Override
+            public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
+                policyStore.findByScopeIds(scopeIds, resourceId, resourceServerId, consumer);
+            }
+
+            @Override
             public List<Policy> findByType(String type, String resourceServerId) {
                 return policyStore.findByType(type, resourceServerId);
             }
@@ -404,6 +419,11 @@ public final class AuthorizationProvider implements Provider {
             public List<Policy> findDependentPolicies(String id, String resourceServerId) {
                 return policyStore.findDependentPolicies(id, resourceServerId);
             }
+
+            @Override
+            public void findByResourceType(String type, String id, Consumer<Policy> policyConsumer) {
+                policyStore.findByResourceType(type, id, policyConsumer);
+            }
         };
     }
 
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/Decision.java b/server-spi-private/src/main/java/org/keycloak/authorization/Decision.java
index 6ebd086..1fccee6 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/Decision.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/Decision.java
@@ -18,6 +18,7 @@
 
 package org.keycloak.authorization;
 
+import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.Evaluation;
 
 /**
@@ -38,4 +39,7 @@ public interface Decision<D extends Evaluation> {
 
     default void onComplete() {
     }
+
+    default void onComplete(ResourcePermission permission) {
+    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
index 2eda4ac..85ef479 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java
@@ -18,12 +18,12 @@
 
 package org.keycloak.authorization.permission.evaluator;
 
-import java.util.List;
-
+import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 
+import java.util.Collection;
+
 /**
  * A factory for the different {@link PermissionEvaluator} implementations.
  *
@@ -31,17 +31,13 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext;
  */
 public final class Evaluators {
 
-    private final DefaultPolicyEvaluator policyEvaluator;
-
-    public Evaluators(DefaultPolicyEvaluator policyEvaluator) {
-        this.policyEvaluator = policyEvaluator;
-    }
+    private final AuthorizationProvider authorizationProvider;
 
-    public PermissionEvaluator from(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
-        return schedule(permissions, evaluationContext);
+    public Evaluators(AuthorizationProvider authorizationProvider) {
+        this.authorizationProvider = authorizationProvider;
     }
 
-    public PermissionEvaluator schedule(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
-        return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator);
+    public PermissionEvaluator from(Collection<ResourcePermission> permissions, EvaluationContext evaluationContext) {
+        return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, authorizationProvider);
     }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
index c43acac..959b277 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java
@@ -17,16 +17,21 @@
  */
 package org.keycloak.authorization.permission.evaluator;
 
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.Map;
 
+import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
+import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
-import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -36,19 +41,24 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
     private final Iterator<ResourcePermission> permissions;
     private final EvaluationContext executionContext;
     private final PolicyEvaluator policyEvaluator;
+    private final AuthorizationProvider authorizationProvider;
 
-    IterablePermissionEvaluator(Iterator<ResourcePermission> permissions, EvaluationContext executionContext, PolicyEvaluator policyEvaluator) {
+    IterablePermissionEvaluator(Iterator<ResourcePermission> permissions, EvaluationContext executionContext, AuthorizationProvider authorizationProvider) {
         this.permissions = permissions;
         this.executionContext = executionContext;
-        this.policyEvaluator = policyEvaluator;
+        this.authorizationProvider = authorizationProvider;
+        this.policyEvaluator = authorizationProvider.getPolicyEvaluator();
     }
 
     @Override
     public Decision evaluate(Decision decision) {
         try {
+            Map<Policy, Map<Object, Decision.Effect>> decisionCache = new HashMap<>();
+
             while (this.permissions.hasNext()) {
-                this.policyEvaluator.evaluate(this.permissions.next(), this.executionContext, decision);
+                this.policyEvaluator.evaluate(this.permissions.next(), authorizationProvider, executionContext, decision, decisionCache);
             }
+
             decision.onComplete();
         } catch (Throwable cause) {
             decision.onError(cause);
@@ -57,21 +67,11 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
     }
 
     @Override
-    public List<Result> evaluate() {
-        AtomicReference<List<Result>> result = new AtomicReference<>();
+    public Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request) {
+        DecisionPermissionCollector decision = new DecisionPermissionCollector(authorizationProvider, resourceServer, request);
 
-        evaluate(new DecisionResultCollector() {
-            @Override
-            public void onError(Throwable cause) {
-                throw new RuntimeException("Failed to evaluate permissions", cause);
-            }
-
-            @Override
-            protected void onComplete(List<Result> results) {
-                result.set(results);
-            }
-        });
+        evaluate(decision);
 
-        return result.get();
+        return decision.results();
     }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
index ae0d7fd..196c52c 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java
@@ -17,10 +17,12 @@
  */
 package org.keycloak.authorization.permission.evaluator;
 
-import java.util.List;
+import java.util.Collection;
 
 import org.keycloak.authorization.Decision;
-import org.keycloak.authorization.policy.evaluation.Result;
+import org.keycloak.authorization.model.ResourceServer;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
 
 /**
  * An {@link PermissionEvaluator} represents a source of {@link org.keycloak.authorization.permission.ResourcePermission}, responsible for emitting these permissions
@@ -31,5 +33,5 @@ import org.keycloak.authorization.policy.evaluation.Result;
 public interface PermissionEvaluator {
 
     <D extends Decision> D evaluate(D decision);
-    List<Result> evaluate();
+    Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request);
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/AbstractDecisionCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/AbstractDecisionCollector.java
new file mode 100644
index 0000000..151b391
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/AbstractDecisionCollector.java
@@ -0,0 +1,99 @@
+/*
+ * 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.authorization.policy.evaluation;
+
+import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
+import org.keycloak.authorization.permission.ResourcePermission;
+import org.keycloak.representations.idm.authorization.DecisionStrategy;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public abstract class AbstractDecisionCollector implements Decision<DefaultEvaluation> {
+
+    protected final Map<ResourcePermission, Result> results = new LinkedHashMap<>();
+
+    @Override
+    public void onDecision(DefaultEvaluation evaluation) {
+        Policy parentPolicy = evaluation.getParentPolicy();
+
+        if (parentPolicy != null) {
+            results.computeIfAbsent(evaluation.getPermission(), permission -> new Result(permission, evaluation)).policy(parentPolicy).policy(evaluation.getPolicy(), evaluation.getEffect());
+        } else {
+            results.computeIfAbsent(evaluation.getPermission(), permission -> new Result(permission, evaluation)).setStatus(evaluation.getEffect());
+        }
+    }
+
+    @Override
+    public void onComplete() {
+        onComplete(results.values());
+    }
+
+    @Override
+    public void onComplete(ResourcePermission permission) {
+        onComplete(results.get(permission));
+    }
+
+    protected void onComplete(Result result) {
+
+    }
+
+    protected void onComplete(Collection<Result> permissions) {
+
+    }
+
+    protected boolean isGranted(Result.PolicyResult policyResult) {
+        Policy policy = policyResult.getPolicy();
+        DecisionStrategy decisionStrategy = policy.getDecisionStrategy();
+
+        switch (decisionStrategy) {
+            case AFFIRMATIVE:
+                for (Result.PolicyResult decision : policyResult.getAssociatedPolicies()) {
+                    if (Effect.PERMIT.equals(decision.getEffect())) {
+                        return true;
+                    }
+                }
+                return false;
+            case CONSENSUS:
+                int grantCount = 0;
+                int denyCount = policy.getAssociatedPolicies().size();
+
+                for (Result.PolicyResult decision : policyResult.getAssociatedPolicies()) {
+                    if (decision.getEffect().equals(Effect.PERMIT)) {
+                        grantCount++;
+                        denyCount--;
+                    }
+                }
+
+                return grantCount > denyCount;
+            default:
+                // defaults to UNANIMOUS
+                for (Result.PolicyResult decision : policyResult.getAssociatedPolicies()) {
+                    if (Effect.DENY.equals(decision.getEffect())) {
+                        return false;
+                    }
+                }
+                return true;
+        }
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionPermissionCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionPermissionCollector.java
new file mode 100644
index 0000000..701c27a
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionPermissionCollector.java
@@ -0,0 +1,186 @@
+/*
+ * 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.authorization.policy.evaluation;
+
+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.store.ResourceStore;
+import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class DecisionPermissionCollector extends AbstractDecisionCollector {
+
+    private final AuthorizationProvider authorizationProvider;
+    private final ResourceServer resourceServer;
+    private final AuthorizationRequest request;
+    private final List<Permission> permissions = new ArrayList<>();
+
+    public DecisionPermissionCollector(AuthorizationProvider authorizationProvider, ResourceServer resourceServer, AuthorizationRequest request) {
+        this.authorizationProvider = authorizationProvider;
+        this.resourceServer = resourceServer;
+        this.request = request;
+    }
+
+    @Override
+    public void onComplete(Result result) {
+        ResourcePermission permission = result.getPermission();
+        Resource resource = permission.getResource();
+        Set<Scope> grantedScopes = new HashSet<>();
+
+        if (Effect.PERMIT.equals(result.getEffect())) {
+            if (resource != null) {
+                grantedScopes.addAll(resource.getScopes());
+            } else {
+                grantedScopes.addAll(permission.getScopes());
+            }
+
+            grantPermission(authorizationProvider, permissions, permission, grantedScopes, resourceServer, request, result);
+        } else {
+            Set<Scope> deniedScopes = new HashSet<>();
+            List<Result.PolicyResult> userManagedPermissions = new ArrayList<>();
+            Collection<Result.PolicyResult> permissionResults = new ArrayList<>(result.getResults());
+            Iterator<Result.PolicyResult> iterator = permissionResults.iterator();
+
+            while (iterator.hasNext()) {
+                Result.PolicyResult policyResult = iterator.next();
+                Policy policy = policyResult.getPolicy();
+                Set<Scope> policyScopes = policy.getScopes();
+
+                if (isGranted(policyResult)) {
+                    if (isScopePermission(policy)) {
+                        for (Scope scope : permission.getScopes()) {
+                            if (policyScopes.contains(scope)) {
+                                // try to grant any scope from a scope-based permission
+                                grantedScopes.add(scope);
+                            }
+                        }
+                    } else if (isResourcePermission(policy)) {
+                        // we assume that all requested scopes should be granted given that we are processing a resource-based permission.
+                        // Later they will be filtered based on any denied scope, if any.
+                        // TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
+                        grantedScopes.addAll(permission.getScopes());
+                    }
+                    if (resource != null && resource.isOwnerManagedAccess() && "uma".equals(policy.getType())) {
+                        userManagedPermissions.add(policyResult);
+                    }
+                    iterator.remove();
+                } else {
+                    if (isResourcePermission(policy)) {
+                        deniedScopes.addAll(resource.getScopes());
+                    } else {
+                        deniedScopes.addAll(policyScopes);
+                    }
+                }
+            }
+
+            // remove any scope denied from the list of granted scopes
+            grantedScopes.removeAll(deniedScopes);
+
+            if (!userManagedPermissions.isEmpty()) {
+                Set<Scope> scopes = new HashSet<>();
+
+                for (Result.PolicyResult userManagedPermission : userManagedPermissions) {
+                    grantedScopes.addAll(userManagedPermission.getPolicy().getScopes());
+                }
+
+                if (!scopes.isEmpty()) {
+                    grantedScopes.clear();
+                }
+
+                // deny scopes associated with a resource that are not explicitly granted by the user
+                if (!resource.getScopes().isEmpty() && scopes.isEmpty()) {
+                    deniedScopes.addAll(resource.getScopes());
+                } else {
+                    permissionResults.clear();
+                }
+            }
+
+            if (!grantedScopes.isEmpty() || (permissionResults.isEmpty() && deniedScopes.isEmpty())) {
+                grantPermission(authorizationProvider, permissions, permission, grantedScopes, resourceServer, request, result);
+            }
+        }
+    }
+
+    public Collection<Permission> results() {
+        return permissions;
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+        throw new RuntimeException("Failed to evaluate permissions", cause);
+    }
+
+    protected void grantPermission(AuthorizationProvider authorizationProvider, List<Permission> permissions, ResourcePermission permission, Set<Scope> grantedScopes, ResourceServer resourceServer, AuthorizationRequest request, Result result) {
+        Set<String> scopeNames = grantedScopes.stream().map(Scope::getName).collect(Collectors.toSet());
+        Resource resource = permission.getResource();
+
+        if (resource != null) {
+            permissions.add(createPermission(resource, scopeNames, permission.getClaims(), request));
+        } else if (!grantedScopes.isEmpty()) {
+            ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
+            List<Resource> resources = resourceStore.findByScope(grantedScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId());
+
+            if (resources.isEmpty()) {
+                permissions.add(createPermission(null, scopeNames, permission.getClaims(), request));
+            } else {
+                for (Resource grantedResource : resources) {
+                    permissions.add(createPermission(grantedResource, scopeNames, permission.getClaims(), request));
+                }
+            }
+        }
+    }
+
+    private Permission createPermission(Resource resource, Set<String> scopes, Map<String, Set<String>> claims, AuthorizationRequest request) {
+        AuthorizationRequest.Metadata metadata = null;
+
+        if (request != null) {
+            metadata = request.getMetadata();
+        }
+
+        if (resource != null) {
+            String resourceName = metadata == null || metadata.getIncludeResourceName() ? resource.getName() : null;
+            return new Permission(resource.getId(), resourceName, scopes, claims);
+        }
+
+        return new Permission(null, null, scopes, claims);
+    }
+
+    private static boolean isResourcePermission(Policy policy) {
+        return "resource".equals(policy.getType());
+    }
+
+    private static boolean isScopePermission(Policy policy) {
+        return "scope".equals(policy.getType());
+    }
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
index bfba2c6..42609d7 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java
@@ -18,99 +18,14 @@
 
 package org.keycloak.authorization.policy.evaluation;
 
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
+import java.util.Collection;
 import java.util.Map;
-import java.util.stream.Collectors;
 
-import org.keycloak.authorization.Decision;
-import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.representations.idm.authorization.DecisionStrategy;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
+public abstract class DecisionResultCollector extends AbstractDecisionCollector {
 
-    private Map<ResourcePermission, Result> results = new LinkedHashMap<>();
-
-    @Override
-    public void onDecision(DefaultEvaluation evaluation) {
-        Policy parentPolicy = evaluation.getParentPolicy();
-
-        if (parentPolicy != null) {
-            results.computeIfAbsent(evaluation.getPermission(), Result::new).policy(parentPolicy).policy(evaluation.getPolicy()).setStatus(evaluation.getEffect());
-        } else {
-            results.computeIfAbsent(evaluation.getPermission(), Result::new).setStatus(evaluation.getEffect());
-        }
-    }
-
-    @Override
-    public void onComplete() {
-        for (Result result : results.values()) {
-            int deniedCount = result.getResults().size();
-
-            for (Result.PolicyResult policyResult : result.getResults()) {
-                if (isGranted(policyResult)) {
-                    policyResult.setStatus(Effect.PERMIT);
-                    deniedCount--;
-                } else {
-                    policyResult.setStatus(Effect.DENY);
-                }
-            }
-
-            if (deniedCount == 0) {
-                onGrant(result);
-            } else {
-                onDeny(result);
-            }
-        }
-
-        onComplete(results.values().stream().collect(Collectors.toList()));
-    }
-
-    protected void onGrant(Result result) {
-        result.setStatus(Effect.PERMIT);
-    }
-
-    protected abstract void onComplete(List<Result> results);
-
-    protected void onDeny(Result result) {
-        result.setStatus(Effect.DENY);
-    }
-
-    private boolean isGranted(Result.PolicyResult policyResult) {
-        List<Result.PolicyResult> values = policyResult.getAssociatedPolicies();
-
-        int grantCount = 0;
-        int denyCount = policyResult.getPolicy().getAssociatedPolicies().size();
-
-        for (Result.PolicyResult decision : values) {
-            if (decision.getStatus().equals(Effect.PERMIT)) {
-                grantCount++;
-                denyCount--;
-            }
-        }
-
-        Policy policy = policyResult.getPolicy();
-        DecisionStrategy decisionStrategy = policy.getDecisionStrategy();
-
-        if (decisionStrategy == null) {
-            decisionStrategy = DecisionStrategy.UNANIMOUS;
-        }
-
-        if (DecisionStrategy.AFFIRMATIVE.equals(decisionStrategy) && grantCount > 0) {
-            return true;
-        } else if (DecisionStrategy.UNANIMOUS.equals(decisionStrategy) && denyCount == 0) {
-            return true;
-        } else if (DecisionStrategy.CONSENSUS.equals(decisionStrategy)) {
-            if (grantCount > denyCount) {
-                return true;
-            }
-        }
-
-        return false;
-    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
index e92dbba..d46006d 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java
@@ -18,6 +18,8 @@
 
 package org.keycloak.authorization.policy.evaluation;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,25 +53,30 @@ public class DefaultEvaluation implements Evaluation {
     private Policy policy;
     private final Policy parentPolicy;
     private final AuthorizationProvider authorizationProvider;
+    private Map<Policy, Map<Object, Effect>> decisionCache;
     private final Realm realm;
     private Effect effect;
 
-    public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider) {
-        this.permission = permission;
-        this.executionContext = executionContext;
-        this.parentPolicy = parentPolicy;
-        this.decision = decision;
-        this.authorizationProvider = authorizationProvider;
-        this.realm = createRealm();
+    public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
+        this(permission, executionContext, parentPolicy, null, decision, authorizationProvider, decisionCache);
     }
 
     public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
+        this(permission, executionContext, parentPolicy, policy, decision, authorizationProvider, null);
+    }
+
+    public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AuthorizationProvider authorizationProvider) {
+        this(permission, executionContext, null, null, decision, authorizationProvider, Collections.emptyMap());
+    }
+
+    public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
         this.permission = permission;
         this.executionContext = executionContext;
         this.parentPolicy = parentPolicy;
         this.policy = policy;
         this.decision = decision;
         this.authorizationProvider = authorizationProvider;
+        this.decisionCache = decisionCache;
         this.realm = createRealm();
     }
 
@@ -131,6 +138,10 @@ public class DefaultEvaluation implements Evaluation {
         return effect;
     }
 
+    public Map<Policy, Map<Object, Effect>> getDecisionCache() {
+        return decisionCache;
+    }
+
     @Override
     public void denyIfNoEffect() {
         if (this.effect == null) {
@@ -265,4 +276,12 @@ public class DefaultEvaluation implements Evaluation {
     public void setPolicy(Policy policy) {
         this.policy = policy;
     }
+
+    public void setEffect(Effect effect) {
+        if (Effect.PERMIT.equals(effect)) {
+            grant();
+        } else {
+            deny();
+        }
+    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
index dd1a81a..055a5a8 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java
@@ -19,12 +19,10 @@
 package org.keycloak.authorization.policy.evaluation;
 
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 import org.keycloak.authorization.AuthorizationProvider;
@@ -45,163 +43,73 @@ import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
  */
 public class DefaultPolicyEvaluator implements PolicyEvaluator {
 
-    private final AuthorizationProvider authorization;
-    private final StoreFactory storeFactory;
-    private final PolicyStore policyStore;
-    private final ResourceStore resourceStore;
-
-    public DefaultPolicyEvaluator(AuthorizationProvider authorization) {
-        this.authorization = authorization;
-        storeFactory = this.authorization.getStoreFactory();
-        policyStore = storeFactory.getPolicyStore();
-        resourceStore = storeFactory.getResourceStore();
-    }
-
     @Override
-    public void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision) {
+    public void evaluate(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
+        StoreFactory storeFactory = authorizationProvider.getStoreFactory();
+        PolicyStore policyStore = storeFactory.getPolicyStore();
+        ResourceStore resourceStore = storeFactory.getResourceStore();
+
         ResourceServer resourceServer = permission.getResourceServer();
         PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
 
         if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) {
-            createEvaluation(permission, executionContext, decision, null).grant();
+            DefaultEvaluation evaluation = new DefaultEvaluation(permission, executionContext, decision, authorizationProvider);
+
+            evaluation.grant();
+
+            decision.onComplete(permission);
             return;
         }
 
-        AtomicBoolean verified = new AtomicBoolean(false);
-        Consumer<Policy> consumer = createDecisionConsumer(permission, executionContext, decision, verified);
+        Set<Policy> verified = new HashSet<>();
+        Consumer<Policy> policyConsumer = createPolicyEvaluator(permission, authorizationProvider, executionContext, decision, verified, decisionCache);
         Resource resource = permission.getResource();
-        List<Scope> scopes = permission.getScopes();
 
         if (resource != null) {
-            evaluatePolicies(() -> policyStore.findByResource(resource.getId(), resourceServer.getId()), consumer);
+            policyStore.findByResource(resource.getId(), resourceServer.getId(), policyConsumer);
 
             if (resource.getType() != null) {
-                evaluatePolicies(() -> {
-                    List<Policy> policies = policyStore.findByResourceType(resource.getType(), resourceServer.getId());
+                policyStore.findByResourceType(resource.getType(), resourceServer.getId(), policyConsumer);
 
-                    if (!resource.getOwner().equals(resourceServer.getId())) {
-                        for (Resource typedResource : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
-                            policies.addAll(policyStore.findByResource(typedResource.getId(), resourceServer.getId()));
-                        }
+                if (!resource.getOwner().equals(resourceServer.getId())) {
+                    for (Resource typedResource : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
+                        policyStore.findByResource(typedResource.getId(), resourceServer.getId(), policyConsumer);
                     }
-
-                    return policies;
-                }, consumer);
+                }
             }
         }
 
+        List<Scope> scopes = permission.getScopes();
+
         if (!scopes.isEmpty()) {
-            evaluatePolicies(() -> policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), null, resourceServer.getId()), consumer);
+            policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), null, resourceServer.getId(), policyConsumer);
         }
 
-        if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
-            createEvaluation(permission, executionContext, decision, null).grant();
+        if (!verified.isEmpty()) {
+            decision.onComplete(permission);
+            return;
         }
-    }
 
-    private void evaluatePolicies(Supplier<List<Policy>> supplier, Consumer<Policy> consumer) {
-        List<Policy> policies = supplier.get();
-
-        if (!policies.isEmpty()) {
-            policies.forEach(consumer);
+        if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode)) {
+            DefaultEvaluation evaluation = new DefaultEvaluation(permission, executionContext, decision, authorizationProvider);
+            evaluation.grant();
+            decision.onComplete(permission);
         }
     }
 
-    private Consumer<Policy> createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicBoolean verified) {
-        return (parentPolicy) -> {
-            if (!hasRequestedScopes(permission, parentPolicy)) {
+    private Consumer<Policy> createPolicyEvaluator(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Set<Policy> verified, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
+        return parentPolicy -> {
+            if (!verified.add(parentPolicy)) {
                 return;
             }
 
-            PolicyProvider policyProvider = authorization.getProvider(parentPolicy.getType());
+            PolicyProvider policyProvider = authorizationProvider.getProvider(parentPolicy.getType());
 
             if (policyProvider == null) {
                 throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
             }
 
-            DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy);
-
-            policyProvider.evaluate(evaluation);
-
-            verified.compareAndSet(false, true);
+            policyProvider.evaluate(new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorizationProvider, decisionCache));
         };
     }
-
-    private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy) {
-        return new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorization);
-    }
-
-    private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
-        if (permission.getScopes().isEmpty()) {
-            return true;
-        }
-
-        Resource resourcePermission = permission.getResource();
-        Set<Resource> policyResources = policy.getResources();
-
-        if (resourcePermission != null && !policyResources.isEmpty()) {
-            if (!policyResources.stream().filter(resource -> {
-                Iterator<Resource> policyResourceType = policy.getResources().iterator();
-                Resource policyResource = policyResourceType.hasNext() ? policyResourceType.next() : null;
-                return resource.getId().equals(resourcePermission.getId()) || (policyResourceType != null && policyResource.getType() != null && policyResource.getType().equals(resourcePermission.getType()));
-            }).findFirst().isPresent()) {
-                return false;
-            }
-        }
-
-        Set<Scope> scopes = new HashSet<>(policy.getScopes());
-
-        if (scopes.isEmpty()) {
-            Set<Resource> resources = new HashSet<>();
-
-            resources.addAll(policyResources);
-
-            for (Resource resource : resources) {
-                scopes.addAll(resource.getScopes());
-            }
-
-            if (!resources.isEmpty() && scopes.isEmpty()) {
-                return false;
-            }
-
-            if (scopes.isEmpty()) {
-                Resource resource = permission.getResource();
-                String type = resource.getType();
-
-                if (type != null) {
-                    List<Resource> resourcesByType = resourceStore.findByType(type, resource.getResourceServer().getId());
-
-                    for (Resource resourceType : resourcesByType) {
-                        if (resourceType.getOwner().equals(resource.getResourceServer().getId())) {
-                            resources.add(resourceType);
-                        }
-                    }
-                }
-            }
-
-            for (Resource resource : resources) {
-                scopes.addAll(resource.getScopes());
-            }
-        }
-
-        for (Scope givenScope : scopes) {
-            for (Scope scope : permission.getScopes()) {
-                if (givenScope.getId().equals(scope.getId())) {
-                    return true;
-                }
-            }
-        }
-
-        if (policyResources.isEmpty() && scopes.isEmpty()) {
-            String defaultResourceType = policy.getConfig().get("defaultResourceType");
-
-            if (defaultResourceType == null) {
-                return false;
-            }
-
-            return defaultResourceType.equals(permission.getResource().getType());
-        }
-
-        return false;
-    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
index 6b671e3..e6a5ef9 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.authorization.policy.evaluation;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -41,16 +42,16 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken;
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
  */
-public class PermissionTicketAwareDecisionResultCollector extends DecisionResultCollector {
+public class PermissionTicketAwareDecisionResultCollector extends DecisionPermissionCollector {
 
     private final AuthorizationRequest request;
     private PermissionTicketToken ticket;
     private final Identity identity;
     private ResourceServer resourceServer;
     private final AuthorizationProvider authorization;
-    private List<Result> results;
 
     public PermissionTicketAwareDecisionResultCollector(AuthorizationRequest request, PermissionTicketToken ticket, Identity identity, ResourceServer resourceServer, AuthorizationProvider authorization) {
+        super(authorization, resourceServer, request);
         this.request = request;
         this.ticket = ticket;
         this.identity = identity;
@@ -168,13 +169,4 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
             }
         }
     }
-
-    @Override
-    protected void onComplete(List<Result> results) {
-        this.results = results;
-    }
-
-    public List<Result> results() {
-        return results;
-    }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
index c380ba6..7bb905e 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java
@@ -18,9 +18,13 @@
 
 package org.keycloak.authorization.policy.evaluation;
 
+import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.permission.ResourcePermission;
 
+import java.util.Map;
+
 /**
  * <p>A {@link PolicyEvaluator} evaluates authorization policies based on a given {@link ResourcePermission}, sending
  * the results to a {@link Decision} point through the methods defined in that interface.
@@ -34,5 +38,5 @@ public interface PolicyEvaluator {
      *
      * @param decision a {@link Decision} point to where notifications events will be delivered during the evaluation
      */
-    void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision);
+    void evaluate(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Map<Policy, Map<Object, Decision.Effect>> decisionCache);
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
index 325af3d..95e3993 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java
@@ -22,8 +22,9 @@ import org.keycloak.authorization.Decision.Effect;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.permission.ResourcePermission;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -31,33 +32,29 @@ import java.util.List;
 public class Result {
 
     private final ResourcePermission permission;
-    private List<PolicyResult> results = new ArrayList<>();
-    private Effect status;
+    private final Map<String, PolicyResult> results = new HashMap<>();
+    private final Evaluation evaluation;
+    private Effect status = Effect.DENY;
 
-    public Result(ResourcePermission permission) {
+    public Result(ResourcePermission permission, Evaluation evaluation) {
         this.permission = permission;
+        this.evaluation = evaluation;
     }
 
     public ResourcePermission getPermission() {
         return permission;
     }
 
-    public List<PolicyResult> getResults() {
-        return results;
+    public Collection<PolicyResult> getResults() {
+        return results.values();
     }
 
-    public PolicyResult policy(Policy policy) {
-        for (PolicyResult result : this.results) {
-            if (result.getPolicy().equals(policy)) {
-                return result;
-            }
-        }
-
-        PolicyResult policyResult = new PolicyResult(policy);
-
-        this.results.add(policyResult);
+    public Evaluation getEvaluation() {
+        return evaluation;
+    }
 
-        return policyResult;
+    public PolicyResult policy(Policy policy) {
+        return results.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy));
     }
 
     public void setStatus(final Effect status) {
@@ -71,50 +68,40 @@ public class Result {
     public static class PolicyResult {
 
         private final Policy policy;
-        private List<PolicyResult> associatedPolicies = new ArrayList<>();
-        private Effect status;
+        private final Map<String, PolicyResult> associatedPolicies = new HashMap<>();
+        private Effect effect = Effect.DENY;
 
-        public PolicyResult(Policy policy) {
+        public PolicyResult(Policy policy, Effect status) {
             this.policy = policy;
+            this.effect = status;
         }
 
-        public PolicyResult status(Effect status) {
-            this.status = status;
-            return this;
-        }
-
-        public PolicyResult policy(Policy policy) {
-            return getPolicy(policy, this.associatedPolicies);
+        public PolicyResult(Policy policy) {
+            this(policy, Effect.DENY);
         }
 
-        private PolicyResult getPolicy(Policy policy, List<PolicyResult> results) {
-            for (PolicyResult result : results) {
-                if (result.getPolicy().equals(policy)) {
-                    return result;
-                }
-            }
-
-            PolicyResult policyResult = new PolicyResult(policy);
+        public PolicyResult policy(Policy policy, Effect effect) {
+            PolicyResult result = associatedPolicies.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy, effect));
 
-            results.add(policyResult);
+            result.setEffect(effect);
 
-            return policyResult;
+            return result;
         }
 
         public Policy getPolicy() {
             return policy;
         }
 
-        public List<PolicyResult> getAssociatedPolicies() {
-            return associatedPolicies;
+        public Collection<PolicyResult> getAssociatedPolicies() {
+            return associatedPolicies.values();
         }
 
-        public Effect getStatus() {
-            return status;
+        public Effect getEffect() {
+            return effect;
         }
 
-        public void setStatus(final Effect status) {
-            this.status = status;
+        public void setEffect(final Effect status) {
+            this.effect = status;
         }
     }
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
index 29ff235..fee258b 100644
--- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
+++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java
@@ -20,6 +20,7 @@ package org.keycloak.authorization.store;
 
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.ResourceServer;
@@ -93,6 +94,8 @@ public interface PolicyStore {
      */
     List<Policy> findByResource(String resourceId, String resourceServerId);
 
+    void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer);
+
     /**
      * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given <code>type</code>.
      *
@@ -121,6 +124,8 @@ public interface PolicyStore {
      */
     List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId);
 
+    void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer);
+
     /**
      * Returns a list of {@link Policy} with the given <code>type</code>.
      *
@@ -138,4 +143,6 @@ public interface PolicyStore {
      * @return a list of policies that depends on the a policy with the given identifier
      */
     List<Policy> findDependentPolicies(String id, String resourceServerId);
+
+    void findByResourceType(String type, String id, Consumer<Policy> policyConsumer);
 }
diff --git a/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
index 2a76add..2b78858 100644
--- a/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/scripting/EvaluatableScriptAdapter.java
@@ -1,5 +1,7 @@
 package org.keycloak.scripting;
 
+import javax.script.ScriptContext;
+
 import org.keycloak.models.ScriptModel;
 
 /**
@@ -11,4 +13,5 @@ public interface EvaluatableScriptAdapter {
     ScriptModel getScriptModel();
 
     Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException;
+    Object eval(ScriptContext context) throws ScriptExecutionException;
 }
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 7aac436..bf80cc3 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -21,7 +21,6 @@ package org.keycloak.authorization.admin;
 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.List;
@@ -37,6 +36,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import org.jboss.logging.Logger;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder;
@@ -47,23 +47,24 @@ 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.DecisionPermissionCollector;
+import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.authorization.policy.evaluation.Result;
 import org.keycloak.authorization.store.ScopeStore;
 import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.authorization.util.Permissions;
-import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.ClientSessionContext;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.protocol.oidc.TokenManager;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.authorization.AuthorizationRequest;
+import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@@ -78,6 +79,8 @@ import org.keycloak.sessions.AuthenticationSessionModel;
  */
 public class PolicyEvaluationService {
 
+    private static final Logger logger = Logger.getLogger(PolicyEvaluationService.class);
+
     private final AuthorizationProvider authorization;
     private final AdminPermissionEvaluator auth;
     private final ResourceServer resourceServer;
@@ -117,14 +120,15 @@ public class PolicyEvaluationService {
 
             return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity), request), resourceServer, authorization, identity)).build();
         } catch (Exception e) {
+            logger.error("Error while evaluating permissions", e);
             throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
         } finally {
             identity.close();
         }
     }
 
-    private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
-        return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate();
+    private EvaluationDecisionCollector evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
+        return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate(new EvaluationDecisionCollector(authorization, resourceServer, request));
     }
 
     private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
@@ -171,7 +175,7 @@ public class PolicyEvaluationService {
 
             if (resource.getId() != null) {
                 Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId(), resourceServer.getId());
-                return Arrays.asList(Permissions.createResourcePermissions(resourceModel, scopeNames, authorization, request)).stream();
+                return new ArrayList<>(Arrays.asList(Permissions.createResourcePermissions(resourceModel, scopeNames, authorization, request))).stream();
             } else if (resource.getType() != null) {
                 return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().map(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization, request));
             } else {
@@ -180,7 +184,7 @@ public class PolicyEvaluationService {
                 List<ResourcePermission> collect = new ArrayList<>();
 
                 if (!scopes.isEmpty()) {
-                    collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, Arrays.asList(scope), resourceServer)).collect(Collectors.toList()));
+                    collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, new ArrayList<>(Arrays.asList(scope)), resourceServer)).collect(Collectors.toList()));
                 } else {
                     collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization, request));
                 }
@@ -261,4 +265,31 @@ public class PolicyEvaluationService {
 
         return new CloseableKeycloakIdentity(accessToken, keycloakSession, userSession);
     }
+
+    public class EvaluationDecisionCollector extends DecisionPermissionCollector {
+
+        public EvaluationDecisionCollector(AuthorizationProvider authorizationProvider, ResourceServer resourceServer, AuthorizationRequest request) {
+            super(authorizationProvider, resourceServer, request);
+        }
+
+        @Override
+        protected boolean isGranted(Result.PolicyResult policyResult) {
+            if (super.isGranted(policyResult)) {
+                policyResult.setEffect(Effect.PERMIT);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected void grantPermission(AuthorizationProvider authorizationProvider, List<Permission> permissions, ResourcePermission permission, Set<Scope> grantedScopes, ResourceServer resourceServer, AuthorizationRequest request, Result result) {
+            result.setStatus(Effect.PERMIT);
+            result.getPermission().getScopes().retainAll(grantedScopes);
+            super.grantPermission(authorizationProvider, permissions, permission, grantedScopes, resourceServer, request, result);
+        }
+
+        public Collection<Result> getResults() {
+            return results.values();
+        }
+    }
 }
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
index 3842a94..e4bf9f8 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java
@@ -18,13 +18,13 @@ package org.keycloak.authorization.admin.representation;
 
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.Decision;
+import org.keycloak.authorization.admin.PolicyEvaluationService;
 import org.keycloak.authorization.common.KeycloakIdentity;
 import org.keycloak.authorization.model.PermissionTicket;
 import org.keycloak.authorization.model.Policy;
 import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.policy.evaluation.Result;
-import org.keycloak.authorization.util.Permissions;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserModel;
@@ -38,6 +38,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -52,13 +53,13 @@ import java.util.stream.Stream;
  * @version $Revision: 1 $
  */
 public class PolicyEvaluationResponseBuilder {
-    public static PolicyEvaluationResponse build(List<Result> results, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) {
+    public static PolicyEvaluationResponse build(PolicyEvaluationService.EvaluationDecisionCollector decision, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) {
         PolicyEvaluationResponse response = new PolicyEvaluationResponse();
         List<PolicyEvaluationResponse.EvaluationResultRepresentation> resultsRep = new ArrayList<>();
         AccessToken accessToken = identity.getAccessToken();
         AccessToken.Authorization authorizationData = new AccessToken.Authorization();
 
-        authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer));
+        authorizationData.setPermissions(decision.results());
         accessToken.setAuthorization(authorizationData);
 
         ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId());
@@ -69,6 +70,8 @@ public class PolicyEvaluationResponseBuilder {
 
         response.setRpt(accessToken);
 
+        Collection<Result> results = decision.getResults();
+
         if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) {
             response.setStatus(DecisionEffect.DENY);
         } else {
@@ -217,7 +220,7 @@ public class PolicyEvaluationResponseBuilder {
 
         policyResultRep.setPolicy(representation);
 
-        if (result.getStatus() == Decision.Effect.DENY) {
+        if (result.getEffect() == Decision.Effect.DENY) {
             policyResultRep.setStatus(DecisionEffect.DENY);
             policyResultRep.setScopes(representation.getScopes());
         } else {
diff --git a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
index 05a732f..e9ebee3 100644
--- a/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
+++ b/services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java
@@ -19,6 +19,7 @@ package org.keycloak.authorization.authorization;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -46,7 +47,6 @@ import org.keycloak.authorization.model.ResourceServer;
 import org.keycloak.authorization.model.Scope;
 import org.keycloak.authorization.permission.ResourcePermission;
 import org.keycloak.authorization.policy.evaluation.PermissionTicketAwareDecisionResultCollector;
-import org.keycloak.authorization.policy.evaluation.Result;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.ScopeStore;
@@ -88,6 +88,9 @@ public class AuthorizationTokenService {
     public static final String CLAIM_TOKEN_FORMAT_ID_TOKEN = "http://openid.net/specs/openid-connect-core-1_0.html#IDToken";
 
     private static final Logger logger = Logger.getLogger(AuthorizationTokenService.class);
+    private static final String RESPONSE_MODE_DECISION = "decision";
+    private static final String RESPONSE_MODE_PERMISSIONS = "permissions";
+    private static final String RESPONSE_MODE_DECISION_RESULT = "result";
     private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
 
     static {
@@ -125,28 +128,20 @@ public class AuthorizationTokenService {
         });
     }
 
-    private final TokenManager tokenManager;
-    private final EventBuilder event;
-    private final HttpRequest httpRequest;
-    private final AuthorizationProvider authorization;
-    private final Cors cors;
-
-    public AuthorizationTokenService(AuthorizationProvider authorization, TokenManager tokenManager, EventBuilder event, HttpRequest httpRequest, Cors cors) {
-        this.tokenManager = tokenManager;
-        this.event = event;
-        this.httpRequest = httpRequest;
-        this.authorization = authorization;
-        this.cors = cors;
+    private static final AuthorizationTokenService INSTANCE = new AuthorizationTokenService();
+
+    public static AuthorizationTokenService instance() {
+        return INSTANCE;
     }
 
-    public Response authorize(AuthorizationRequest request) {
+    public Response authorize(KeycloakAuthorizationRequest request) {
         if (request == null) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
         }
 
         // it is not secure to allow public clients to push arbitrary claims because message can be tampered
         if (isPublicClientRequestingEntitlementWithClaims(request)) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
         }
 
         try {
@@ -154,35 +149,46 @@ public class AuthorizationTokenService {
 
             request.setClaims(ticket.getClaims());
 
-            ResourceServer resourceServer = getResourceServer(ticket);
+            ResourceServer resourceServer = getResourceServer(ticket, request);
             KeycloakEvaluationContext evaluationContext = createEvaluationContext(request);
             KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
-            List<Result> evaluation;
+            Collection<Permission> permissions;
 
-            if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
-                evaluation = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
-            } else if(!request.getPermissions().getPermissions().isEmpty()) {
-                evaluation = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
+            if (request.getTicket() != null) {
+                permissions = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
+            } else if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
+                permissions = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
             } else {
-                evaluation = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
+                permissions = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
             }
 
-            List<Permission> permissions = Permissions.permits(evaluation, request.getMetadata(), authorization, resourceServer);
-
             if (isGranted(ticket, request, permissions)) {
-                ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId());
-                AuthorizationResponse response = createAuthorizationResponse(identity, permissions, request, targetClient);
+                AuthorizationProvider authorization = request.getAuthorization();
+                ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
+                Metadata metadata = request.getMetadata();
+                String responseMode = metadata != null ? metadata.getResponseMode() : null;
+
+                if (responseMode != null) {
+                    if (RESPONSE_MODE_DECISION.equals(metadata.getResponseMode())) {
+                        Map<String, Object> responseClaims = new HashMap<>();
 
-                return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
-                        .allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient)
-                        .allowedMethods(HttpMethod.POST)
-                        .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+                        responseClaims.put(RESPONSE_MODE_DECISION_RESULT, true);
+
+                        return createSuccessfulResponse(responseClaims, targetClient, request);
+                    } else if (RESPONSE_MODE_PERMISSIONS.equals(metadata.getResponseMode())) {
+                        return createSuccessfulResponse(permissions, targetClient, request);
+                    } else {
+                        throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Invalid response_mode", Status.BAD_REQUEST);
+                    }
+                } else {
+                    return createSuccessfulResponse(createAuthorizationResponse(identity, permissions, request, targetClient), targetClient, request);
+                }
             }
 
             if (request.isSubmitRequest()) {
-                throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
+                throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
             } else {
-                throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
+                throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
             }
         } catch (ErrorResponseException | CorsErrorResponseException cause) {
             if (logger.isDebugEnabled()) {
@@ -191,45 +197,55 @@ public class AuthorizationTokenService {
             throw cause;
         } catch (Exception cause) {
             logger.error("Unexpected error while evaluating permissions", cause);
-            throw new CorsErrorResponseException(cors, OAuthErrorException.SERVER_ERROR, "Unexpected error while evaluating permissions", Status.INTERNAL_SERVER_ERROR);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.SERVER_ERROR, "Unexpected error while evaluating permissions", Status.INTERNAL_SERVER_ERROR);
         }
     }
 
-    private boolean isPublicClientRequestingEntitlementWithClaims(AuthorizationRequest request) {
-        return request.getClaimToken() != null && getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
+    private Response createSuccessfulResponse(Object response, ClientModel targetClient, KeycloakAuthorizationRequest request) {
+        return Cors.add(request.getHttpRequest(), Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
+                .allowedOrigins(request.getKeycloakSession().getContext().getUri(), targetClient)
+                .allowedMethods(HttpMethod.POST)
+                .exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
+    }
+
+    private boolean isPublicClientRequestingEntitlementWithClaims(KeycloakAuthorizationRequest request) {
+        return request.getClaimToken() != null && request.getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
     }
 
-    private List<Result> evaluatePermissions(AuthorizationRequest authorizationRequest, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+    private Collection<Permission> evaluatePermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+        AuthorizationProvider authorization = request.getAuthorization();
         return authorization.evaluators()
-                .from(createPermissions(ticket, authorizationRequest, resourceServer, identity, authorization), evaluationContext)
-                .evaluate();
+                .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
+                .evaluate(resourceServer, request);
     }
 
-    private List<Result> evaluateUserManagedPermissions(AuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+    private Collection<Permission> evaluateUserManagedPermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+        AuthorizationProvider authorization = request.getAuthorization();
         return authorization.evaluators()
                 .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
                 .evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results();
     }
 
-    private List<Result> evaluateAllPermissions(AuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+    private Collection<Permission> evaluateAllPermissions(KeycloakAuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
+        AuthorizationProvider authorization = request.getAuthorization();
         return authorization.evaluators()
                 .from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext)
-                .evaluate();
+                .evaluate(resourceServer, request);
     }
 
-    private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) {
-        KeycloakSession keycloakSession = getKeycloakSession();
+    private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, Collection<Permission> entitlements, KeycloakAuthorizationRequest request, ClientModel targetClient) {
+        KeycloakSession keycloakSession = request.getKeycloakSession();
         AccessToken accessToken = identity.getAccessToken();
-        UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(getRealm(), accessToken.getSessionState());
-        ClientModel client = getRealm().getClientByClientId(accessToken.getIssuedFor());
+        RealmModel realm = request.getRealm();
+        UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(realm, accessToken.getSessionState());
+        ClientModel client = realm.getClientByClientId(accessToken.getIssuedFor());
         AuthenticatedClientSessionModel clientSession = userSessionModel.getAuthenticatedClientSessionByClient(client.getId());
-
         ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession);
-
-        AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(getRealm(), clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx)
+        TokenManager tokenManager = request.getTokenManager();
+        EventBuilder event = request.getEvent();
+        AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx)
                 .generateAccessToken()
                 .generateRefreshToken();
-
         AccessToken rpt = responseBuilder.getAccessToken();
 
         rpt.issuedFor(client.getClientId());
@@ -262,7 +278,7 @@ public class AuthorizationTokenService {
         Authorization previousAuthorization = previousRpt.getAuthorization();
 
         if (previousAuthorization != null) {
-            List<Permission> previousPermissions = previousAuthorization.getPermissions();
+            Collection<Permission> previousPermissions = previousAuthorization.getPermissions();
 
             if (previousPermissions != null) {
                 for (Permission previousPermission : previousPermissions) {
@@ -276,7 +292,7 @@ public class AuthorizationTokenService {
         return true;
     }
 
-    private PermissionTicketToken getPermissionTicket(AuthorizationRequest request) {
+    private PermissionTicketToken getPermissionTicket(KeycloakAuthorizationRequest request) {
         // if there is a ticket is because it is a UMA flow and the ticket was sent by the client after obtaining it from the target resource server
         if (request.getTicket() != null) {
             return verifyPermissionTicket(request);
@@ -292,32 +308,33 @@ public class AuthorizationTokenService {
         return permissions;
     }
 
-    private ResourceServer getResourceServer(PermissionTicketToken ticket) {
+    private ResourceServer getResourceServer(PermissionTicketToken ticket, KeycloakAuthorizationRequest request) {
+        AuthorizationProvider authorization = request.getAuthorization();
         StoreFactory storeFactory = authorization.getStoreFactory();
         ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore();
         String[] audience = ticket.getAudience();
 
         if (audience == null || audience.length == 0) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "You must provide the audience", Status.BAD_REQUEST);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "You must provide the audience", Status.BAD_REQUEST);
         }
 
-        ClientModel clientModel = getRealm().getClientByClientId(audience[0]);
+        ClientModel clientModel = request.getRealm().getClientByClientId(audience[0]);
 
         if (clientModel == null) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Unknown resource server id.", Status.BAD_REQUEST);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Unknown resource server id.", Status.BAD_REQUEST);
         }
 
         ResourceServer resourceServer = resourceServerStore.findById(clientModel.getId());
 
         if (resourceServer == null) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST);
         }
 
         return resourceServer;
     }
 
-    private KeycloakEvaluationContext createEvaluationContext(AuthorizationRequest authorizationRequest) {
-        String claimTokenFormat = authorizationRequest.getClaimTokenFormat();
+    private KeycloakEvaluationContext createEvaluationContext(KeycloakAuthorizationRequest request) {
+        String claimTokenFormat = request.getClaimTokenFormat();
 
         if (claimTokenFormat == null) {
             claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
@@ -326,13 +343,13 @@ public class AuthorizationTokenService {
         BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
 
         if (evaluationContextProvider == null) {
-            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);
+            throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);
         }
 
-        return evaluationContextProvider.apply(authorizationRequest, authorization);
+        return evaluationContextProvider.apply(request, request.getAuthorization());
     }
 
-    private List<ResourcePermission> createPermissions(PermissionTicketToken ticket, AuthorizationRequest request, ResourceServer resourceServer, KeycloakIdentity identity, AuthorizationProvider authorization) {
+    private Collection<ResourcePermission> createPermissions(PermissionTicketToken ticket, KeycloakAuthorizationRequest request, ResourceServer resourceServer, KeycloakIdentity identity, AuthorizationProvider authorization) {
         StoreFactory storeFactory = authorization.getStoreFactory();
         Map<String, ResourcePermission> permissionsToEvaluate = new LinkedHashMap<>();
         ResourceStore resourceStore = storeFactory.getResourceStore();
@@ -340,30 +357,35 @@ public class AuthorizationTokenService {
         Metadata metadata = request.getMetadata();
         Integer limit = metadata != null ? metadata.getLimit() : null;
 
-        for (Permission requestedResource : ticket.getPermissions()) {
+        for (Permission permission : ticket.getPermissions()) {
             if (limit != null && limit <= 0) {
                 break;
             }
 
-            Set<String> requestedScopes = requestedResource.getScopes();
+            Set<String> requestedScopes = permission.getScopes();
 
-            if (requestedResource.getScopes() == null) {
+            if (permission.getScopes() == null) {
                 requestedScopes = new HashSet<>();
             }
 
             List<Resource> existingResources = new ArrayList<>();
+            String resourceId = permission.getResourceId();
 
-            if (requestedResource.getResourceId() != null) {
-                Resource resource = resourceStore.findById(requestedResource.getResourceId(), resourceServer.getId());
+            if (resourceId != null) {
+                Resource resource = null;
+
+                if (resourceId.indexOf('-') != -1) {
+                    resource = resourceStore.findById(resourceId, resourceServer.getId());
+                }
 
                 if (resource != null) {
                     existingResources.add(resource);
                 } else {
-                    String resourceName = requestedResource.getResourceId();
+                    String resourceName = resourceId;
                     Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId());
 
                     if (ownerResource != null) {
-                        requestedResource.setResourceId(ownerResource.getId());
+                        permission.setResourceId(ownerResource.getId());
                         existingResources.add(ownerResource);
                     }
 
@@ -371,7 +393,7 @@ public class AuthorizationTokenService {
                         Resource serverResource = resourceStore.findByName(resourceName, resourceServer.getId());
 
                         if (serverResource != null) {
-                            requestedResource.setResourceId(serverResource.getId());
+                            permission.setResourceId(serverResource.getId());
                             existingResources.add(serverResource);
                         }
                     }
@@ -386,28 +408,28 @@ public class AuthorizationTokenService {
 
             List<Scope> requestedScopesModel = requestedScopes.stream().map(s -> scopeStore.findByName(s, resourceServer.getId())).filter(Objects::nonNull).collect(Collectors.toList());
 
-            if (requestedResource.getResourceId() != null && !"".equals(requestedResource.getResourceId().trim()) && existingResources.isEmpty()) {
-                throw new CorsErrorResponseException(cors, "invalid_resource", "Resource with id [" + requestedResource.getResourceId() + "] does not exist.", Status.BAD_REQUEST);
+            if (resourceId != null && existingResources.isEmpty()) {
+                throw new CorsErrorResponseException(request.getCors(), "invalid_resource", "Resource with id [" + resourceId + "] does not exist.", Status.BAD_REQUEST);
             }
 
-            if ((requestedResource.getScopes() != null && !requestedResource.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) {
-                throw new CorsErrorResponseException(cors, "invalid_scope", "One of the given scopes " + requestedResource.getScopes() + " are invalid", Status.BAD_REQUEST);
+            if ((permission.getScopes() != null && !permission.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) {
+                throw new CorsErrorResponseException(request.getCors(), "invalid_scope", "One of the given scopes " + permission.getScopes() + " are invalid", Status.BAD_REQUEST);
             }
 
             if (!existingResources.isEmpty()) {
                 for (Resource resource : existingResources) {
-                    ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
+                    ResourcePermission perm = permissionsToEvaluate.get(resource.getId());
 
-                    if (permission == null) {
-                        permission = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
-                        permissionsToEvaluate.put(resource.getId(), permission);
+                    if (perm == null) {
+                        perm = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
+                        permissionsToEvaluate.put(resource.getId(), perm);
                         if (limit != null) {
                             limit--;
                         }
                     } else {
                         for (Scope scope : requestedScopesModel) {
-                            if (!permission.getScopes().contains(scope)) {
-                                permission.getScopes().add(scope);
+                            if (!perm.getScopes().contains(scope)) {
+                                perm.getScopes().add(scope);
                             }
                         }
                     }
@@ -415,14 +437,16 @@ public class AuthorizationTokenService {
             } else {
                 List<Resource> resources = resourceStore.findByScope(requestedScopesModel.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId());
 
-                for (Resource resource : resources) {
-                    permissionsToEvaluate.put(resource.getId(), Permissions.createResourcePermissions(resource, requestedScopes, authorization, request));
-                    if (limit != null) {
-                        limit--;
+                if (resources.isEmpty()) {
+                    permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
+                } else {
+                    for (Resource resource : resources) {
+                        permissionsToEvaluate.put(resource.getId(), Permissions.createResourcePermissions(resource, requestedScopes, authorization, request));
+                        if (limit != null) {
+                            limit--;
+                        }
                     }
                 }
-
-                permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
             }
         }
 
@@ -432,7 +456,7 @@ public class AuthorizationTokenService {
             AccessToken.Authorization authorizationData = rpt.getAuthorization();
 
             if (authorizationData != null) {
-                List<Permission> permissions = authorizationData.getPermissions();
+                Collection<Permission> permissions = authorizationData.getPermissions();
 
                 if (permissions != null) {
                     for (Permission grantedPermission : permissions) {
@@ -478,30 +502,30 @@ public class AuthorizationTokenService {
             }
         }
 
-        return new ArrayList<>(permissionsToEvaluate.values());
+        return permissionsToEvaluate.values();
     }
 
-    private PermissionTicketToken verifyPermissionTicket(AuthorizationRequest request) {
+    private PermissionTicketToken verifyPermissionTicket(KeycloakAuthorizationRequest request) {
         String ticketString = request.getTicket();
 
-        if (ticketString == null || !Tokens.verifySignature(getKeycloakSession(), getRealm(), ticketString)) {
-            throw new CorsErrorResponseException(cors, "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
+        if (ticketString == null || !Tokens.verifySignature(request.getKeycloakSession(), request.getRealm(), ticketString)) {
+            throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
         }
 
         try {
             PermissionTicketToken ticket = new JWSInput(ticketString).readJsonContent(PermissionTicketToken.class);
 
             if (!ticket.isActive()) {
-                throw new CorsErrorResponseException(cors, "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
+                throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
             }
 
             return ticket;
         } catch (JWSInputException e) {
-            throw new CorsErrorResponseException(cors, "invalid_ticket", "Could not parse permission ticket.", Status.FORBIDDEN);
+            throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Could not parse permission ticket.", Status.FORBIDDEN);
         }
     }
 
-    private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, List<Permission> permissions) {
+    private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, Collection<Permission> permissions) {
         List<Permission> requestedPermissions = ticket.getPermissions();
 
         // denies in case a rpt was provided along with the authorization request but any requested permission was not granted
@@ -512,11 +536,48 @@ public class AuthorizationTokenService {
         return !permissions.isEmpty();
     }
 
-    private KeycloakSession getKeycloakSession() {
-        return this.authorization.getKeycloakSession();
-    }
+    public static class KeycloakAuthorizationRequest extends AuthorizationRequest {
+
+        private final AuthorizationProvider authorization;
+        private final TokenManager tokenManager;
+        private final EventBuilder event;
+        private final HttpRequest httpRequest;
+        private final Cors cors;
+
+        public KeycloakAuthorizationRequest(AuthorizationProvider authorization, TokenManager tokenManager, EventBuilder event, HttpRequest request, Cors cors) {
+            this.authorization = authorization;
+            this.tokenManager = tokenManager;
+            this.event = event;
+            httpRequest = request;
+            this.cors = cors;
+        }
+
+        TokenManager getTokenManager() {
+            return tokenManager;
+        }
+
+        EventBuilder getEvent() {
+            return event;
+        }
 
-    private RealmModel getRealm() {
-        return getKeycloakSession().getContext().getRealm();
+        HttpRequest getHttpRequest() {
+            return httpRequest;
+        }
+
+        AuthorizationProvider getAuthorization() {
+            return authorization;
+        }
+
+        Cors getCors() {
+            return cors;
+        }
+
+        KeycloakSession getKeycloakSession() {
+            return getAuthorization().getKeycloakSession();
+        }
+
+        RealmModel getRealm() {
+            return getKeycloakSession().getContext().getRealm();
+        }
     }
 }
diff --git a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java
index d9d7b2d..24390b3 100644
--- a/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java
+++ b/services/src/main/java/org/keycloak/authorization/DefaultAuthorizationProviderFactory.java
@@ -23,13 +23,13 @@ import java.util.List;
 import java.util.Map;
 
 import org.keycloak.Config;
+import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
+import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
 import org.keycloak.authorization.policy.provider.PolicyProvider;
 import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
-import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
 import org.keycloak.provider.ProviderFactory;
 
 /**
@@ -38,6 +38,7 @@ import org.keycloak.provider.ProviderFactory;
 public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
 
     private Map<String, PolicyProviderFactory> policyProviderFactories;
+    private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator();
 
     @Override
     public AuthorizationProvider create(KeycloakSession session) {
@@ -65,7 +66,7 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
 
     @Override
     public AuthorizationProvider create(KeycloakSession session, RealmModel realm) {
-        return new AuthorizationProvider(session, realm, policyProviderFactories);
+        return new AuthorizationProvider(session, realm, policyProviderFactories, policyEvaluator);
     }
 
     private Map<String, PolicyProviderFactory> configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) {
diff --git a/services/src/main/java/org/keycloak/authorization/util/Permissions.java b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
index 903623b..f25dc12 100644
--- a/services/src/main/java/org/keycloak/authorization/util/Permissions.java
+++ b/services/src/main/java/org/keycloak/authorization/util/Permissions.java
@@ -21,8 +21,6 @@ package org.keycloak.authorization.util;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -32,21 +30,17 @@ import java.util.stream.Collectors;
 import javax.ws.rs.core.Response.Status;
 
 import org.keycloak.authorization.AuthorizationProvider;
-import org.keycloak.authorization.Decision.Effect;
 import org.keycloak.authorization.identity.Identity;
 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;
 import org.keycloak.authorization.permission.ResourcePermission;
-import org.keycloak.authorization.policy.evaluation.Result;
 import org.keycloak.authorization.store.ResourceStore;
 import org.keycloak.authorization.store.ScopeStore;
 import org.keycloak.authorization.store.StoreFactory;
 import org.keycloak.representations.idm.authorization.AuthorizationRequest;
 import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
-import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.services.ErrorResponseException;
 
 /**
@@ -54,8 +48,8 @@ import org.keycloak.services.ErrorResponseException;
  */
 public final class Permissions {
 
-    public static List<ResourcePermission> permission(ResourceServer server, Resource resource, Scope scope) {
-       return Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), server));
+    public static ResourcePermission permission(ResourceServer server, Resource resource, Scope scope) {
+       return new ResourcePermission(resource, new ArrayList<>(Arrays.asList(scope)), server);
     }
 
     /**
@@ -73,22 +67,40 @@ public final class Permissions {
         List<ResourcePermission> permissions = new ArrayList<>();
         StoreFactory storeFactory = authorization.getStoreFactory();
         ResourceStore resourceStore = storeFactory.getResourceStore();
+        Metadata metadata = request.getMetadata();
+        long limit = Long.MAX_VALUE;
+
+        if (metadata != null && metadata.getLimit() != null) {
+            limit = metadata.getLimit();
+        }
 
         // obtain all resources where owner is the resource server
-        resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
+        resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().limit(limit).forEach(resource -> permissions.add(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
 
         // obtain all resources where owner is the current user
-        resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
+        resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().limit(limit).forEach(resource -> permissions.add(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
 
         // obtain all resources granted to the user via permission tickets (uma)
         List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId());
-        Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
 
-        for (PermissionTicket ticket : tickets) {
-            userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer, request.getClaims()));
-        }
+        if (!tickets.isEmpty()) {
+            Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
+
+            for (PermissionTicket ticket : tickets) {
+                ResourcePermission permission = userManagedPermissions.get(ticket.getResource().getId());
+
+                if (permission == null) {
+                    userManagedPermissions.put(ticket.getResource().getId(), new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer, request.getClaims()));
+                    limit--;
+                }
+
+                if (--limit <= 0) {
+                    break;
+                }
+            }
 
-        permissions.addAll(userManagedPermissions.values());
+            permissions.addAll(userManagedPermissions.values());
+        }
 
         return permissions;
     }
@@ -131,8 +143,7 @@ public final class Permissions {
         return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
     }
 
-    public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
-        List<ResourcePermission> permissions = new ArrayList<>();
+    public static ResourcePermission createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
         String type = resource.getType();
         ResourceServer resourceServer = resource.getResourceServer();
 
@@ -152,151 +163,6 @@ public final class Permissions {
             });
         }
 
-        permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims()));
-
-        return permissions;
-    }
-
-    public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
-        return permits(evaluation, null, authorizationProvider, resourceServer);
-    }
-
-    public static List<Permission> permits(List<Result> evaluation, Metadata metadata, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
-        Map<String, Permission> permissions = new LinkedHashMap<>();
-
-        for (Result result : evaluation) {
-            Set<Scope> deniedScopes = new HashSet<>();
-            Set<Scope> grantedScopes = new HashSet<>();
-            boolean resourceDenied = false;
-            ResourcePermission permission = result.getPermission();
-            List<Result.PolicyResult> results = result.getResults();
-            List<Result.PolicyResult> userManagedPermissions = new ArrayList<>();
-            int deniedCount = results.size();
-            Resource resource = permission.getResource();
-
-            for (Result.PolicyResult policyResult : results) {
-                Policy policy = policyResult.getPolicy();
-                Set<Scope> policyScopes = policy.getScopes();
-
-                if (Effect.PERMIT.equals(policyResult.getStatus())) {
-                    if (isScopePermission(policy)) {
-                        for (Scope scope : permission.getScopes()) {
-                            if (policyScopes.contains(scope)) {
-                                // try to grant any scope from a scope-based permission
-                                grantedScopes.add(scope);
-                            }
-                        }
-                    } else if (isResourcePermission(policy)) {
-                        // we assume that all requested scopes should be granted given that we are processing a resource-based permission.
-                        // Later they will be filtered based on any denied scope, if any.
-                        // TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
-                        grantedScopes.addAll(permission.getScopes());
-                    } if (resource != null && resource.isOwnerManagedAccess() && "uma".equals(policy.getType())) {
-                        userManagedPermissions.add(policyResult);
-                    }
-                    deniedCount--;
-                } else {
-                    if (isScopePermission(policy)) {
-                        // store all scopes associated with the scope-based permission
-                        deniedScopes.addAll(policyScopes);
-                    } else if (isResourcePermission(policy)) {
-                        resourceDenied = true;
-                        deniedScopes.addAll(resource.getScopes());
-                    }
-                }
-            }
-
-            // remove any scope denied from the list of granted scopes
-            if (!deniedScopes.isEmpty()) {
-                grantedScopes.removeAll(deniedScopes);
-            }
-
-            for (Result.PolicyResult policyResult : userManagedPermissions) {
-                Policy policy = policyResult.getPolicy();
-
-                grantedScopes.addAll(policy.getScopes());
-
-                resourceDenied = false;
-            }
-
-            // if there are no policy results is because the permission didn't match any policy.
-            // In this case, if results is empty is because we are in permissive mode.
-            if (!results.isEmpty()) {
-                // update the current permission with the granted scopes
-                permission.getScopes().clear();
-                permission.getScopes().addAll(grantedScopes);
-            }
-
-            if (deniedCount == 0) {
-                result.setStatus(Effect.PERMIT);
-                grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
-            } else {
-                // if a full deny or resource denied or the requested scopes were denied
-                if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
-                    result.setStatus(Effect.DENY);
-                } else {
-                    result.setStatus(Effect.PERMIT);
-                    grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
-                }
-            }
-        }
-
-        return permissions.values().stream().collect(Collectors.toList());
-    }
-
-    private static boolean isResourcePermission(Policy policy) {
-        return "resource".equals(policy.getType());
-    }
-
-    private static boolean isScopePermission(Policy policy) {
-        return "scope".equals(policy.getType());
-    }
-
-    private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, ResourceServer resourceServer, Metadata metadata) {
-        List<Resource> resources = new ArrayList<>();
-        Resource resource = permission.getResource();
-        Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
-
-        if (resource != null) {
-            resources.add(resource);
-        } else {
-            List<Scope> permissionScopes = permission.getScopes();
-
-            if (!permissionScopes.isEmpty()) {
-                ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
-                resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()));
-            }
-        }
-
-        if (!resources.isEmpty()) {
-            for (Resource allowedResource : resources) {
-                String resourceId = allowedResource.getId();
-                String resourceName = metadata == null || metadata.getIncludeResourceName() ? allowedResource.getName() : null;
-                Permission evalPermission = permissions.get(allowedResource.getId());
-
-                if (evalPermission == null) {
-                    evalPermission = new Permission(resourceId, resourceName, scopes, permission.getClaims());
-                    permissions.put(resourceId, evalPermission);
-                }
-
-                if (scopes != null && !scopes.isEmpty()) {
-                    Set<String> finalScopes = evalPermission.getScopes();
-
-                    if (finalScopes == null) {
-                        finalScopes = new HashSet();
-                        evalPermission.setScopes(finalScopes);
-                    }
-
-                    for (String scopeName : scopes) {
-                        if (!finalScopes.contains(scopeName)) {
-                            finalScopes.add(scopeName);
-                        }
-                    }
-                }
-            }
-        } else {
-            Permission scopePermission = new Permission(null, null, scopes, permission.getClaims());
-            permissions.put(scopePermission.toString(), scopePermission);
-        }
+        return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
     }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 762dc55..adb069d 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -26,7 +26,6 @@ import org.keycloak.OAuthErrorException;
 import org.keycloak.authentication.AuthenticationProcessor;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.authorization.AuthorizationTokenService;
-import org.keycloak.representations.idm.authorization.AuthorizationRequest;
 import org.keycloak.authorization.util.Tokens;
 import org.keycloak.broker.provider.BrokeredIdentityContext;
 import org.keycloak.broker.provider.ExchangeExternalToken;
@@ -1070,11 +1069,13 @@ public class TokenEndpoint {
             }
         }
 
-        AuthorizationRequest authorizationRequest = new AuthorizationRequest(formParams.getFirst("ticket"));
+        AuthorizationTokenService.KeycloakAuthorizationRequest authorizationRequest = new AuthorizationTokenService.KeycloakAuthorizationRequest(session.getProvider(AuthorizationProvider.class), tokenManager, event, this.request, cors);
 
+        authorizationRequest.setTicket(formParams.getFirst("ticket"));
         authorizationRequest.setClaimToken(claimToken);
         authorizationRequest.setClaimTokenFormat(claimTokenFormat);
         authorizationRequest.setPct(formParams.getFirst("pct"));
+
         String rpt = formParams.getFirst("rpt");
 
         if (rpt != null) {
@@ -1128,9 +1129,11 @@ public class TokenEndpoint {
             metadata.setLimit(Integer.parseInt(responsePermissionsLimit));
         }
 
+        metadata.setResponseMode(formParams.getFirst("response_mode"));
+
         authorizationRequest.setMetadata(metadata);
 
-        return new AuthorizationTokenService(session.getProvider(AuthorizationProvider.class), tokenManager, event, request, cors).authorize(authorizationRequest);
+        return AuthorizationTokenService.instance().authorize(authorizationRequest);
     }
 
     // https://tools.ietf.org/html/rfc7636#section-4.1
diff --git a/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java b/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java
index 7359dc9..be1c6d5 100644
--- a/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java
+++ b/services/src/main/java/org/keycloak/scripting/CompiledEvaluatableScriptAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.scripting;
 
 import javax.script.Bindings;
 import javax.script.CompiledScript;
+import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
 import javax.script.ScriptException;
 
@@ -37,4 +38,13 @@ class CompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapter 
     protected Object eval(final Bindings bindings) throws ScriptException {
         return compiledScript.eval(bindings);
     }
+
+    @Override
+    public Object eval(ScriptContext context) throws ScriptExecutionException {
+        try {
+            return compiledScript.eval(context);
+        } catch (ScriptException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java b/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java
index 8464fdf..4e5baee 100644
--- a/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java
+++ b/services/src/main/java/org/keycloak/scripting/UncompiledEvaluatableScriptAdapter.java
@@ -1,6 +1,7 @@
 package org.keycloak.scripting;
 
 import javax.script.Bindings;
+import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
 import javax.script.ScriptException;
 
@@ -36,4 +37,12 @@ class UncompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapte
         return getEngine().eval(getCode(), bindings);
     }
 
+    @Override
+    public Object eval(ScriptContext context) throws ScriptExecutionException {
+        try {
+            return getEngine().eval(getCode(), context);
+        } catch (ScriptException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
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 9b5e63c..b0994b8 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
@@ -20,7 +20,6 @@ import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.authorization.AuthorizationProvider;
 import org.keycloak.authorization.AuthorizationProviderFactory;
-import org.keycloak.authorization.Decision;
 import org.keycloak.authorization.common.DefaultEvaluationContext;
 import org.keycloak.authorization.common.KeycloakIdentity;
 import org.keycloak.authorization.common.UserModelIdentity;
@@ -30,7 +29,6 @@ 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.DecisionResult;
 import org.keycloak.authorization.policy.evaluation.EvaluationContext;
 import org.keycloak.authorization.store.ResourceServerStore;
 import org.keycloak.authorization.util.Permissions;
@@ -45,7 +43,7 @@ import org.keycloak.services.ForbiddenException;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.resources.admin.AdminAuth;
 
-import java.util.List;
+import java.util.Arrays;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -332,15 +330,8 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
         RealmModel oldRealm = session.getContext().getRealm();
         try {
             session.getContext().setRealm(realm);
-            DecisionResult decisionCollector = new DecisionResult();
-            List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope);
-            PermissionEvaluator from = authz.evaluators().from(permissions, context);
-            from.evaluate(decisionCollector);
-            if (!decisionCollector.completed()) {
-                logger.error("Failed to run permission check", decisionCollector.getError());
-                return false;
-            }
-            return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT;
+            ResourcePermission permission = Permissions.permission(resourceServer, resource, scope);
+            return !authz.evaluators().from(Arrays.asList(permission), context).evaluate(resourceServer, null).isEmpty();
         } finally {
             session.getContext().setRealm(oldRealm);
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
index 23a95ec..00480a1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -33,8 +34,6 @@ import org.keycloak.admin.client.resource.RealmResource;
 import org.keycloak.authorization.client.AuthzClient;
 import org.keycloak.authorization.client.Configuration;
 import org.keycloak.authorization.client.resource.ProtectionResource;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.authorization.AuthorizationRequest;
 import org.keycloak.representations.idm.authorization.AuthorizationResponse;
@@ -42,7 +41,6 @@ import org.keycloak.representations.idm.authorization.Permission;
 import org.keycloak.representations.idm.authorization.PermissionRequest;
 import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
-import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.RoleBuilder;
@@ -176,7 +174,7 @@ public abstract class AbstractResourceServerTest extends AbstractAuthzTest {
         }
     }
 
-    protected void assertPermissions(List<Permission> permissions, String expectedResource, String... expectedScopes) {
+    protected void assertPermissions(Collection<Permission> permissions, String expectedResource, String... expectedScopes) {
         Iterator<Permission> iterator = permissions.iterator();
 
         while (iterator.hasNext()) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationTest.java
index 16eb0cb..5418845 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
@@ -174,7 +175,7 @@ public class AuthorizationTest extends AbstractAuthzTest {
         AuthorizationResponse response = getAuthzClient().authorization(userName, password).authorize(request);
         AccessToken token = toAccessToken(response.getToken());
         Authorization authorization = token.getAuthorization();
-        return authorization.getPermissions();
+        return new ArrayList<>(authorization.getPermissions());
     }
 
     private void createResourcePermission(ResourceRepresentation resource, String... policies) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
index 63726b5..7760791 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -127,7 +128,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        List<Permission> permissions = new ArrayList<>(authorization.getPermissions());
 
         assertFalse(permissions.isEmpty());
         assertEquals("Default Resource", permissions.get(0).getResourceName());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
index 7393a94..a7d4b7e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java
@@ -16,14 +16,17 @@
  */
 package org.keycloak.testsuite.authz;
 
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -44,9 +47,11 @@ import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.authorization.AuthorizationResponse;
 import org.keycloak.representations.idm.authorization.Permission;
+import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
 import org.keycloak.representations.idm.authorization.PolicyRepresentation;
 import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
 import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
 import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
 import org.keycloak.testsuite.util.ClientBuilder;
@@ -76,13 +81,14 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
 
     @Before
     public void configureAuthorization() throws Exception {
-        createResourcesAndScopes();
-
         RealmResource realm = getRealm();
         ClientResource client = getClient(realm);
 
-        createPolicies(realm, client);
-        createPermissions(client);
+        if (client.authorization().resources().findByName("Resource A").isEmpty()) {
+            createResourcesAndScopes();
+            createPolicies(realm, client);
+            createPermissions(client);
+        }
     }
 
     /**
@@ -91,24 +97,105 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
      * <p>Scope Read should not be granted for Marta.
      */
     @Test
-    public void testMartaCanAccessResourceAWithExecuteAndWrite() {
-        List<Permission> permissions = getEntitlements("marta", "password");
+    public void testMartaCanAccessResourceAWithExecuteAndWrite() throws Exception {
+        ClientResource client = getClient(getRealm());
+        AuthorizationResource authorization = client.authorization();
+        ResourceServerRepresentation settings = authorization.getSettings();
+
+        settings.setPolicyEnforcementMode(PolicyEnforcementMode.ENFORCING);
+
+        authorization.update(settings);
+
+        Collection<Permission> permissions = getEntitlements("marta", "password");
+
+        assertEquals(1, permissions.size());
 
         for (Permission permission : new ArrayList<>(permissions)) {
             String resourceSetName = permission.getResourceName();
 
             switch (resourceSetName) {
                 case "Resource A":
-                    assertEquals(2, permission.getScopes().size());
-                    assertTrue(permission.getScopes().contains("execute"));
-                    assertTrue(permission.getScopes().contains("write"));
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write"));
                     permissions.remove(permission);
                     break;
                 case "Resource C":
-                    assertEquals(3, permission.getScopes().size());
-                    assertTrue(permission.getScopes().contains("execute"));
-                    assertTrue(permission.getScopes().contains("write"));
-                    assertTrue(permission.getScopes().contains("read"));
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
+                    permissions.remove(permission);
+                    break;
+                default:
+                    fail("Unexpected permission for resource [" + resourceSetName + "]");
+            }
+        }
+
+        assertTrue(permissions.isEmpty());
+    }
+
+    @Test
+    public void testWithPermissiveMode() throws Exception {
+        ClientResource client = getClient(getRealm());
+        AuthorizationResource authorization = client.authorization();
+        ResourceServerRepresentation settings = authorization.getSettings();
+
+        settings.setPolicyEnforcementMode(PolicyEnforcementMode.PERMISSIVE);
+
+        authorization.update(settings);
+
+        Collection<Permission> permissions = getEntitlements("marta", "password");
+
+        assertEquals(3, permissions.size());
+
+        for (Permission permission : new ArrayList<>(permissions)) {
+            String resourceSetName = permission.getResourceName();
+
+            switch (resourceSetName) {
+                case "Resource A":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write"));
+                    permissions.remove(permission);
+                    break;
+                case "Resource C":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
+                    permissions.remove(permission);
+                    break;
+                case "Resource B":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
+                    permissions.remove(permission);
+                    break;
+                default:
+                    fail("Unexpected permission for resource [" + resourceSetName + "]");
+            }
+        }
+
+        assertTrue(permissions.isEmpty());
+    }
+
+    @Test
+    public void testWithDisabledMode() throws Exception {
+        ClientResource client = getClient(getRealm());
+        AuthorizationResource authorization = client.authorization();
+        ResourceServerRepresentation settings = authorization.getSettings();
+
+        settings.setPolicyEnforcementMode(PolicyEnforcementMode.DISABLED);
+
+        authorization.update(settings);
+
+        Collection<Permission> permissions = getEntitlements("marta", "password");
+
+        assertEquals(3, permissions.size());
+
+        for (Permission permission : new ArrayList<>(permissions)) {
+            String resourceSetName = permission.getResourceName();
+
+            switch (resourceSetName) {
+                case "Resource A":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
+                    permissions.remove(permission);
+                    break;
+                case "Resource C":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
+                    permissions.remove(permission);
+                    break;
+                case "Resource B":
+                    assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
                     permissions.remove(permission);
                     break;
                 default:
@@ -119,7 +206,7 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
         assertTrue(permissions.isEmpty());
     }
 
-    private List<Permission> getEntitlements(String username, String password) {
+    private Collection<Permission> getEntitlements(String username, String password) {
         AuthzClient authzClient = getAuthzClient();
         AuthorizationResponse response = authzClient.authorization(username, password).authorize();
         AccessToken accessToken;
@@ -147,7 +234,7 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
     }
 
     private void createPermissions(ClientResource client) throws IOException {
-        createResourcePermission("Resource C Only For Marta Permission", "Resource C", Arrays.asList("Only Marta Policy"), client);
+        createResourcePermission("Resource A Only For Marta Permission", "Resource A", Arrays.asList("Only Marta Policy"), client);
         createScopePermission("Resource A Scope Read Only For Marta Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Marta Policy"), client);
         createScopePermission("Resource A Scope Read Only For Kolo Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Kolo Policy"), client);
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
index 562555a..9287bff 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.fail;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -273,7 +274,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         AuthorizationResponse response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
         AccessToken rpt = toAccessToken(response.getToken());
 
-        List<Permission> permissions = rpt.getAuthorization().getPermissions();
+        List<Permission> permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
 
         assertEquals(10, permissions.size());
 
@@ -293,7 +294,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
         rpt = toAccessToken(response.getToken());
 
-        permissions = rpt.getAuthorization().getPermissions();
+        permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
 
         assertEquals(10, permissions.size());
 
@@ -317,7 +318,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
         rpt = toAccessToken(response.getToken());
 
-        permissions = rpt.getAuthorization().getPermissions();
+        permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
 
         assertEquals(10, permissions.size());
         assertEquals("Resource 16", permissions.get(0).getResourceName());
@@ -340,7 +341,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
         rpt = toAccessToken(response.getToken());
 
-        permissions = rpt.getAuthorization().getPermissions();
+        permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
 
         assertEquals(5, permissions.size());
         assertEquals("Resource 16", permissions.get(0).getResourceName());
@@ -442,7 +443,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
 
         authorization.resources().resource(resource.getId()).update(resource);
 
-        // the addition of a new scope invalidates the permission previously granted to the resource
+        // the addition of a new scope still grants access to resource and any scope
         assertFalse(hasPermission("kolo", "password", resource.getId()));
 
         accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
@@ -487,6 +488,39 @@ public class EntitlementAPITest extends AbstractAuthzTest {
     }
 
     @Test
+    public void testObtainAllEntitlementsWithLimit() throws Exception {
+        org.keycloak.authorization.client.resource.AuthorizationResource authorizationResource = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization("marta", "password");
+        AuthorizationResponse response = authorizationResource.authorize();
+        AccessToken accessToken = toAccessToken(response.getToken());
+        Authorization authorization = accessToken.getAuthorization();
+
+        assertTrue(authorization.getPermissions().size() >= 20);
+
+        AuthorizationRequest request = new AuthorizationRequest();
+        Metadata metadata = new Metadata();
+
+        metadata.setLimit(10);
+
+        request.setMetadata(metadata);
+
+        response = authorizationResource.authorize(request);
+        accessToken = toAccessToken(response.getToken());
+        authorization = accessToken.getAuthorization();
+
+        assertEquals(10, authorization.getPermissions().size());
+
+        metadata.setLimit(1);
+
+        request.setMetadata(metadata);
+
+        response = authorizationResource.authorize(request);
+        accessToken = toAccessToken(response.getToken());
+        authorization = accessToken.getAuthorization();
+
+        assertEquals(1, authorization.getPermissions().size());
+    }
+
+    @Test
     public void testObtainAllEntitlementsInvalidResource() throws Exception {
         ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
         AuthorizationResource authorization = client.authorization();
@@ -625,7 +659,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
 
         AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request);
         assertNotNull(response.getToken());
-        List<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
+        Collection<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
         assertEquals(2, permissions.size());
 
         for (Permission grantedPermission : permissions) {
@@ -697,7 +731,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
         AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest());
         AccessToken rpt = toAccessToken(response.getToken());
         Authorization authz = rpt.getAuthorization();
-        List<Permission> permissions = authz.getPermissions();
+        Collection<Permission> permissions = authz.getPermissions();
 
         assertNotNull(permissions);
         assertFalse(permissions.isEmpty());
@@ -718,7 +752,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
     private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
         AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertFalse(permissions.isEmpty());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionClaimTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionClaimTest.java
index 7a9600f..8e3e50b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionClaimTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionClaimTest.java
@@ -21,7 +21,9 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -128,7 +130,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
         assertNotNull(response.getToken());
         AccessToken rpt = toAccessToken(response.getToken());
         Authorization authorizationClaim = rpt.getAuthorization();
-        List<Permission> permissions = authorizationClaim.getPermissions();
+        List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
 
         assertEquals(1, permissions.size());
 
@@ -164,7 +166,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
         assertNotNull(response.getToken());
         AccessToken rpt = toAccessToken(response.getToken());
         Authorization authorizationClaim = rpt.getAuthorization();
-        List<Permission> permissions = authorizationClaim.getPermissions();
+        List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
 
         assertEquals(1, permissions.size());
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
index 031ec4f..0b3795d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java
@@ -664,6 +664,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
                 }
                 return baseAttributes;
             }
-        }, policy, evaluation -> {}, authorization);
+        }, policy, evaluation -> {}, authorization, null);
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
index c89d98e..0d6203b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.fail;
 import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
 
 import java.net.URI;
+import java.util.Collection;
 import java.util.List;
 
 import javax.ws.rs.client.Client;
@@ -98,7 +99,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"}, new String[] {"ScopeC"});
         AccessToken accessToken = toAccessToken(response.getToken());
         AccessToken.Authorization authorization = accessToken.getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB", "ScopeC");
@@ -110,7 +111,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@@ -132,7 +133,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", null, new String[] {"ScopeA", "ScopeB"});
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertFalse(response.isUpgraded());
         assertNotNull(permissions);
@@ -155,7 +156,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertFalse(response.isUpgraded());
         assertNotNull(permissions);
@@ -194,7 +195,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeA", "ScopeB"});
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertFalse(response.isUpgraded());
         assertNotNull(permissions);
@@ -261,7 +262,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
         AuthorizationResponse response = authorize("marta", "password", resourceA.getName(), new String[] {"READ"});
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertFalse(response.isUpgraded());
         assertNotNull(permissions);
@@ -303,7 +304,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
                 new PermissionRequest(resourceB.getName(), "ScopeC"));
         String rpt = response.getToken();
         AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB");
@@ -324,7 +325,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@@ -346,7 +347,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@@ -366,7 +367,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@@ -447,7 +448,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
index 7274ca5..8b0ac60 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UserManagedAccessTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import javax.ws.rs.core.Response;
@@ -84,7 +85,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
@@ -142,7 +143,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
@@ -204,7 +205,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@@ -280,7 +281,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A");
@@ -382,7 +383,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
 
         assertNotNull(authorization);
 
-        List<Permission> permissions = authorization.getPermissions();
+        Collection<Permission> permissions = authorization.getPermissions();
 
         assertNotNull(permissions);
         assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
diff --git a/testsuite/performance/tests/src/test/resources/dataset/authz/default.properties b/testsuite/performance/tests/src/test/resources/dataset/authz/default.properties
new file mode 100644
index 0000000..8b1a059
--- /dev/null
+++ b/testsuite/performance/tests/src/test/resources/dataset/authz/default.properties
@@ -0,0 +1,184 @@
+#
+# * 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.
+#
+
+# This dataset provides a default realm configuration using role-based policies which can be extended to create more test scenarios:
+#
+#   - 1 realm
+#   - 1 client
+#   - 1 user
+#   - 1k resources
+#     - Each resource with 10 scopes
+#     - Each resource associated with a single resource permission
+#   - 500 Scopes
+#   - 100 role policies
+#   - 1000 resource permissions
+#     - Each resource permissions associated with 10 role policies
+
+# REALM
+realms=1
+realm.realm=authz-perf-tests
+realm.displayName=AuthZ Performance Tests
+realm.enabled=true
+realm.registrationAllowed=true
+realm.accessTokenLifeSpan=60
+realm.passwordPolicy=hashIterations(1000)
+
+# REALM ROLE
+realmRolesPerRealm=100
+realmRole.name=role_${index?string("00")}
+realmRole.description=Role ${index} of ${realm.displayName}
+
+# CLIENT
+clientsPerRealm=1
+client.clientId=client_${index?string("00")}
+client.name=Client ${index} of ${realm.displayName}
+client.description=Description of ${name}
+client.rootUrl=
+client.adminUrl=
+client.baseUrl=http://clients.${realm.realm}.test/client_${index}
+client.enabled=true
+client.secret=secret
+# TODO support for multiple redirect uris
+#client.redirectUris=${baseUrl}/* http://load-balancing-domain.test/${clientId}/*
+client.redirectUris=${baseUrl}/*
+client.webOrigins=
+client.protocol=openid-connect
+client.publicClient=false
+client.bearerOnly=false
+client.authorizationServicesEnabled=true
+client.serviceAccountsEnabled=true
+
+# CLIENT ROLE
+clientRolesPerClient=1
+clientRole.name=clientrole_${index?string("00")}
+clientRole.description=Role ${index} of ${client.name}
+
+# USER
+usersPerRealm=1
+user.username=user_${index?string("00")}
+user.enabled=true
+user.email=${username}@email.test
+user.emailVerified=true
+user.firstName=User_${index}
+user.lastName=O'Realm_${realm.index}
+
+credential.type=password
+credential.value=password
+credential.temporary=false
+
+# USER ATTRIBUTE
+attributesPerUser=50
+userAttribute.name=attribute_${index?string("00")}
+userAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>,</#sep></#list>
+
+# USER ROLE MAPPINGS
+realmRolesPerUser=50
+clientRolesPerUser=0
+
+
+# GROUP
+groupsPerRealm=100
+group.name=group_${index?string("00")}
+
+# GROUP ATTRIBUTE
+attributesPerGroup=50
+groupAttribute.name=attribute_${index?string("00")}
+groupAttribute.value=<#list 0..2 as i>value_${i}_of_${name}<#sep>,</#sep></#list>
+
+
+### AUTHZ
+# RESOURCE SERVER
+resourceServer.allowRemoteResourceManagement=true
+resourceServer.policyEnforcementMode=ENFORCING
+
+# SCOPE
+scopesPerResourceServer=10
+scope.name=scope_${index}
+scope.displayName=Scope ${index} of ${resourceServer.clientId}
+
+# RESOURCE
+resourcesPerResourceServer=1000
+resource.name=resource_${index}
+resource.displayName=Resource ${index}
+resource.uri=${resourceServer.client.baseUrl}/resource_${index}
+resource.type=<#if index == 0>urn:${resourceServer.clientId}:resources:default</#if>
+resource.ownerManagedAccess=false
+
+# RESOURCE MAPPINGS
+scopesPerResource=10
+
+
+# ROLE POLICY
+rolePoliciesPerResourceServer=100
+rolePolicy.name=role_policy_${index}
+rolePolicy.description=Role Policy ${index} of ${resourceServer.name}
+rolePolicy.logic=POSITIVE
+
+# ROLE POLICY ROLE DEFINITION
+rolePolicyRoleDefinition.required=false
+realmRolesPerRolePolicy=10
+clientRolesPerRolePolicy=0
+
+
+# JS POLICY
+jsPoliciesPerResourceServer=0
+jsPolicy.name=js_policy_${index}
+jsPolicy.description=JavaScript Policy ${index} of ${resourceServer.name}
+jsPolicy.code=// TODO add some JavaScript code\n// for JavaScript Policy ${index}\n// more\n// lines ...
+jsPolicy.logic=POSITIVE
+
+# USER POLICY
+userPoliciesPerResourceServer=0
+userPolicy.name=user_policy_${index}
+userPolicy.description=User Policy ${index} of ${resourceServer.name}
+userPolicy.logic=POSITIVE
+
+# USER POLICY MAPPINGS
+usersPerUserPolicy=0
+
+
+# CLIENT POLICY
+clientPoliciesPerResourceServer=0
+clientPolicy.name=client_policy_${index}
+clientPolicy.description=Client Policy ${index} of ${resourceServer.name}
+clientPolicy.logic=POSITIVE
+
+# CLIENT POLICY MAPPINGS
+clientsPerClientPolicy=0
+
+
+# RESOURCE PERMISSION
+resourcePermissionsPerResourceServer=1000
+resourcePermission.name=resource_permission_${index}
+resourcePermission.description=Resource Permisison ${index} of ${resourceServer.name}
+resourcePermission.resourceType=<#if index == 0>urn:${resourceServer.clientId}:resources:default</#if>
+resourcePermission.decisionStrategy=UNANIMOUS
+
+# RESOURCE PERMISSION MAPPINGS
+resourcesPerResourcePermission=1
+policiesPerResourcePermission=10
+
+
+# SCOPE PERMISSION
+scopePermissionsPerResourceServer=0
+scopePermission.name=scope_permission_${index}
+scopePermission.description=Scope Permisison ${index} of ${resourceServer.name}
+scopePermission.decisionStrategy=UNANIMOUS
+
+# SCOPE PERMISSION MAPPINGS
+scopesPerScopePermission=10
+policiesPerScopePermission=5