[KEYCLOAK-4902] - Refactoring and improvements to processing of authz requests

This commit is contained in:
Pedro Igor 2018-07-27 23:22:26 -03:00
parent 65f51b7b83
commit 80e5227bcd
50 changed files with 1343 additions and 825 deletions

View file

@ -17,6 +17,7 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -151,7 +152,7 @@ public abstract class AbstractPolicyEnforcer {
} }
boolean hasPermission = false; boolean hasPermission = false;
List<Permission> grantedPermissions = authorization.getPermissions(); Collection<Permission> grantedPermissions = authorization.getPermissions();
for (Permission permission : grantedPermissions) { for (Permission permission : grantedPermissions) {
if (permission.getResourceId() != null) { if (permission.getResourceId() != null) {

View file

@ -18,7 +18,7 @@
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -78,8 +78,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AccessToken.Authorization newAuthorization = accessToken.getAuthorization(); AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
if (newAuthorization != null) { if (newAuthorization != null) {
List<Permission> grantedPermissions = authorization.getPermissions(); Collection<Permission> grantedPermissions = authorization.getPermissions();
List<Permission> newPermissions = newAuthorization.getPermissions(); Collection<Permission> newPermissions = newAuthorization.getPermissions();
for (Permission newPermission : newPermissions) { for (Permission newPermission : newPermissions) {
if (!grantedPermissions.contains(newPermission)) { if (!grantedPermissions.contains(newPermission)) {

View file

@ -17,10 +17,15 @@
*/ */
package org.keycloak.authorization.policy.provider.aggregated; 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.AuthorizationProvider;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.model.Policy; 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.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation; import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.evaluation.Evaluation;
@ -34,31 +39,40 @@ public class AggregatePolicyProvider implements PolicyProvider {
@Override @Override
public void evaluate(Evaluation evaluation) { public void evaluate(Evaluation evaluation) {
//TODO: need to detect deep recursions
DecisionResultCollector decision = new DecisionResultCollector() { DecisionResultCollector decision = new DecisionResultCollector() {
@Override @Override
protected void onComplete(List<Result> results) { protected void onComplete(Result result) {
if (results.isEmpty()) { if (isGranted(result.getResults().iterator().next())) {
evaluation.deny(); evaluation.grant();
} else { } else {
Result result = results.iterator().next(); evaluation.deny();
if (Effect.PERMIT.equals(result.getEffect())) {
evaluation.grant();
}
} }
} }
}; };
Policy policy = evaluation.getPolicy();
AuthorizationProvider authorization = evaluation.getAuthorizationProvider(); 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();
policy.getAssociatedPolicies().forEach(associatedPolicy -> { for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType()); Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
policyProvider.evaluate(new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization)); Decision.Effect effect = decisions.get(permission);
}); DefaultEvaluation eval = new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization, decisionCache);
decision.onComplete(); if (effect == null) {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
policyProvider.evaluate(eval);
eval.denyIfNoEffect();
decisions.put(permission, eval.getEffect());
} else {
eval.setEffect(effect);
}
}
decision.onComplete(permission);
} }
@Override @Override

View file

@ -19,6 +19,9 @@ package org.keycloak.authorization.policy.provider.js;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import javax.script.ScriptContext;
import javax.script.SimpleScriptContext;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.evaluation.Evaluation;
@ -40,14 +43,14 @@ class JSPolicyProvider implements PolicyProvider {
public void evaluate(Evaluation evaluation) { public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy(); Policy policy = evaluation.getPolicy();
AuthorizationProvider authorization = evaluation.getAuthorizationProvider(); AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
final EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy); EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
try { try {
//how to deal with long running scripts -> timeout? SimpleScriptContext context = new SimpleScriptContext();
adapter.eval(bindings -> {
bindings.put("script", adapter.getScriptModel()); context.setAttribute("$evaluation", evaluation, ScriptContext.ENGINE_SCOPE);
bindings.put("$evaluation", evaluation);
}); adapter.eval(context);
} }
catch (Exception e) { catch (Exception e) {
throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e); throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e);

View file

@ -67,6 +67,12 @@ public class ScriptCache {
public EvaluatableScriptAdapter computeIfAbsent(String id, Function<String, EvaluatableScriptAdapter> function) { public EvaluatableScriptAdapter computeIfAbsent(String id, Function<String, EvaluatableScriptAdapter> function) {
try { try {
EvaluatableScriptAdapter adapter = removeIfExpired(cache.get(id));
if (adapter != null) {
return adapter;
}
if (parkForWriteAndCheckInterrupt()) { if (parkForWriteAndCheckInterrupt()) {
return null; 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) { public void remove(String key) {
try { try {
if (parkForWriteAndCheckInterrupt()) { if (parkForWriteAndCheckInterrupt()) {
@ -132,16 +124,6 @@ public class ScriptCache {
return false; return false;
} }
private boolean parkForReadAndCheckInterrupt() {
while (writing.get()) {
LockSupport.parkNanos(1L);
if (Thread.interrupted()) {
return true;
}
}
return false;
}
private static final class CacheEntry { private static final class CacheEntry {
final String key; final String key;

View file

@ -17,34 +17,42 @@
package org.keycloak.authorization.policy.provider.permission; package org.keycloak.authorization.policy.provider.permission;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.model.Policy; 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.DefaultEvaluation;
import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider; 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> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class AbstractPermissionProvider implements PolicyProvider { public abstract class AbstractPermissionProvider implements PolicyProvider {
public AbstractPermissionProvider() {
}
@Override @Override
public void evaluate(Evaluation evaluation) { 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(); 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 -> { policy.getAssociatedPolicies().forEach(associatedPolicy -> {
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType()); Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
DefaultEvaluation.class.cast(evaluation).setPolicy(associatedPolicy); Decision.Effect effect = decisions.get(permission);
policyProvider.evaluate(evaluation);
evaluation.denyIfNoEffect(); 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);
}
}); });
} }

View file

@ -16,9 +16,36 @@
*/ */
package org.keycloak.authorization.policy.provider.permission; 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> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class ResourcePolicyProvider extends AbstractPermissionProvider { 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());
}
} }

View file

@ -16,9 +16,45 @@
*/ */
package org.keycloak.authorization.policy.provider.permission; 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> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
public class ScopePolicyProvider extends AbstractPermissionProvider { 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());
}
}
}
} }

View file

@ -22,9 +22,9 @@ import org.keycloak.representations.AccessToken.Authorization;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.Permission;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -113,7 +113,7 @@ public class AuthorizationContext {
return Collections.emptyList(); return Collections.emptyList();
} }
return Collections.unmodifiableList(authorization.getPermissions()); return Collections.unmodifiableList(new ArrayList<>(authorization.getPermissions()));
} }
public boolean isGranted() { public boolean isGranted() {

View file

@ -22,9 +22,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.Permission;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -86,13 +86,13 @@ public class AccessToken extends IDToken {
public static class Authorization implements Serializable { public static class Authorization implements Serializable {
@JsonProperty("permissions") @JsonProperty("permissions")
private List<Permission> permissions; private Collection<Permission> permissions;
public List<Permission> getPermissions() { public Collection<Permission> getPermissions() {
return permissions; return permissions;
} }
public void setPermissions(List<Permission> permissions) { public void setPermissions(Collection<Permission> permissions) {
this.permissions = permissions; this.permissions = permissions;
} }
} }

View file

@ -186,6 +186,7 @@ public class AuthorizationRequest {
private Boolean includeResourceName; private Boolean includeResourceName;
private Integer limit; private Integer limit;
private String responseMode;
public Boolean getIncludeResourceName() { public Boolean getIncludeResourceName() {
if (includeResourceName == null) { if (includeResourceName == null) {
@ -205,5 +206,13 @@ public class AuthorizationRequest {
public void setLimit(Integer limit) { public void setLimit(Integer limit) {
this.limit = limit; this.limit = limit;
} }
public void setResponseMode(String responseMode) {
this.responseMode = responseMode;
}
public String getResponseMode() {
return responseMode;
}
} }
} }

View file

@ -61,6 +61,9 @@ public class Permission {
} }
public String getResourceId() { public String getResourceId() {
if (resourceId == null || "".equals(resourceId.trim())) {
return null;
}
return this.resourceId; return this.resourceId;
} }

View file

@ -35,7 +35,7 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class StoreFactoryCacheManager extends CacheManager { 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) { public StoreFactoryCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
super(cache, revisions); super(cache, revisions);

View file

@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -756,8 +757,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
} }
return Arrays.asList(policy); return Arrays.asList(policy);
}, }, (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
(revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
if (result.isEmpty()) { if (result.isEmpty()) {
return null; return null;
@ -780,40 +780,62 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
public List<Policy> findByResource(String resourceId, String resourceServerId) { public List<Policy> findByResource(String resourceId, String resourceServerId) {
String cacheKey = getPolicyByResource(resourceId, resourceServerId); String cacheKey = getPolicyByResource(resourceId, resourceServerId);
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(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 @Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) { public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
String cacheKey = getPolicyByResourceType(resourceType, resourceServerId); String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(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 @Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) { public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
if (scopeIds == null) return null; if (scopeIds == null) return null;
List<Policy> result = new ArrayList<>(); Set<Policy> result = new HashSet<>();
for (String id : scopeIds) { for (String id : scopeIds) {
String cacheKey = getPolicyByScope(id, resourceServerId); 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 @Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) { public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
if (scopeIds == null) return null; if (scopeIds == null) return null;
List<Policy> result = new ArrayList<>(); Set<Policy> result = new HashSet<>();
for (String id : scopeIds) { for (String id : scopeIds) {
String cacheKey = getPolicyByResourceScope(id, resourceId, resourceServerId); 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 @Override
@ -826,7 +848,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId); return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
} }
private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) { 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); Q query = cache.get(cacheKey, queryType);
if (query != null) { if (query != null) {
logger.tracev("cache hit for key: {0}", cacheKey); logger.tracev("cache hit for key: {0}", cacheKey);
@ -838,11 +860,34 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
if (invalidations.contains(cacheKey)) return model; if (invalidations.contains(cacheKey)) return model;
query = querySupplier.apply(loaded, model); query = querySupplier.apply(loaded, model);
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
if (consumer != null) {
for (R policy: model) {
consumer.accept(policy);
}
}
return model; return model;
} else if (query.isInvalid(invalidations)) { } 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 { } 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());
} }
} }
} }

View file

@ -23,6 +23,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.FlushModeType; import javax.persistence.FlushModeType;
@ -188,40 +189,52 @@ public class JPAPolicyStore implements PolicyStore {
@Override @Override
public List<Policy> findByResource(final String resourceId, String resourceServerId) { 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); TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResource", String.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("resourceId", resourceId); query.setParameter("resourceId", resourceId);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
List<String> result = query.getResultList(); PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
List<Policy> list = new LinkedList<>();
for (String id : result) { query.getResultList().stream()
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId); .map(id -> policyStore.findById(id, resourceServerId))
if (Objects.nonNull(policy)) { .filter(Objects::nonNull)
list.add(policy); .forEach(consumer::accept);
}
}
return list;
} }
@Override @Override
public List<Policy> findByResourceType(final String resourceType, String resourceServerId) { 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); TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResourceType", String.class);
query.setFlushMode(FlushModeType.COMMIT); query.setFlushMode(FlushModeType.COMMIT);
query.setParameter("type", resourceType); query.setParameter("type", resourceType);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
List<String> result = query.getResultList(); PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
List<Policy> list = new LinkedList<>();
for (String id : result) { query.getResultList().stream()
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId); .map(id -> policyStore.findById(id, resourceServerId))
if (Objects.nonNull(policy)) { .filter(Objects::nonNull)
list.add(policy); .forEach(consumer::accept);
}
}
return list;
} }
@Override @Override
@ -250,10 +263,15 @@ public class JPAPolicyStore implements PolicyStore {
@Override @Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) { public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
if (scopeIds==null || scopeIds.isEmpty()) { List<Policy> result = new LinkedList<>();
return Collections.emptyList();
}
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 // Use separate subquery to handle DB2 and MSSSQL
TypedQuery<String> query; TypedQuery<String> query;
@ -268,15 +286,12 @@ public class JPAPolicyStore implements PolicyStore {
query.setParameter("scopeIds", scopeIds); query.setParameter("scopeIds", scopeIds);
query.setParameter("serverId", resourceServerId); query.setParameter("serverId", resourceServerId);
List<String> result = query.getResultList(); PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
List<Policy> list = new LinkedList<>();
for (String id : result) { query.getResultList().stream()
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId); .map(id -> policyStore.findById(id, resourceServerId))
if (Objects.nonNull(policy)) { .filter(Objects::nonNull)
list.add(policy); .forEach(consumer::accept);
}
}
return list;
} }
@Override @Override

View file

@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.keycloak.authorization.model.PermissionTicket; 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.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.evaluator.Evaluators; 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.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.PermissionTicketStore; import org.keycloak.authorization.store.PermissionTicketStore;
@ -74,18 +75,18 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati
*/ */
public final class AuthorizationProvider implements Provider { public final class AuthorizationProvider implements Provider {
private final DefaultPolicyEvaluator policyEvaluator; private final PolicyEvaluator policyEvaluator;
private StoreFactory storeFactory; private StoreFactory storeFactory;
private StoreFactory storeFactoryDelegate; private StoreFactory storeFactoryDelegate;
private final Map<String, PolicyProviderFactory> policyProviderFactories; private final Map<String, PolicyProviderFactory> policyProviderFactories;
private final KeycloakSession keycloakSession; private final KeycloakSession keycloakSession;
private final RealmModel realm; 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.keycloakSession = session;
this.realm = realm; this.realm = realm;
this.policyProviderFactories = policyProviderFactories; 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 * @return a {@link Evaluators} instance
*/ */
public Evaluators evaluators() { public Evaluators evaluators() {
return new Evaluators(policyEvaluator); return new Evaluators(this);
} }
/** /**
@ -169,6 +170,10 @@ public final class AuthorizationProvider implements Provider {
return realm; return realm;
} }
public PolicyEvaluator getPolicyEvaluator() {
return policyEvaluator;
}
@Override @Override
public void close() { public void close() {
@ -380,6 +385,11 @@ public final class AuthorizationProvider implements Provider {
return policyStore.findByResource(resourceId, resourceServerId); return policyStore.findByResource(resourceId, resourceServerId);
} }
@Override
public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
policyStore.findByResource(resourceId, resourceServerId, consumer);
}
@Override @Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) { public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
return policyStore.findByResourceType(resourceType, resourceServerId); return policyStore.findByResourceType(resourceType, resourceServerId);
@ -395,6 +405,11 @@ public final class AuthorizationProvider implements Provider {
return policyStore.findByScopeIds(scopeIds, resourceId, resourceServerId); return policyStore.findByScopeIds(scopeIds, resourceId, resourceServerId);
} }
@Override
public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
policyStore.findByScopeIds(scopeIds, resourceId, resourceServerId, consumer);
}
@Override @Override
public List<Policy> findByType(String type, String resourceServerId) { public List<Policy> findByType(String type, String resourceServerId) {
return policyStore.findByType(type, resourceServerId); return policyStore.findByType(type, resourceServerId);
@ -404,6 +419,11 @@ public final class AuthorizationProvider implements Provider {
public List<Policy> findDependentPolicies(String id, String resourceServerId) { public List<Policy> findDependentPolicies(String id, String resourceServerId) {
return policyStore.findDependentPolicies(id, resourceServerId); return policyStore.findDependentPolicies(id, resourceServerId);
} }
@Override
public void findByResourceType(String type, String id, Consumer<Policy> policyConsumer) {
policyStore.findByResourceType(type, id, policyConsumer);
}
}; };
} }

View file

@ -18,6 +18,7 @@
package org.keycloak.authorization; package org.keycloak.authorization;
import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.evaluation.Evaluation;
/** /**
@ -38,4 +39,7 @@ public interface Decision<D extends Evaluation> {
default void onComplete() { default void onComplete() {
} }
default void onComplete(ResourcePermission permission) {
}
} }

View file

@ -18,12 +18,12 @@
package org.keycloak.authorization.permission.evaluator; 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.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import java.util.Collection;
/** /**
* A factory for the different {@link PermissionEvaluator} implementations. * A factory for the different {@link PermissionEvaluator} implementations.
* *
@ -31,17 +31,13 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext;
*/ */
public final class Evaluators { public final class Evaluators {
private final DefaultPolicyEvaluator policyEvaluator; private final AuthorizationProvider authorizationProvider;
public Evaluators(DefaultPolicyEvaluator policyEvaluator) { public Evaluators(AuthorizationProvider authorizationProvider) {
this.policyEvaluator = policyEvaluator; this.authorizationProvider = authorizationProvider;
} }
public PermissionEvaluator from(List<ResourcePermission> permissions, EvaluationContext evaluationContext) { public PermissionEvaluator from(Collection<ResourcePermission> permissions, EvaluationContext evaluationContext) {
return schedule(permissions, evaluationContext); return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, authorizationProvider);
}
public PermissionEvaluator schedule(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator);
} }
} }

View file

@ -17,16 +17,21 @@
*/ */
package org.keycloak.authorization.permission.evaluator; package org.keycloak.authorization.permission.evaluator;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision; 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.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.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator; 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> * @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 Iterator<ResourcePermission> permissions;
private final EvaluationContext executionContext; private final EvaluationContext executionContext;
private final PolicyEvaluator policyEvaluator; 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.permissions = permissions;
this.executionContext = executionContext; this.executionContext = executionContext;
this.policyEvaluator = policyEvaluator; this.authorizationProvider = authorizationProvider;
this.policyEvaluator = authorizationProvider.getPolicyEvaluator();
} }
@Override @Override
public Decision evaluate(Decision decision) { public Decision evaluate(Decision decision) {
try { try {
Map<Policy, Map<Object, Decision.Effect>> decisionCache = new HashMap<>();
while (this.permissions.hasNext()) { 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(); decision.onComplete();
} catch (Throwable cause) { } catch (Throwable cause) {
decision.onError(cause); decision.onError(cause);
@ -57,21 +67,11 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
} }
@Override @Override
public List<Result> evaluate() { public Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request) {
AtomicReference<List<Result>> result = new AtomicReference<>(); DecisionPermissionCollector decision = new DecisionPermissionCollector(authorizationProvider, resourceServer, request);
evaluate(new DecisionResultCollector() { evaluate(decision);
@Override
public void onError(Throwable cause) {
throw new RuntimeException("Failed to evaluate permissions", cause);
}
@Override return decision.results();
protected void onComplete(List<Result> results) {
result.set(results);
}
});
return result.get();
} }
} }

View file

@ -17,10 +17,12 @@
*/ */
package org.keycloak.authorization.permission.evaluator; package org.keycloak.authorization.permission.evaluator;
import java.util.List; import java.util.Collection;
import org.keycloak.authorization.Decision; 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 * 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 { public interface PermissionEvaluator {
<D extends Decision> D evaluate(D decision); <D extends Decision> D evaluate(D decision);
List<Result> evaluate(); Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request);
} }

View file

@ -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;
}
}
}

View file

@ -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());
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2016 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 java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DecisionResult extends DecisionResultCollector {
protected List<Result> results;
protected Throwable error;
@Override
protected void onComplete(List<Result> results) {
this.results = results;
}
@Override
public void onError(Throwable cause) {
this.error = cause;
}
public boolean completed() {
return results != null && error == null;
}
public List<Result> getResults() {
return results;
}
public Throwable getError() {
return error;
}
}

View file

@ -18,99 +18,14 @@
package org.keycloak.authorization.policy.evaluation; package org.keycloak.authorization.policy.evaluation;
import java.util.HashMap; import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; 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.authorization.permission.ResourcePermission;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @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;
}
} }

View file

@ -18,6 +18,8 @@
package org.keycloak.authorization.policy.evaluation; package org.keycloak.authorization.policy.evaluation;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -51,25 +53,30 @@ public class DefaultEvaluation implements Evaluation {
private Policy policy; private Policy policy;
private final Policy parentPolicy; private final Policy parentPolicy;
private final AuthorizationProvider authorizationProvider; private final AuthorizationProvider authorizationProvider;
private Map<Policy, Map<Object, Effect>> decisionCache;
private final Realm realm; private final Realm realm;
private Effect effect; private Effect effect;
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider) { public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
this.permission = permission; this(permission, executionContext, parentPolicy, null, decision, authorizationProvider, decisionCache);
this.executionContext = executionContext;
this.parentPolicy = parentPolicy;
this.decision = decision;
this.authorizationProvider = authorizationProvider;
this.realm = createRealm();
} }
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) { 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.permission = permission;
this.executionContext = executionContext; this.executionContext = executionContext;
this.parentPolicy = parentPolicy; this.parentPolicy = parentPolicy;
this.policy = policy; this.policy = policy;
this.decision = decision; this.decision = decision;
this.authorizationProvider = authorizationProvider; this.authorizationProvider = authorizationProvider;
this.decisionCache = decisionCache;
this.realm = createRealm(); this.realm = createRealm();
} }
@ -131,6 +138,10 @@ public class DefaultEvaluation implements Evaluation {
return effect; return effect;
} }
public Map<Policy, Map<Object, Effect>> getDecisionCache() {
return decisionCache;
}
@Override @Override
public void denyIfNoEffect() { public void denyIfNoEffect() {
if (this.effect == null) { if (this.effect == null) {
@ -265,4 +276,12 @@ public class DefaultEvaluation implements Evaluation {
public void setPolicy(Policy policy) { public void setPolicy(Policy policy) {
this.policy = policy; this.policy = policy;
} }
public void setEffect(Effect effect) {
if (Effect.PERMIT.equals(effect)) {
grant();
} else {
deny();
}
}
} }

View file

@ -19,12 +19,10 @@
package org.keycloak.authorization.policy.evaluation; package org.keycloak.authorization.policy.evaluation;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
@ -45,163 +43,73 @@ import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
*/ */
public class DefaultPolicyEvaluator implements PolicyEvaluator { 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 @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(); ResourceServer resourceServer = permission.getResourceServer();
PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode(); PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) { 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; return;
} }
AtomicBoolean verified = new AtomicBoolean(false); Set<Policy> verified = new HashSet<>();
Consumer<Policy> consumer = createDecisionConsumer(permission, executionContext, decision, verified); Consumer<Policy> policyConsumer = createPolicyEvaluator(permission, authorizationProvider, executionContext, decision, verified, decisionCache);
Resource resource = permission.getResource(); Resource resource = permission.getResource();
List<Scope> scopes = permission.getScopes();
if (resource != null) { if (resource != null) {
evaluatePolicies(() -> policyStore.findByResource(resource.getId(), resourceServer.getId()), consumer); policyStore.findByResource(resource.getId(), resourceServer.getId(), policyConsumer);
if (resource.getType() != null) { if (resource.getType() != null) {
evaluatePolicies(() -> { policyStore.findByResourceType(resource.getType(), resourceServer.getId(), policyConsumer);
List<Policy> policies = policyStore.findByResourceType(resource.getType(), resourceServer.getId());
if (!resource.getOwner().equals(resourceServer.getId())) { if (!resource.getOwner().equals(resourceServer.getId())) {
for (Resource typedResource : resourceStore.findByType(resource.getType(), resourceServer.getId())) { for (Resource typedResource : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
policies.addAll(policyStore.findByResource(typedResource.getId(), resourceServer.getId())); policyStore.findByResource(typedResource.getId(), resourceServer.getId(), policyConsumer);
}
} }
}
return policies;
}, consumer);
} }
} }
List<Scope> scopes = permission.getScopes();
if (!scopes.isEmpty()) { 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()) { if (!verified.isEmpty()) {
createEvaluation(permission, executionContext, decision, null).grant(); decision.onComplete(permission);
return;
}
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode)) {
DefaultEvaluation evaluation = new DefaultEvaluation(permission, executionContext, decision, authorizationProvider);
evaluation.grant();
decision.onComplete(permission);
} }
} }
private void evaluatePolicies(Supplier<List<Policy>> supplier, Consumer<Policy> consumer) { private Consumer<Policy> createPolicyEvaluator(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Set<Policy> verified, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
List<Policy> policies = supplier.get(); return parentPolicy -> {
if (!verified.add(parentPolicy)) {
if (!policies.isEmpty()) {
policies.forEach(consumer);
}
}
private Consumer<Policy> createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicBoolean verified) {
return (parentPolicy) -> {
if (!hasRequestedScopes(permission, parentPolicy)) {
return; return;
} }
PolicyProvider policyProvider = authorization.getProvider(parentPolicy.getType()); PolicyProvider policyProvider = authorizationProvider.getProvider(parentPolicy.getType());
if (policyProvider == null) { if (policyProvider == null) {
throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "]."); throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
} }
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy); policyProvider.evaluate(new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorizationProvider, decisionCache));
policyProvider.evaluate(evaluation);
verified.compareAndSet(false, true);
}; };
} }
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;
}
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.authorization.policy.evaluation; package org.keycloak.authorization.policy.evaluation;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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> * @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 final AuthorizationRequest request;
private PermissionTicketToken ticket; private PermissionTicketToken ticket;
private final Identity identity; private final Identity identity;
private ResourceServer resourceServer; private ResourceServer resourceServer;
private final AuthorizationProvider authorization; private final AuthorizationProvider authorization;
private List<Result> results;
public PermissionTicketAwareDecisionResultCollector(AuthorizationRequest request, PermissionTicketToken ticket, Identity identity, ResourceServer resourceServer, AuthorizationProvider authorization) { public PermissionTicketAwareDecisionResultCollector(AuthorizationRequest request, PermissionTicketToken ticket, Identity identity, ResourceServer resourceServer, AuthorizationProvider authorization) {
super(authorization, resourceServer, request);
this.request = request; this.request = request;
this.ticket = ticket; this.ticket = ticket;
this.identity = identity; 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;
}
} }

View file

@ -18,9 +18,13 @@
package org.keycloak.authorization.policy.evaluation; package org.keycloak.authorization.policy.evaluation;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision; import org.keycloak.authorization.Decision;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import java.util.Map;
/** /**
* <p>A {@link PolicyEvaluator} evaluates authorization policies based on a given {@link ResourcePermission}, sending * <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. * 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 * @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);
} }

View file

@ -22,8 +22,9 @@ import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import java.util.ArrayList; import java.util.Collection;
import java.util.List; import java.util.HashMap;
import java.util.Map;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@ -31,33 +32,29 @@ import java.util.List;
public class Result { public class Result {
private final ResourcePermission permission; private final ResourcePermission permission;
private List<PolicyResult> results = new ArrayList<>(); private final Map<String, PolicyResult> results = new HashMap<>();
private Effect status; private final Evaluation evaluation;
private Effect status = Effect.DENY;
public Result(ResourcePermission permission) { public Result(ResourcePermission permission, Evaluation evaluation) {
this.permission = permission; this.permission = permission;
this.evaluation = evaluation;
} }
public ResourcePermission getPermission() { public ResourcePermission getPermission() {
return permission; return permission;
} }
public List<PolicyResult> getResults() { public Collection<PolicyResult> getResults() {
return results; return results.values();
}
public Evaluation getEvaluation() {
return evaluation;
} }
public PolicyResult policy(Policy policy) { public PolicyResult policy(Policy policy) {
for (PolicyResult result : this.results) { return results.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy));
if (result.getPolicy().equals(policy)) {
return result;
}
}
PolicyResult policyResult = new PolicyResult(policy);
this.results.add(policyResult);
return policyResult;
} }
public void setStatus(final Effect status) { public void setStatus(final Effect status) {
@ -71,50 +68,40 @@ public class Result {
public static class PolicyResult { public static class PolicyResult {
private final Policy policy; private final Policy policy;
private List<PolicyResult> associatedPolicies = new ArrayList<>(); private final Map<String, PolicyResult> associatedPolicies = new HashMap<>();
private Effect status; private Effect effect = Effect.DENY;
public PolicyResult(Policy policy, Effect status) {
this.policy = policy;
this.effect = status;
}
public PolicyResult(Policy policy) { public PolicyResult(Policy policy) {
this.policy = policy; this(policy, Effect.DENY);
} }
public PolicyResult status(Effect status) { public PolicyResult policy(Policy policy, Effect effect) {
this.status = status; PolicyResult result = associatedPolicies.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy, effect));
return this;
}
public PolicyResult policy(Policy policy) { result.setEffect(effect);
return getPolicy(policy, this.associatedPolicies);
}
private PolicyResult getPolicy(Policy policy, List<PolicyResult> results) { return result;
for (PolicyResult result : results) {
if (result.getPolicy().equals(policy)) {
return result;
}
}
PolicyResult policyResult = new PolicyResult(policy);
results.add(policyResult);
return policyResult;
} }
public Policy getPolicy() { public Policy getPolicy() {
return policy; return policy;
} }
public List<PolicyResult> getAssociatedPolicies() { public Collection<PolicyResult> getAssociatedPolicies() {
return associatedPolicies; return associatedPolicies.values();
} }
public Effect getStatus() { public Effect getEffect() {
return status; return effect;
} }
public void setStatus(final Effect status) { public void setEffect(final Effect status) {
this.status = status; this.effect = status;
} }
} }
} }

View file

@ -20,6 +20,7 @@ package org.keycloak.authorization.store;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
@ -93,6 +94,8 @@ public interface PolicyStore {
*/ */
List<Policy> findByResource(String resourceId, String resourceServerId); 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>. * 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); 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>. * 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 * @return a list of policies that depends on the a policy with the given identifier
*/ */
List<Policy> findDependentPolicies(String id, String resourceServerId); List<Policy> findDependentPolicies(String id, String resourceServerId);
void findByResourceType(String type, String id, Consumer<Policy> policyConsumer);
} }

View file

@ -1,5 +1,7 @@
package org.keycloak.scripting; package org.keycloak.scripting;
import javax.script.ScriptContext;
import org.keycloak.models.ScriptModel; import org.keycloak.models.ScriptModel;
/** /**
@ -11,4 +13,5 @@ public interface EvaluatableScriptAdapter {
ScriptModel getScriptModel(); ScriptModel getScriptModel();
Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException; Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException;
Object eval(ScriptContext context) throws ScriptExecutionException;
} }

View file

@ -23,13 +23,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.keycloak.Config; 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.PolicyProvider;
import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
/** /**
@ -38,6 +38,7 @@ import org.keycloak.provider.ProviderFactory;
public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory { public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
private Map<String, PolicyProviderFactory> policyProviderFactories; private Map<String, PolicyProviderFactory> policyProviderFactories;
private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator();
@Override @Override
public AuthorizationProvider create(KeycloakSession session) { public AuthorizationProvider create(KeycloakSession session) {
@ -65,7 +66,7 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
@Override @Override
public AuthorizationProvider create(KeycloakSession session, RealmModel realm) { 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) { private Map<String, PolicyProviderFactory> configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) {

View file

@ -21,7 +21,6 @@ package org.keycloak.authorization.admin;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; 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;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder; 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.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionContext; import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.authorization.AuthorizationRequest; 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.PolicyEvaluationRequest;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
@ -78,6 +79,8 @@ import org.keycloak.sessions.AuthenticationSessionModel;
*/ */
public class PolicyEvaluationService { public class PolicyEvaluationService {
private static final Logger logger = Logger.getLogger(PolicyEvaluationService.class);
private final AuthorizationProvider authorization; private final AuthorizationProvider authorization;
private final AdminPermissionEvaluator auth; private final AdminPermissionEvaluator auth;
private final ResourceServer resourceServer; 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(); return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity), request), resourceServer, authorization, identity)).build();
} catch (Exception e) { } catch (Exception e) {
logger.error("Error while evaluating permissions", e);
throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR); throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
} finally { } finally {
identity.close(); identity.close();
} }
} }
private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) { private EvaluationDecisionCollector evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate(); return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate(new EvaluationDecisionCollector(authorization, resourceServer, request));
} }
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) { private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
@ -171,7 +175,7 @@ public class PolicyEvaluationService {
if (resource.getId() != null) { if (resource.getId() != null) {
Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId(), resourceServer.getId()); 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) { } else if (resource.getType() != null) {
return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().map(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization, request)); return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().map(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization, request));
} else { } else {
@ -180,7 +184,7 @@ public class PolicyEvaluationService {
List<ResourcePermission> collect = new ArrayList<>(); List<ResourcePermission> collect = new ArrayList<>();
if (!scopes.isEmpty()) { 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 { } else {
collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization, request)); collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization, request));
} }
@ -261,4 +265,31 @@ public class PolicyEvaluationService {
return new CloseableKeycloakIdentity(accessToken, keycloakSession, userSession); 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();
}
}
} }

View file

@ -18,13 +18,13 @@ package org.keycloak.authorization.admin.representation;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision; import org.keycloak.authorization.Decision;
import org.keycloak.authorization.admin.PolicyEvaluationService;
import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.util.Permissions;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -38,6 +38,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -52,13 +53,13 @@ import java.util.stream.Stream;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class PolicyEvaluationResponseBuilder { 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(); PolicyEvaluationResponse response = new PolicyEvaluationResponse();
List<PolicyEvaluationResponse.EvaluationResultRepresentation> resultsRep = new ArrayList<>(); List<PolicyEvaluationResponse.EvaluationResultRepresentation> resultsRep = new ArrayList<>();
AccessToken accessToken = identity.getAccessToken(); AccessToken accessToken = identity.getAccessToken();
AccessToken.Authorization authorizationData = new AccessToken.Authorization(); AccessToken.Authorization authorizationData = new AccessToken.Authorization();
authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer)); authorizationData.setPermissions(decision.results());
accessToken.setAuthorization(authorizationData); accessToken.setAuthorization(authorizationData);
ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId()); ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId());
@ -69,6 +70,8 @@ public class PolicyEvaluationResponseBuilder {
response.setRpt(accessToken); response.setRpt(accessToken);
Collection<Result> results = decision.getResults();
if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) { if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) {
response.setStatus(DecisionEffect.DENY); response.setStatus(DecisionEffect.DENY);
} else { } else {
@ -217,7 +220,7 @@ public class PolicyEvaluationResponseBuilder {
policyResultRep.setPolicy(representation); policyResultRep.setPolicy(representation);
if (result.getStatus() == Decision.Effect.DENY) { if (result.getEffect() == Decision.Effect.DENY) {
policyResultRep.setStatus(DecisionEffect.DENY); policyResultRep.setStatus(DecisionEffect.DENY);
policyResultRep.setScopes(representation.getScopes()); policyResultRep.setScopes(representation.getScopes());
} else { } else {

View file

@ -19,6 +19,7 @@ package org.keycloak.authorization.authorization;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -46,7 +47,6 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.PermissionTicketAwareDecisionResultCollector; 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.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore; 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"; 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 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; private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
static { static {
@ -125,28 +128,20 @@ public class AuthorizationTokenService {
}); });
} }
private final TokenManager tokenManager; private static final AuthorizationTokenService INSTANCE = new AuthorizationTokenService();
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) { public static AuthorizationTokenService instance() {
this.tokenManager = tokenManager; return INSTANCE;
this.event = event;
this.httpRequest = httpRequest;
this.authorization = authorization;
this.cors = cors;
} }
public Response authorize(AuthorizationRequest request) { public Response authorize(KeycloakAuthorizationRequest request) {
if (request == null) { 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 // it is not secure to allow public clients to push arbitrary claims because message can be tampered
if (isPublicClientRequestingEntitlementWithClaims(request)) { 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 { try {
@ -154,35 +149,46 @@ public class AuthorizationTokenService {
request.setClaims(ticket.getClaims()); request.setClaims(ticket.getClaims());
ResourceServer resourceServer = getResourceServer(ticket); ResourceServer resourceServer = getResourceServer(ticket, request);
KeycloakEvaluationContext evaluationContext = createEvaluationContext(request); KeycloakEvaluationContext evaluationContext = createEvaluationContext(request);
KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity()); KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
List<Result> evaluation; Collection<Permission> permissions;
if (ticket.getPermissions().isEmpty() && request.getRpt() == null) { if (request.getTicket() != null) {
evaluation = evaluateAllPermissions(request, resourceServer, evaluationContext, identity); permissions = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
} else if(!request.getPermissions().getPermissions().isEmpty()) { } else if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
evaluation = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity); permissions = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
} else { } 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)) { if (isGranted(ticket, request, permissions)) {
ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId()); AuthorizationProvider authorization = request.getAuthorization();
AuthorizationResponse response = createAuthorizationResponse(identity, permissions, request, targetClient); ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
Metadata metadata = request.getMetadata();
String responseMode = metadata != null ? metadata.getResponseMode() : null;
return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response)) if (responseMode != null) {
.allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient) if (RESPONSE_MODE_DECISION.equals(metadata.getResponseMode())) {
.allowedMethods(HttpMethod.POST) Map<String, Object> responseClaims = new HashMap<>();
.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()) { 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 { } 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) { } catch (ErrorResponseException | CorsErrorResponseException cause) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -191,45 +197,55 @@ public class AuthorizationTokenService {
throw cause; throw cause;
} catch (Exception cause) { } catch (Exception cause) {
logger.error("Unexpected error while evaluating permissions", 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) { private Response createSuccessfulResponse(Object response, ClientModel targetClient, KeycloakAuthorizationRequest request) {
return request.getClaimToken() != null && getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null; 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 List<Result> evaluatePermissions(AuthorizationRequest authorizationRequest, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) { private boolean isPublicClientRequestingEntitlementWithClaims(KeycloakAuthorizationRequest request) {
return request.getClaimToken() != null && request.getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
}
private Collection<Permission> evaluatePermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
AuthorizationProvider authorization = request.getAuthorization();
return authorization.evaluators() return authorization.evaluators()
.from(createPermissions(ticket, authorizationRequest, resourceServer, identity, authorization), evaluationContext) .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
.evaluate(); .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() return authorization.evaluators()
.from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext) .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
.evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results(); .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() return authorization.evaluators()
.from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext) .from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext)
.evaluate(); .evaluate(resourceServer, request);
} }
private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) { private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, Collection<Permission> entitlements, KeycloakAuthorizationRequest request, ClientModel targetClient) {
KeycloakSession keycloakSession = getKeycloakSession(); KeycloakSession keycloakSession = request.getKeycloakSession();
AccessToken accessToken = identity.getAccessToken(); AccessToken accessToken = identity.getAccessToken();
UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(getRealm(), accessToken.getSessionState()); RealmModel realm = request.getRealm();
ClientModel client = getRealm().getClientByClientId(accessToken.getIssuedFor()); UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(realm, accessToken.getSessionState());
ClientModel client = realm.getClientByClientId(accessToken.getIssuedFor());
AuthenticatedClientSessionModel clientSession = userSessionModel.getAuthenticatedClientSessionByClient(client.getId()); AuthenticatedClientSessionModel clientSession = userSessionModel.getAuthenticatedClientSessionByClient(client.getId());
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession); ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession);
TokenManager tokenManager = request.getTokenManager();
AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(getRealm(), clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx) EventBuilder event = request.getEvent();
AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx)
.generateAccessToken() .generateAccessToken()
.generateRefreshToken(); .generateRefreshToken();
AccessToken rpt = responseBuilder.getAccessToken(); AccessToken rpt = responseBuilder.getAccessToken();
rpt.issuedFor(client.getClientId()); rpt.issuedFor(client.getClientId());
@ -262,7 +278,7 @@ public class AuthorizationTokenService {
Authorization previousAuthorization = previousRpt.getAuthorization(); Authorization previousAuthorization = previousRpt.getAuthorization();
if (previousAuthorization != null) { if (previousAuthorization != null) {
List<Permission> previousPermissions = previousAuthorization.getPermissions(); Collection<Permission> previousPermissions = previousAuthorization.getPermissions();
if (previousPermissions != null) { if (previousPermissions != null) {
for (Permission previousPermission : previousPermissions) { for (Permission previousPermission : previousPermissions) {
@ -276,7 +292,7 @@ public class AuthorizationTokenService {
return true; 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 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) { if (request.getTicket() != null) {
return verifyPermissionTicket(request); return verifyPermissionTicket(request);
@ -292,32 +308,33 @@ public class AuthorizationTokenService {
return permissions; return permissions;
} }
private ResourceServer getResourceServer(PermissionTicketToken ticket) { private ResourceServer getResourceServer(PermissionTicketToken ticket, KeycloakAuthorizationRequest request) {
AuthorizationProvider authorization = request.getAuthorization();
StoreFactory storeFactory = authorization.getStoreFactory(); StoreFactory storeFactory = authorization.getStoreFactory();
ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore(); ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore();
String[] audience = ticket.getAudience(); String[] audience = ticket.getAudience();
if (audience == null || audience.length == 0) { 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) { 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()); ResourceServer resourceServer = resourceServerStore.findById(clientModel.getId());
if (resourceServer == null) { 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; return resourceServer;
} }
private KeycloakEvaluationContext createEvaluationContext(AuthorizationRequest authorizationRequest) { private KeycloakEvaluationContext createEvaluationContext(KeycloakAuthorizationRequest request) {
String claimTokenFormat = authorizationRequest.getClaimTokenFormat(); String claimTokenFormat = request.getClaimTokenFormat();
if (claimTokenFormat == null) { if (claimTokenFormat == null) {
claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN; claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
@ -326,13 +343,13 @@ public class AuthorizationTokenService {
BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat); BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
if (evaluationContextProvider == null) { 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(); StoreFactory storeFactory = authorization.getStoreFactory();
Map<String, ResourcePermission> permissionsToEvaluate = new LinkedHashMap<>(); Map<String, ResourcePermission> permissionsToEvaluate = new LinkedHashMap<>();
ResourceStore resourceStore = storeFactory.getResourceStore(); ResourceStore resourceStore = storeFactory.getResourceStore();
@ -340,30 +357,35 @@ public class AuthorizationTokenService {
Metadata metadata = request.getMetadata(); Metadata metadata = request.getMetadata();
Integer limit = metadata != null ? metadata.getLimit() : null; Integer limit = metadata != null ? metadata.getLimit() : null;
for (Permission requestedResource : ticket.getPermissions()) { for (Permission permission : ticket.getPermissions()) {
if (limit != null && limit <= 0) { if (limit != null && limit <= 0) {
break; break;
} }
Set<String> requestedScopes = requestedResource.getScopes(); Set<String> requestedScopes = permission.getScopes();
if (requestedResource.getScopes() == null) { if (permission.getScopes() == null) {
requestedScopes = new HashSet<>(); requestedScopes = new HashSet<>();
} }
List<Resource> existingResources = new ArrayList<>(); List<Resource> existingResources = new ArrayList<>();
String resourceId = permission.getResourceId();
if (requestedResource.getResourceId() != null) { if (resourceId != null) {
Resource resource = resourceStore.findById(requestedResource.getResourceId(), resourceServer.getId()); Resource resource = null;
if (resourceId.indexOf('-') != -1) {
resource = resourceStore.findById(resourceId, resourceServer.getId());
}
if (resource != null) { if (resource != null) {
existingResources.add(resource); existingResources.add(resource);
} else { } else {
String resourceName = requestedResource.getResourceId(); String resourceName = resourceId;
Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId()); Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId());
if (ownerResource != null) { if (ownerResource != null) {
requestedResource.setResourceId(ownerResource.getId()); permission.setResourceId(ownerResource.getId());
existingResources.add(ownerResource); existingResources.add(ownerResource);
} }
@ -371,7 +393,7 @@ public class AuthorizationTokenService {
Resource serverResource = resourceStore.findByName(resourceName, resourceServer.getId()); Resource serverResource = resourceStore.findByName(resourceName, resourceServer.getId());
if (serverResource != null) { if (serverResource != null) {
requestedResource.setResourceId(serverResource.getId()); permission.setResourceId(serverResource.getId());
existingResources.add(serverResource); 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()); 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()) { if (resourceId != null && existingResources.isEmpty()) {
throw new CorsErrorResponseException(cors, "invalid_resource", "Resource with id [" + requestedResource.getResourceId() + "] does not exist.", Status.BAD_REQUEST); 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()) { if ((permission.getScopes() != null && !permission.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) {
throw new CorsErrorResponseException(cors, "invalid_scope", "One of the given scopes " + requestedResource.getScopes() + " are invalid", Status.BAD_REQUEST); throw new CorsErrorResponseException(request.getCors(), "invalid_scope", "One of the given scopes " + permission.getScopes() + " are invalid", Status.BAD_REQUEST);
} }
if (!existingResources.isEmpty()) { if (!existingResources.isEmpty()) {
for (Resource resource : existingResources) { for (Resource resource : existingResources) {
ResourcePermission permission = permissionsToEvaluate.get(resource.getId()); ResourcePermission perm = permissionsToEvaluate.get(resource.getId());
if (permission == null) { if (perm == null) {
permission = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request); perm = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
permissionsToEvaluate.put(resource.getId(), permission); permissionsToEvaluate.put(resource.getId(), perm);
if (limit != null) { if (limit != null) {
limit--; limit--;
} }
} else { } else {
for (Scope scope : requestedScopesModel) { for (Scope scope : requestedScopesModel) {
if (!permission.getScopes().contains(scope)) { if (!perm.getScopes().contains(scope)) {
permission.getScopes().add(scope); perm.getScopes().add(scope);
} }
} }
} }
@ -415,14 +437,16 @@ public class AuthorizationTokenService {
} else { } else {
List<Resource> resources = resourceStore.findByScope(requestedScopesModel.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()); List<Resource> resources = resourceStore.findByScope(requestedScopesModel.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId());
for (Resource resource : resources) { if (resources.isEmpty()) {
permissionsToEvaluate.put(resource.getId(), Permissions.createResourcePermissions(resource, requestedScopes, authorization, request)); permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
if (limit != null) { } else {
limit--; 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(); AccessToken.Authorization authorizationData = rpt.getAuthorization();
if (authorizationData != null) { if (authorizationData != null) {
List<Permission> permissions = authorizationData.getPermissions(); Collection<Permission> permissions = authorizationData.getPermissions();
if (permissions != null) { if (permissions != null) {
for (Permission grantedPermission : permissions) { 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(); String ticketString = request.getTicket();
if (ticketString == null || !Tokens.verifySignature(getKeycloakSession(), getRealm(), ticketString)) { if (ticketString == null || !Tokens.verifySignature(request.getKeycloakSession(), request.getRealm(), ticketString)) {
throw new CorsErrorResponseException(cors, "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN); throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
} }
try { try {
PermissionTicketToken ticket = new JWSInput(ticketString).readJsonContent(PermissionTicketToken.class); PermissionTicketToken ticket = new JWSInput(ticketString).readJsonContent(PermissionTicketToken.class);
if (!ticket.isActive()) { 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; return ticket;
} catch (JWSInputException e) { } 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(); List<Permission> requestedPermissions = ticket.getPermissions();
// denies in case a rpt was provided along with the authorization request but any requested permission was not granted // 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(); return !permissions.isEmpty();
} }
private KeycloakSession getKeycloakSession() { public static class KeycloakAuthorizationRequest extends AuthorizationRequest {
return this.authorization.getKeycloakSession();
}
private RealmModel getRealm() { private final AuthorizationProvider authorization;
return getKeycloakSession().getContext().getRealm(); 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;
}
HttpRequest getHttpRequest() {
return httpRequest;
}
AuthorizationProvider getAuthorization() {
return authorization;
}
Cors getCors() {
return cors;
}
KeycloakSession getKeycloakSession() {
return getAuthorization().getKeycloakSession();
}
RealmModel getRealm() {
return getKeycloakSession().getContext().getRealm();
}
} }
} }

View file

@ -21,8 +21,6 @@ package org.keycloak.authorization.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -32,21 +30,17 @@ import java.util.stream.Collectors;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.identity.Identity; import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.PermissionTicket;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.representations.idm.authorization.AuthorizationRequest; import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata; import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ErrorResponseException;
/** /**
@ -54,8 +48,8 @@ import org.keycloak.services.ErrorResponseException;
*/ */
public final class Permissions { public final class Permissions {
public static List<ResourcePermission> permission(ResourceServer server, Resource resource, Scope scope) { public static ResourcePermission permission(ResourceServer server, Resource resource, Scope scope) {
return Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), server)); return new ResourcePermission(resource, new ArrayList<>(Arrays.asList(scope)), server);
} }
/** /**
@ -73,23 +67,41 @@ public final class Permissions {
List<ResourcePermission> permissions = new ArrayList<>(); List<ResourcePermission> permissions = new ArrayList<>();
StoreFactory storeFactory = authorization.getStoreFactory(); StoreFactory storeFactory = authorization.getStoreFactory();
ResourceStore resourceStore = storeFactory.getResourceStore(); 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 // 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 // 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) // obtain all resources granted to the user via permission tickets (uma)
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId()); List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId());
Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
for (PermissionTicket ticket : tickets) { if (!tickets.isEmpty()) {
userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer, request.getClaims())); 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; return permissions;
} }
@ -131,8 +143,7 @@ public final class Permissions {
return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims()); return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
} }
public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) { public static ResourcePermission createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
List<ResourcePermission> permissions = new ArrayList<>();
String type = resource.getType(); String type = resource.getType();
ResourceServer resourceServer = resource.getResourceServer(); ResourceServer resourceServer = resource.getResourceServer();
@ -152,151 +163,6 @@ public final class Permissions {
}); });
} }
permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims())); return 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);
}
} }
} }

View file

@ -26,7 +26,6 @@ import org.keycloak.OAuthErrorException;
import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.authorization.AuthorizationTokenService; import org.keycloak.authorization.authorization.AuthorizationTokenService;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.authorization.util.Tokens; import org.keycloak.authorization.util.Tokens;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.ExchangeExternalToken; 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.setClaimToken(claimToken);
authorizationRequest.setClaimTokenFormat(claimTokenFormat); authorizationRequest.setClaimTokenFormat(claimTokenFormat);
authorizationRequest.setPct(formParams.getFirst("pct")); authorizationRequest.setPct(formParams.getFirst("pct"));
String rpt = formParams.getFirst("rpt"); String rpt = formParams.getFirst("rpt");
if (rpt != null) { if (rpt != null) {
@ -1128,9 +1129,11 @@ public class TokenEndpoint {
metadata.setLimit(Integer.parseInt(responsePermissionsLimit)); metadata.setLimit(Integer.parseInt(responsePermissionsLimit));
} }
metadata.setResponseMode(formParams.getFirst("response_mode"));
authorizationRequest.setMetadata(metadata); 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 // https://tools.ietf.org/html/rfc7636#section-4.1

View file

@ -2,6 +2,7 @@ package org.keycloak.scripting;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.CompiledScript; import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptException; import javax.script.ScriptException;
@ -37,4 +38,13 @@ class CompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapter
protected Object eval(final Bindings bindings) throws ScriptException { protected Object eval(final Bindings bindings) throws ScriptException {
return compiledScript.eval(bindings); return compiledScript.eval(bindings);
} }
@Override
public Object eval(ScriptContext context) throws ScriptExecutionException {
try {
return compiledScript.eval(context);
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.scripting; package org.keycloak.scripting;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptException; import javax.script.ScriptException;
@ -36,4 +37,12 @@ class UncompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapte
return getEngine().eval(getCode(), bindings); 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);
}
}
} }

View file

@ -20,7 +20,6 @@ import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.AuthorizationProviderFactory;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.common.DefaultEvaluationContext; import org.keycloak.authorization.common.DefaultEvaluationContext;
import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.common.UserModelIdentity; 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.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.permission.evaluator.PermissionEvaluator; import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
import org.keycloak.authorization.policy.evaluation.DecisionResult;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.util.Permissions; import org.keycloak.authorization.util.Permissions;
@ -45,7 +43,7 @@ import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.AdminAuth; import org.keycloak.services.resources.admin.AdminAuth;
import java.util.List; import java.util.Arrays;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -332,15 +330,8 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
RealmModel oldRealm = session.getContext().getRealm(); RealmModel oldRealm = session.getContext().getRealm();
try { try {
session.getContext().setRealm(realm); session.getContext().setRealm(realm);
DecisionResult decisionCollector = new DecisionResult(); ResourcePermission permission = Permissions.permission(resourceServer, resource, scope);
List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope); return !authz.evaluators().from(Arrays.asList(permission), context).evaluate(resourceServer, null).isEmpty();
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;
} finally { } finally {
session.getContext().setRealm(oldRealm); session.getContext().setRealm(oldRealm);
} }

View file

@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; 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.AuthzClient;
import org.keycloak.authorization.client.Configuration; import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.resource.ProtectionResource; 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.RealmRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationRequest; import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationResponse; 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.PermissionRequest;
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RoleBuilder; 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(); Iterator<Permission> iterator = permissions.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {

View file

@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -174,7 +175,7 @@ public class AuthorizationTest extends AbstractAuthzTest {
AuthorizationResponse response = getAuthzClient().authorization(userName, password).authorize(request); AuthorizationResponse response = getAuthzClient().authorization(userName, password).authorize(request);
AccessToken token = toAccessToken(response.getToken()); AccessToken token = toAccessToken(response.getToken());
Authorization authorization = token.getAuthorization(); Authorization authorization = token.getAuthorization();
return authorization.getPermissions(); return new ArrayList<>(authorization.getPermissions());
} }
private void createResourcePermission(ResourceRepresentation resource, String... policies) { private void createResourcePermission(ResourceRepresentation resource, String... policies) {

View file

@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -127,7 +128,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); List<Permission> permissions = new ArrayList<>(authorization.getPermissions());
assertFalse(permissions.isEmpty()); assertFalse(permissions.isEmpty());
assertEquals("Default Resource", permissions.get(0).getResourceName()); assertEquals("Default Resource", permissions.get(0).getResourceName());

View file

@ -16,14 +16,17 @@
*/ */
package org.keycloak.testsuite.authz; package org.keycloak.testsuite.authz;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -44,9 +47,11 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.AuthorizationResponse; import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.Permission; 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.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation; 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.ScopePermissionRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
@ -76,13 +81,14 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
@Before @Before
public void configureAuthorization() throws Exception { public void configureAuthorization() throws Exception {
createResourcesAndScopes();
RealmResource realm = getRealm(); RealmResource realm = getRealm();
ClientResource client = getClient(realm); ClientResource client = getClient(realm);
createPolicies(realm, client); if (client.authorization().resources().findByName("Resource A").isEmpty()) {
createPermissions(client); createResourcesAndScopes();
createPolicies(realm, client);
createPermissions(client);
}
} }
/** /**
@ -91,24 +97,29 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
* <p>Scope Read should not be granted for Marta. * <p>Scope Read should not be granted for Marta.
*/ */
@Test @Test
public void testMartaCanAccessResourceAWithExecuteAndWrite() { public void testMartaCanAccessResourceAWithExecuteAndWrite() throws Exception {
List<Permission> permissions = getEntitlements("marta", "password"); 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)) { for (Permission permission : new ArrayList<>(permissions)) {
String resourceSetName = permission.getResourceName(); String resourceSetName = permission.getResourceName();
switch (resourceSetName) { switch (resourceSetName) {
case "Resource A": case "Resource A":
assertEquals(2, permission.getScopes().size()); assertThat(permission.getScopes(), containsInAnyOrder("execute", "write"));
assertTrue(permission.getScopes().contains("execute"));
assertTrue(permission.getScopes().contains("write"));
permissions.remove(permission); permissions.remove(permission);
break; break;
case "Resource C": case "Resource C":
assertEquals(3, permission.getScopes().size()); assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
assertTrue(permission.getScopes().contains("execute"));
assertTrue(permission.getScopes().contains("write"));
assertTrue(permission.getScopes().contains("read"));
permissions.remove(permission); permissions.remove(permission);
break; break;
default: default:
@ -119,7 +130,83 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
assertTrue(permissions.isEmpty()); assertTrue(permissions.isEmpty());
} }
private List<Permission> getEntitlements(String username, String password) { @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:
fail("Unexpected permission for resource [" + resourceSetName + "]");
}
}
assertTrue(permissions.isEmpty());
}
private Collection<Permission> getEntitlements(String username, String password) {
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
AuthorizationResponse response = authzClient.authorization(username, password).authorize(); AuthorizationResponse response = authzClient.authorization(username, password).authorize();
AccessToken accessToken; AccessToken accessToken;
@ -147,7 +234,7 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
} }
private void createPermissions(ClientResource client) throws IOException { 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 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); createScopePermission("Resource A Scope Read Only For Kolo Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Kolo Policy"), client);
} }

View file

@ -26,6 +26,7 @@ import static org.junit.Assert.fail;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -273,7 +274,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
AuthorizationResponse response = getAuthzClient(configFile).authorization("marta", "password").authorize(request); AuthorizationResponse response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
AccessToken rpt = toAccessToken(response.getToken()); AccessToken rpt = toAccessToken(response.getToken());
List<Permission> permissions = rpt.getAuthorization().getPermissions(); List<Permission> permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
assertEquals(10, permissions.size()); assertEquals(10, permissions.size());
@ -293,7 +294,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request); response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken()); rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions(); permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
assertEquals(10, permissions.size()); assertEquals(10, permissions.size());
@ -317,7 +318,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request); response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken()); rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions(); permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
assertEquals(10, permissions.size()); assertEquals(10, permissions.size());
assertEquals("Resource 16", permissions.get(0).getResourceName()); assertEquals("Resource 16", permissions.get(0).getResourceName());
@ -340,7 +341,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request); response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
rpt = toAccessToken(response.getToken()); rpt = toAccessToken(response.getToken());
permissions = rpt.getAuthorization().getPermissions(); permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
assertEquals(5, permissions.size()); assertEquals(5, permissions.size());
assertEquals("Resource 16", permissions.get(0).getResourceName()); assertEquals("Resource 16", permissions.get(0).getResourceName());
@ -442,7 +443,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
authorization.resources().resource(resource.getId()).update(resource); 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())); assertFalse(hasPermission("kolo", "password", resource.getId()));
accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken(); accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
@ -486,6 +487,39 @@ public class EntitlementAPITest extends AbstractAuthzTest {
assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B")); assertFalse(hasPermission("kolo", "password", resource.getId(), "Scope B"));
} }
@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 @Test
public void testObtainAllEntitlementsInvalidResource() throws Exception { public void testObtainAllEntitlementsInvalidResource() throws Exception {
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST); ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
@ -625,7 +659,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request); AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request);
assertNotNull(response.getToken()); assertNotNull(response.getToken());
List<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions(); Collection<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
assertEquals(2, permissions.size()); assertEquals(2, permissions.size());
for (Permission grantedPermission : permissions) { for (Permission grantedPermission : permissions) {
@ -697,7 +731,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest()); AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest());
AccessToken rpt = toAccessToken(response.getToken()); AccessToken rpt = toAccessToken(response.getToken());
Authorization authz = rpt.getAuthorization(); Authorization authz = rpt.getAuthorization();
List<Permission> permissions = authz.getPermissions(); Collection<Permission> permissions = authz.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertFalse(permissions.isEmpty()); assertFalse(permissions.isEmpty());
@ -718,7 +752,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) { private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertFalse(permissions.isEmpty()); assertFalse(permissions.isEmpty());

View file

@ -21,7 +21,9 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -128,7 +130,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
assertNotNull(response.getToken()); assertNotNull(response.getToken());
AccessToken rpt = toAccessToken(response.getToken()); AccessToken rpt = toAccessToken(response.getToken());
Authorization authorizationClaim = rpt.getAuthorization(); Authorization authorizationClaim = rpt.getAuthorization();
List<Permission> permissions = authorizationClaim.getPermissions(); List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
assertEquals(1, permissions.size()); assertEquals(1, permissions.size());
@ -164,7 +166,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
assertNotNull(response.getToken()); assertNotNull(response.getToken());
AccessToken rpt = toAccessToken(response.getToken()); AccessToken rpt = toAccessToken(response.getToken());
Authorization authorizationClaim = rpt.getAuthorization(); Authorization authorizationClaim = rpt.getAuthorization();
List<Permission> permissions = authorizationClaim.getPermissions(); List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
assertEquals(1, permissions.size()); assertEquals(1, permissions.size());

View file

@ -664,6 +664,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
} }
return baseAttributes; return baseAttributes;
} }
}, policy, evaluation -> {}, authorization); }, policy, evaluation -> {}, authorization, null);
} }
} }

View file

@ -23,6 +23,7 @@ import static org.junit.Assert.fail;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT; import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
import java.net.URI; import java.net.URI;
import java.util.Collection;
import java.util.List; import java.util.List;
import javax.ws.rs.client.Client; 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"}); AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"}, new String[] {"ScopeC"});
AccessToken accessToken = toAccessToken(response.getToken()); AccessToken accessToken = toAccessToken(response.getToken());
AccessToken.Authorization authorization = accessToken.getAuthorization(); AccessToken.Authorization authorization = accessToken.getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB", "ScopeC"); 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"}); AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); 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"}); AuthorizationResponse response = authorize("marta", "password", null, new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertFalse(response.isUpgraded()); assertFalse(response.isUpgraded());
assertNotNull(permissions); assertNotNull(permissions);
@ -155,7 +156,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"}); AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertFalse(response.isUpgraded()); assertFalse(response.isUpgraded());
assertNotNull(permissions); assertNotNull(permissions);
@ -194,7 +195,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
AuthorizationResponse response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeA", "ScopeB"}); AuthorizationResponse response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeA", "ScopeB"});
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertFalse(response.isUpgraded()); assertFalse(response.isUpgraded());
assertNotNull(permissions); assertNotNull(permissions);
@ -261,7 +262,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
AuthorizationResponse response = authorize("marta", "password", resourceA.getName(), new String[] {"READ"}); AuthorizationResponse response = authorize("marta", "password", resourceA.getName(), new String[] {"READ"});
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertFalse(response.isUpgraded()); assertFalse(response.isUpgraded());
assertNotNull(permissions); assertNotNull(permissions);
@ -303,7 +304,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
new PermissionRequest(resourceB.getName(), "ScopeC")); new PermissionRequest(resourceB.getName(), "ScopeC"));
String rpt = response.getToken(); String rpt = response.getToken();
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization(); AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB"); assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB");
@ -324,7 +325,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@ -346,7 +347,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@ -366,7 +367,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@ -447,7 +448,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");

View file

@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -84,7 +85,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB"); assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
@ -142,7 +143,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB"); assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
@ -204,7 +205,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
@ -280,7 +281,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A"); assertPermissions(permissions, "Resource A");
@ -382,7 +383,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
assertNotNull(authorization); assertNotNull(authorization);
List<Permission> permissions = authorization.getPermissions(); Collection<Permission> permissions = authorization.getPermissions();
assertNotNull(permissions); assertNotNull(permissions);
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB"); assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");

View file

@ -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