keycloak-uncached
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java 3(+2 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java 6(+3 -3)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/aggregated/AggregatePolicyProvider.java 48(+31 -17)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/JSPolicyProvider.java 15(+9 -6)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/js/ScriptCache.java 30(+6 -24)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/AbstractPermissionProvider.java 36(+22 -14)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ResourcePolicyProvider.java 27(+27 -0)
authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/permission/ScopePolicyProvider.java 36(+36 -0)
core/src/main/java/org/keycloak/representations/idm/authorization/AuthorizationRequest.java 9(+9 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheManager.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java 71(+58 -13)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/Evaluators.java 20(+8 -12)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/IterablePermissionEvaluator.java 42(+21 -21)
server-spi-private/src/main/java/org/keycloak/authorization/permission/evaluator/PermissionEvaluator.java 8(+5 -3)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/AbstractDecisionCollector.java 99(+99 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionPermissionCollector.java 186(+186 -0)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResult.java 51(+0 -51)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DecisionResultCollector.java 89(+2 -87)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultEvaluation.java 33(+26 -7)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/DefaultPolicyEvaluator.java 160(+34 -126)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PermissionTicketAwareDecisionResultCollector.java 14(+3 -11)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/PolicyEvaluator.java 6(+5 -1)
server-spi-private/src/main/java/org/keycloak/authorization/policy/evaluation/Result.java 75(+31 -44)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponseBuilder.java 11(+7 -4)
services/src/main/java/org/keycloak/authorization/authorization/AuthorizationTokenService.java 259(+160 -99)
services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java 15(+3 -12)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AbstractResourceServerTest.java 6(+2 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/ConflictingScopePermissionTest.java 117(+102 -15)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java 50(+42 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PermissionClaimTest.java 6(+4 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/PolicyEvaluationTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/UmaGrantTypeTest.java 23(+12 -11)
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