[KEYCLOAK-4902] - Refactoring and improvements to processing of authz requests
This commit is contained in:
parent
65f51b7b83
commit
80e5227bcd
50 changed files with 1343 additions and 825 deletions
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -151,7 +152,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||
}
|
||||
|
||||
boolean hasPermission = false;
|
||||
List<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -78,8 +78,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
List<Permission> grantedPermissions = authorization.getPermissions();
|
||||
List<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
|
|
|
@ -17,10 +17,15 @@
|
|||
*/
|
||||
package org.keycloak.authorization.policy.provider.aggregated;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
|
@ -34,31 +39,40 @@ public class AggregatePolicyProvider implements PolicyProvider {
|
|||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
//TODO: need to detect deep recursions
|
||||
DecisionResultCollector decision = new DecisionResultCollector() {
|
||||
@Override
|
||||
protected void onComplete(List<Result> results) {
|
||||
if (results.isEmpty()) {
|
||||
evaluation.deny();
|
||||
} else {
|
||||
Result result = results.iterator().next();
|
||||
|
||||
if (Effect.PERMIT.equals(result.getEffect())) {
|
||||
protected void onComplete(Result result) {
|
||||
if (isGranted(result.getResults().iterator().next())) {
|
||||
evaluation.grant();
|
||||
}
|
||||
} else {
|
||||
evaluation.deny();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Policy policy = evaluation.getPolicy();
|
||||
AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
|
||||
Policy policy = evaluation.getPolicy();
|
||||
DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
|
||||
Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
|
||||
ResourcePermission permission = evaluation.getPermission();
|
||||
|
||||
policy.getAssociatedPolicies().forEach(associatedPolicy -> {
|
||||
for (Policy associatedPolicy : policy.getAssociatedPolicies()) {
|
||||
Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
|
||||
Decision.Effect effect = decisions.get(permission);
|
||||
DefaultEvaluation eval = new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization, decisionCache);
|
||||
|
||||
if (effect == null) {
|
||||
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
|
||||
policyProvider.evaluate(new DefaultEvaluation(evaluation.getPermission(), evaluation.getContext(), policy, associatedPolicy, decision, authorization));
|
||||
});
|
||||
|
||||
decision.onComplete();
|
||||
policyProvider.evaluate(eval);
|
||||
|
||||
eval.denyIfNoEffect();
|
||||
decisions.put(permission, eval.getEffect());
|
||||
} else {
|
||||
eval.setEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
decision.onComplete(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,6 +19,9 @@ package org.keycloak.authorization.policy.provider.js;
|
|||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.SimpleScriptContext;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
|
@ -40,14 +43,14 @@ class JSPolicyProvider implements PolicyProvider {
|
|||
public void evaluate(Evaluation evaluation) {
|
||||
Policy policy = evaluation.getPolicy();
|
||||
AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
|
||||
final EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
|
||||
EvaluatableScriptAdapter adapter = evaluatableScript.apply(authorization, policy);
|
||||
|
||||
try {
|
||||
//how to deal with long running scripts -> timeout?
|
||||
adapter.eval(bindings -> {
|
||||
bindings.put("script", adapter.getScriptModel());
|
||||
bindings.put("$evaluation", evaluation);
|
||||
});
|
||||
SimpleScriptContext context = new SimpleScriptContext();
|
||||
|
||||
context.setAttribute("$evaluation", evaluation, ScriptContext.ENGINE_SCOPE);
|
||||
|
||||
adapter.eval(context);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Error evaluating JS Policy [" + policy.getName() + "].", e);
|
||||
|
|
|
@ -67,6 +67,12 @@ public class ScriptCache {
|
|||
|
||||
public EvaluatableScriptAdapter computeIfAbsent(String id, Function<String, EvaluatableScriptAdapter> function) {
|
||||
try {
|
||||
EvaluatableScriptAdapter adapter = removeIfExpired(cache.get(id));
|
||||
|
||||
if (adapter != null) {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -83,20 +89,6 @@ public class ScriptCache {
|
|||
}
|
||||
}
|
||||
|
||||
public EvaluatableScriptAdapter get(String uri) {
|
||||
if (parkForReadAndCheckInterrupt()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry cached = cache.get(uri);
|
||||
|
||||
if (cached != null) {
|
||||
return removeIfExpired(cached);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
|
@ -132,16 +124,6 @@ public class ScriptCache {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean parkForReadAndCheckInterrupt() {
|
||||
while (writing.get()) {
|
||||
LockSupport.parkNanos(1L);
|
||||
if (Thread.interrupted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class CacheEntry {
|
||||
|
||||
final String key;
|
||||
|
|
|
@ -17,34 +17,42 @@
|
|||
package org.keycloak.authorization.policy.provider.permission;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class AbstractPermissionProvider implements PolicyProvider {
|
||||
|
||||
public AbstractPermissionProvider() {
|
||||
|
||||
}
|
||||
public abstract class AbstractPermissionProvider implements PolicyProvider {
|
||||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
if (!(evaluation instanceof DefaultEvaluation)) {
|
||||
throw new IllegalArgumentException("Unexpected evaluation instance type [" + evaluation.getClass() + "]");
|
||||
}
|
||||
|
||||
Policy policy = evaluation.getPolicy();
|
||||
AuthorizationProvider authorization = evaluation.getAuthorizationProvider();
|
||||
DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
|
||||
Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
|
||||
Policy policy = evaluation.getPolicy();
|
||||
ResourcePermission permission = evaluation.getPermission();
|
||||
|
||||
policy.getAssociatedPolicies().forEach(associatedPolicy -> {
|
||||
Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(associatedPolicy, p -> new HashMap<>());
|
||||
Decision.Effect effect = decisions.get(permission);
|
||||
|
||||
if (effect == null) {
|
||||
PolicyProvider policyProvider = authorization.getProvider(associatedPolicy.getType());
|
||||
DefaultEvaluation.class.cast(evaluation).setPolicy(associatedPolicy);
|
||||
policyProvider.evaluate(evaluation);
|
||||
defaultEvaluation.setPolicy(associatedPolicy);
|
||||
policyProvider.evaluate(defaultEvaluation);
|
||||
evaluation.denyIfNoEffect();
|
||||
decisions.put(permission, defaultEvaluation.getEffect());
|
||||
} else {
|
||||
defaultEvaluation.setEffect(effect);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,36 @@
|
|||
*/
|
||||
package org.keycloak.authorization.policy.provider.permission;
|
||||
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ResourcePolicyProvider extends AbstractPermissionProvider {
|
||||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
|
||||
Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
|
||||
Policy policy = defaultEvaluation.getParentPolicy();
|
||||
Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(policy, p -> new HashMap<>());
|
||||
ResourcePermission permission = evaluation.getPermission();
|
||||
Decision.Effect effect = decisions.get(permission.getResource());
|
||||
|
||||
if (effect != null) {
|
||||
defaultEvaluation.setEffect(effect);
|
||||
return;
|
||||
}
|
||||
|
||||
super.evaluate(evaluation);
|
||||
|
||||
decisions.put(permission.getResource(), defaultEvaluation.getEffect());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,45 @@
|
|||
*/
|
||||
package org.keycloak.authorization.policy.provider.permission;
|
||||
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ScopePolicyProvider extends AbstractPermissionProvider {
|
||||
|
||||
@Override
|
||||
public void evaluate(Evaluation evaluation) {
|
||||
DefaultEvaluation defaultEvaluation = DefaultEvaluation.class.cast(evaluation);
|
||||
Map<Policy, Map<Object, Decision.Effect>> decisionCache = defaultEvaluation.getDecisionCache();
|
||||
Policy policy = defaultEvaluation.getParentPolicy();
|
||||
Map<Object, Decision.Effect> decisions = decisionCache.computeIfAbsent(policy, p -> new HashMap<>());
|
||||
ResourcePermission permission = evaluation.getPermission();
|
||||
|
||||
for (Scope scope : permission.getScopes()) {
|
||||
Decision.Effect effect = decisions.get(scope);
|
||||
|
||||
if (effect != null) {
|
||||
defaultEvaluation.setEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
Decision.Effect decision = defaultEvaluation.getEffect();
|
||||
|
||||
if (decision == null) {
|
||||
super.evaluate(evaluation);
|
||||
|
||||
for (Scope scope : policy.getScopes()) {
|
||||
decisions.put(scope, defaultEvaluation.getEffect());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ import org.keycloak.representations.AccessToken.Authorization;
|
|||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -113,7 +113,7 @@ public class AuthorizationContext {
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(authorization.getPermissions());
|
||||
return Collections.unmodifiableList(new ArrayList<>(authorization.getPermissions()));
|
||||
}
|
||||
|
||||
public boolean isGranted() {
|
||||
|
|
|
@ -22,9 +22,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -86,13 +86,13 @@ public class AccessToken extends IDToken {
|
|||
public static class Authorization implements Serializable {
|
||||
|
||||
@JsonProperty("permissions")
|
||||
private List<Permission> permissions;
|
||||
private Collection<Permission> permissions;
|
||||
|
||||
public List<Permission> getPermissions() {
|
||||
public Collection<Permission> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(List<Permission> permissions) {
|
||||
public void setPermissions(Collection<Permission> permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,6 +186,7 @@ public class AuthorizationRequest {
|
|||
|
||||
private Boolean includeResourceName;
|
||||
private Integer limit;
|
||||
private String responseMode;
|
||||
|
||||
public Boolean getIncludeResourceName() {
|
||||
if (includeResourceName == null) {
|
||||
|
@ -205,5 +206,13 @@ public class AuthorizationRequest {
|
|||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public void setResponseMode(String responseMode) {
|
||||
this.responseMode = responseMode;
|
||||
}
|
||||
|
||||
public String getResponseMode() {
|
||||
return responseMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,9 @@ public class Permission {
|
|||
}
|
||||
|
||||
public String getResourceId() {
|
||||
if (resourceId == null || "".equals(resourceId.trim())) {
|
||||
return null;
|
||||
}
|
||||
return this.resourceId;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.Set;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class StoreFactoryCacheManager extends CacheManager {
|
||||
private static final Logger logger = Logger.getLogger(RealmCacheManager.class);
|
||||
private static final Logger logger = Logger.getLogger(StoreFactoryCacheManager.class);
|
||||
|
||||
public StoreFactoryCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
|
||||
super(cache, revisions);
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -756,8 +757,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
}
|
||||
|
||||
return Arrays.asList(policy);
|
||||
},
|
||||
(revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
}, (revision, policies) -> new PolicyListQuery(revision, cacheKey, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
|
@ -780,40 +780,62 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
public List<Policy> findByResource(String resourceId, String resourceServerId) {
|
||||
String cacheKey = getPolicyByResource(resourceId, resourceServerId);
|
||||
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
|
||||
String cacheKey = getPolicyByResource(resourceId, resourceServerId);
|
||||
cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResource(resourceId, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceId, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
|
||||
String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
|
||||
return cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId);
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) {
|
||||
String cacheKey = getPolicyByResourceType(resourceType, resourceServerId);
|
||||
cacheQuery(cacheKey, PolicyResourceListQuery.class, () -> getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId),
|
||||
(revision, policies) -> new PolicyResourceListQuery(revision, cacheKey, resourceType, policies.stream().map(policy -> policy.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
|
||||
if (scopeIds == null) return null;
|
||||
List<Policy> result = new ArrayList<>();
|
||||
Set<Policy> result = new HashSet<>();
|
||||
|
||||
for (String id : scopeIds) {
|
||||
String cacheKey = getPolicyByScope(id, resourceServerId);
|
||||
result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
|
||||
result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null));
|
||||
}
|
||||
|
||||
return result;
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
|
||||
if (scopeIds == null) return null;
|
||||
List<Policy> result = new ArrayList<>();
|
||||
Set<Policy> result = new HashSet<>();
|
||||
|
||||
for (String id : scopeIds) {
|
||||
String cacheKey = getPolicyByResourceScope(id, resourceId, resourceServerId);
|
||||
result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId));
|
||||
result.addAll(cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, null));
|
||||
}
|
||||
|
||||
return result;
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
|
||||
for (String id : scopeIds) {
|
||||
String cacheKey = getPolicyByResourceScope(id, resourceId, resourceServerId);
|
||||
cacheQuery(cacheKey, PolicyScopeListQuery.class, () -> getPolicyStoreDelegate().findByScopeIds(Arrays.asList(id), resourceId, resourceServerId), (revision, resources) -> new PolicyScopeListQuery(revision, cacheKey, id, resources.stream().map(resource -> resource.getId()).collect(Collectors.toSet()), resourceServerId), resourceServerId, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -826,7 +848,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
|
||||
}
|
||||
|
||||
private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId) {
|
||||
private <R, Q extends PolicyQuery> List<R> cacheQuery(String cacheKey, Class<Q> queryType, Supplier<List<R>> resultSupplier, BiFunction<Long, List<R>, Q> querySupplier, String resourceServerId, Consumer<R> consumer) {
|
||||
Q query = cache.get(cacheKey, queryType);
|
||||
if (query != null) {
|
||||
logger.tracev("cache hit for key: {0}", cacheKey);
|
||||
|
@ -838,11 +860,34 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
|
|||
if (invalidations.contains(cacheKey)) return model;
|
||||
query = querySupplier.apply(loaded, model);
|
||||
cache.addRevisioned(query, startupRevision);
|
||||
if (consumer != null) {
|
||||
for (R policy: model) {
|
||||
consumer.accept(policy);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
} else if (query.isInvalid(invalidations)) {
|
||||
return resultSupplier.get();
|
||||
List<R> policies = resultSupplier.get();
|
||||
|
||||
if (consumer != null) {
|
||||
for (R policy : policies) {
|
||||
consumer.accept(policy);
|
||||
}
|
||||
}
|
||||
|
||||
return policies;
|
||||
} else {
|
||||
return query.getPolicies().stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
|
||||
Set<String> policies = query.getPolicies();
|
||||
|
||||
if (consumer != null) {
|
||||
for (String id : policies) {
|
||||
consumer.accept((R) findById(id, resourceServerId));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return policies.stream().map(resourceId -> (R) findById(resourceId, resourceServerId)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.FlushModeType;
|
||||
|
@ -188,40 +189,52 @@ public class JPAPolicyStore implements PolicyStore {
|
|||
|
||||
@Override
|
||||
public List<Policy> findByResource(final String resourceId, String resourceServerId) {
|
||||
List<Policy> result = new LinkedList<>();
|
||||
|
||||
findByResource(resourceId, resourceServerId, result::add);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResource", String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("resourceId", resourceId);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<Policy> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
|
||||
if (Objects.nonNull(policy)) {
|
||||
list.add(policy);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
|
||||
|
||||
query.getResultList().stream()
|
||||
.map(id -> policyStore.findById(id, resourceServerId))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(consumer::accept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByResourceType(final String resourceType, String resourceServerId) {
|
||||
List<Policy> result = new LinkedList<>();
|
||||
|
||||
findByResourceType(resourceType, resourceServerId, result::add);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResourceType(String resourceType, String resourceServerId, Consumer<Policy> consumer) {
|
||||
TypedQuery<String> query = entityManager.createNamedQuery("findPolicyIdByResourceType", String.class);
|
||||
|
||||
query.setFlushMode(FlushModeType.COMMIT);
|
||||
query.setParameter("type", resourceType);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<Policy> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
|
||||
if (Objects.nonNull(policy)) {
|
||||
list.add(policy);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
|
||||
|
||||
query.getResultList().stream()
|
||||
.map(id -> policyStore.findById(id, resourceServerId))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(consumer::accept);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -250,10 +263,15 @@ public class JPAPolicyStore implements PolicyStore {
|
|||
|
||||
@Override
|
||||
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId) {
|
||||
if (scopeIds==null || scopeIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
List<Policy> result = new LinkedList<>();
|
||||
|
||||
findByScopeIds(scopeIds, resourceId, resourceServerId, result::add);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer) {
|
||||
// Use separate subquery to handle DB2 and MSSSQL
|
||||
TypedQuery<String> query;
|
||||
|
||||
|
@ -268,15 +286,12 @@ public class JPAPolicyStore implements PolicyStore {
|
|||
query.setParameter("scopeIds", scopeIds);
|
||||
query.setParameter("serverId", resourceServerId);
|
||||
|
||||
List<String> result = query.getResultList();
|
||||
List<Policy> list = new LinkedList<>();
|
||||
for (String id : result) {
|
||||
Policy policy = provider.getStoreFactory().getPolicyStore().findById(id, resourceServerId);
|
||||
if (Objects.nonNull(policy)) {
|
||||
list.add(policy);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
PolicyStore policyStore = provider.getStoreFactory().getPolicyStore();
|
||||
|
||||
query.getResultList().stream()
|
||||
.map(id -> policyStore.findById(id, resourceServerId))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(consumer::accept);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
|
@ -30,7 +31,7 @@ import org.keycloak.authorization.model.Resource;
|
|||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.evaluator.Evaluators;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
|
||||
import org.keycloak.authorization.store.PermissionTicketStore;
|
||||
|
@ -74,18 +75,18 @@ import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentati
|
|||
*/
|
||||
public final class AuthorizationProvider implements Provider {
|
||||
|
||||
private final DefaultPolicyEvaluator policyEvaluator;
|
||||
private final PolicyEvaluator policyEvaluator;
|
||||
private StoreFactory storeFactory;
|
||||
private StoreFactory storeFactoryDelegate;
|
||||
private final Map<String, PolicyProviderFactory> policyProviderFactories;
|
||||
private final KeycloakSession keycloakSession;
|
||||
private final RealmModel realm;
|
||||
|
||||
public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map<String, PolicyProviderFactory> policyProviderFactories) {
|
||||
public AuthorizationProvider(KeycloakSession session, RealmModel realm, Map<String, PolicyProviderFactory> policyProviderFactories, PolicyEvaluator policyEvaluator) {
|
||||
this.keycloakSession = session;
|
||||
this.realm = realm;
|
||||
this.policyProviderFactories = policyProviderFactories;
|
||||
this.policyEvaluator = new DefaultPolicyEvaluator(this);
|
||||
this.policyEvaluator = policyEvaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,7 +96,7 @@ public final class AuthorizationProvider implements Provider {
|
|||
* @return a {@link Evaluators} instance
|
||||
*/
|
||||
public Evaluators evaluators() {
|
||||
return new Evaluators(policyEvaluator);
|
||||
return new Evaluators(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,6 +170,10 @@ public final class AuthorizationProvider implements Provider {
|
|||
return realm;
|
||||
}
|
||||
|
||||
public PolicyEvaluator getPolicyEvaluator() {
|
||||
return policyEvaluator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
@ -380,6 +385,11 @@ public final class AuthorizationProvider implements Provider {
|
|||
return policyStore.findByResource(resourceId, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer) {
|
||||
policyStore.findByResource(resourceId, resourceServerId, consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
|
||||
return policyStore.findByResourceType(resourceType, resourceServerId);
|
||||
|
@ -395,6 +405,11 @@ public final class AuthorizationProvider implements Provider {
|
|||
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
|
||||
public List<Policy> findByType(String type, String resourceServerId) {
|
||||
return policyStore.findByType(type, resourceServerId);
|
||||
|
@ -404,6 +419,11 @@ public final class AuthorizationProvider implements Provider {
|
|||
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
|
||||
return policyStore.findDependentPolicies(id, resourceServerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findByResourceType(String type, String id, Consumer<Policy> policyConsumer) {
|
||||
policyStore.findByResourceType(type, id, policyConsumer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.keycloak.authorization;
|
||||
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.Evaluation;
|
||||
|
||||
/**
|
||||
|
@ -38,4 +39,7 @@ public interface Decision<D extends Evaluation> {
|
|||
|
||||
default void onComplete() {
|
||||
}
|
||||
|
||||
default void onComplete(ResourcePermission permission) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
|
||||
package org.keycloak.authorization.permission.evaluator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A factory for the different {@link PermissionEvaluator} implementations.
|
||||
*
|
||||
|
@ -31,17 +31,13 @@ import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
|||
*/
|
||||
public final class Evaluators {
|
||||
|
||||
private final DefaultPolicyEvaluator policyEvaluator;
|
||||
private final AuthorizationProvider authorizationProvider;
|
||||
|
||||
public Evaluators(DefaultPolicyEvaluator policyEvaluator) {
|
||||
this.policyEvaluator = policyEvaluator;
|
||||
public Evaluators(AuthorizationProvider authorizationProvider) {
|
||||
this.authorizationProvider = authorizationProvider;
|
||||
}
|
||||
|
||||
public PermissionEvaluator from(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
|
||||
return schedule(permissions, evaluationContext);
|
||||
}
|
||||
|
||||
public PermissionEvaluator schedule(List<ResourcePermission> permissions, EvaluationContext evaluationContext) {
|
||||
return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, this.policyEvaluator);
|
||||
public PermissionEvaluator from(Collection<ResourcePermission> permissions, EvaluationContext evaluationContext) {
|
||||
return new IterablePermissionEvaluator(permissions.iterator(), evaluationContext, authorizationProvider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,16 +17,21 @@
|
|||
*/
|
||||
package org.keycloak.authorization.permission.evaluator;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -36,19 +41,24 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
|
|||
private final Iterator<ResourcePermission> permissions;
|
||||
private final EvaluationContext executionContext;
|
||||
private final PolicyEvaluator policyEvaluator;
|
||||
private final AuthorizationProvider authorizationProvider;
|
||||
|
||||
IterablePermissionEvaluator(Iterator<ResourcePermission> permissions, EvaluationContext executionContext, PolicyEvaluator policyEvaluator) {
|
||||
IterablePermissionEvaluator(Iterator<ResourcePermission> permissions, EvaluationContext executionContext, AuthorizationProvider authorizationProvider) {
|
||||
this.permissions = permissions;
|
||||
this.executionContext = executionContext;
|
||||
this.policyEvaluator = policyEvaluator;
|
||||
this.authorizationProvider = authorizationProvider;
|
||||
this.policyEvaluator = authorizationProvider.getPolicyEvaluator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Decision evaluate(Decision decision) {
|
||||
try {
|
||||
Map<Policy, Map<Object, Decision.Effect>> decisionCache = new HashMap<>();
|
||||
|
||||
while (this.permissions.hasNext()) {
|
||||
this.policyEvaluator.evaluate(this.permissions.next(), this.executionContext, decision);
|
||||
this.policyEvaluator.evaluate(this.permissions.next(), authorizationProvider, executionContext, decision, decisionCache);
|
||||
}
|
||||
|
||||
decision.onComplete();
|
||||
} catch (Throwable cause) {
|
||||
decision.onError(cause);
|
||||
|
@ -57,21 +67,11 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<Result> evaluate() {
|
||||
AtomicReference<List<Result>> result = new AtomicReference<>();
|
||||
public Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request) {
|
||||
DecisionPermissionCollector decision = new DecisionPermissionCollector(authorizationProvider, resourceServer, request);
|
||||
|
||||
evaluate(new DecisionResultCollector() {
|
||||
@Override
|
||||
public void onError(Throwable cause) {
|
||||
throw new RuntimeException("Failed to evaluate permissions", cause);
|
||||
}
|
||||
evaluate(decision);
|
||||
|
||||
@Override
|
||||
protected void onComplete(List<Result> results) {
|
||||
result.set(results);
|
||||
}
|
||||
});
|
||||
|
||||
return result.get();
|
||||
return decision.results();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@
|
|||
*/
|
||||
package org.keycloak.authorization.permission.evaluator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* An {@link PermissionEvaluator} represents a source of {@link org.keycloak.authorization.permission.ResourcePermission}, responsible for emitting these permissions
|
||||
|
@ -31,5 +33,5 @@ import org.keycloak.authorization.policy.evaluation.Result;
|
|||
public interface PermissionEvaluator {
|
||||
|
||||
<D extends Decision> D evaluate(D decision);
|
||||
List<Result> evaluate();
|
||||
Collection<Permission> evaluate(ResourceServer resourceServer, AuthorizationRequest request);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -18,99 +18,14 @@
|
|||
|
||||
package org.keycloak.authorization.policy.evaluation;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.representations.idm.authorization.DecisionStrategy;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class DecisionResultCollector implements Decision<DefaultEvaluation> {
|
||||
public abstract class DecisionResultCollector extends AbstractDecisionCollector {
|
||||
|
||||
private Map<ResourcePermission, Result> results = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onDecision(DefaultEvaluation evaluation) {
|
||||
Policy parentPolicy = evaluation.getParentPolicy();
|
||||
|
||||
if (parentPolicy != null) {
|
||||
results.computeIfAbsent(evaluation.getPermission(), Result::new).policy(parentPolicy).policy(evaluation.getPolicy()).setStatus(evaluation.getEffect());
|
||||
} else {
|
||||
results.computeIfAbsent(evaluation.getPermission(), Result::new).setStatus(evaluation.getEffect());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
for (Result result : results.values()) {
|
||||
int deniedCount = result.getResults().size();
|
||||
|
||||
for (Result.PolicyResult policyResult : result.getResults()) {
|
||||
if (isGranted(policyResult)) {
|
||||
policyResult.setStatus(Effect.PERMIT);
|
||||
deniedCount--;
|
||||
} else {
|
||||
policyResult.setStatus(Effect.DENY);
|
||||
}
|
||||
}
|
||||
|
||||
if (deniedCount == 0) {
|
||||
onGrant(result);
|
||||
} else {
|
||||
onDeny(result);
|
||||
}
|
||||
}
|
||||
|
||||
onComplete(results.values().stream().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
protected void onGrant(Result result) {
|
||||
result.setStatus(Effect.PERMIT);
|
||||
}
|
||||
|
||||
protected abstract void onComplete(List<Result> results);
|
||||
|
||||
protected void onDeny(Result result) {
|
||||
result.setStatus(Effect.DENY);
|
||||
}
|
||||
|
||||
private boolean isGranted(Result.PolicyResult policyResult) {
|
||||
List<Result.PolicyResult> values = policyResult.getAssociatedPolicies();
|
||||
|
||||
int grantCount = 0;
|
||||
int denyCount = policyResult.getPolicy().getAssociatedPolicies().size();
|
||||
|
||||
for (Result.PolicyResult decision : values) {
|
||||
if (decision.getStatus().equals(Effect.PERMIT)) {
|
||||
grantCount++;
|
||||
denyCount--;
|
||||
}
|
||||
}
|
||||
|
||||
Policy policy = policyResult.getPolicy();
|
||||
DecisionStrategy decisionStrategy = policy.getDecisionStrategy();
|
||||
|
||||
if (decisionStrategy == null) {
|
||||
decisionStrategy = DecisionStrategy.UNANIMOUS;
|
||||
}
|
||||
|
||||
if (DecisionStrategy.AFFIRMATIVE.equals(decisionStrategy) && grantCount > 0) {
|
||||
return true;
|
||||
} else if (DecisionStrategy.UNANIMOUS.equals(decisionStrategy) && denyCount == 0) {
|
||||
return true;
|
||||
} else if (DecisionStrategy.CONSENSUS.equals(decisionStrategy)) {
|
||||
if (grantCount > denyCount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.keycloak.authorization.policy.evaluation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -51,25 +53,30 @@ public class DefaultEvaluation implements Evaluation {
|
|||
private Policy policy;
|
||||
private final Policy parentPolicy;
|
||||
private final AuthorizationProvider authorizationProvider;
|
||||
private Map<Policy, Map<Object, Effect>> decisionCache;
|
||||
private final Realm realm;
|
||||
private Effect effect;
|
||||
|
||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||
this.permission = permission;
|
||||
this.executionContext = executionContext;
|
||||
this.parentPolicy = parentPolicy;
|
||||
this.decision = decision;
|
||||
this.authorizationProvider = authorizationProvider;
|
||||
this.realm = createRealm();
|
||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Decision decision, AuthorizationProvider authorizationProvider, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
|
||||
this(permission, executionContext, parentPolicy, null, decision, authorizationProvider, decisionCache);
|
||||
}
|
||||
|
||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||
this(permission, executionContext, parentPolicy, policy, decision, authorizationProvider, null);
|
||||
}
|
||||
|
||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AuthorizationProvider authorizationProvider) {
|
||||
this(permission, executionContext, null, null, decision, authorizationProvider, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public DefaultEvaluation(ResourcePermission permission, EvaluationContext executionContext, Policy parentPolicy, Policy policy, Decision decision, AuthorizationProvider authorizationProvider, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
|
||||
this.permission = permission;
|
||||
this.executionContext = executionContext;
|
||||
this.parentPolicy = parentPolicy;
|
||||
this.policy = policy;
|
||||
this.decision = decision;
|
||||
this.authorizationProvider = authorizationProvider;
|
||||
this.decisionCache = decisionCache;
|
||||
this.realm = createRealm();
|
||||
}
|
||||
|
||||
|
@ -131,6 +138,10 @@ public class DefaultEvaluation implements Evaluation {
|
|||
return effect;
|
||||
}
|
||||
|
||||
public Map<Policy, Map<Object, Effect>> getDecisionCache() {
|
||||
return decisionCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void denyIfNoEffect() {
|
||||
if (this.effect == null) {
|
||||
|
@ -265,4 +276,12 @@ public class DefaultEvaluation implements Evaluation {
|
|||
public void setPolicy(Policy policy) {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
public void setEffect(Effect effect) {
|
||||
if (Effect.PERMIT.equals(effect)) {
|
||||
grant();
|
||||
} else {
|
||||
deny();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,10 @@
|
|||
package org.keycloak.authorization.policy.evaluation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
|
@ -45,163 +43,73 @@ import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
|
|||
*/
|
||||
public class DefaultPolicyEvaluator implements PolicyEvaluator {
|
||||
|
||||
private final AuthorizationProvider authorization;
|
||||
private final StoreFactory storeFactory;
|
||||
private final PolicyStore policyStore;
|
||||
private final ResourceStore resourceStore;
|
||||
|
||||
public DefaultPolicyEvaluator(AuthorizationProvider authorization) {
|
||||
this.authorization = authorization;
|
||||
storeFactory = this.authorization.getStoreFactory();
|
||||
policyStore = storeFactory.getPolicyStore();
|
||||
resourceStore = storeFactory.getResourceStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision) {
|
||||
public void evaluate(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
|
||||
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
|
||||
PolicyStore policyStore = storeFactory.getPolicyStore();
|
||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||
|
||||
ResourceServer resourceServer = permission.getResourceServer();
|
||||
PolicyEnforcementMode enforcementMode = resourceServer.getPolicyEnforcementMode();
|
||||
|
||||
if (PolicyEnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
createEvaluation(permission, executionContext, decision, null).grant();
|
||||
DefaultEvaluation evaluation = new DefaultEvaluation(permission, executionContext, decision, authorizationProvider);
|
||||
|
||||
evaluation.grant();
|
||||
|
||||
decision.onComplete(permission);
|
||||
return;
|
||||
}
|
||||
|
||||
AtomicBoolean verified = new AtomicBoolean(false);
|
||||
Consumer<Policy> consumer = createDecisionConsumer(permission, executionContext, decision, verified);
|
||||
Set<Policy> verified = new HashSet<>();
|
||||
Consumer<Policy> policyConsumer = createPolicyEvaluator(permission, authorizationProvider, executionContext, decision, verified, decisionCache);
|
||||
Resource resource = permission.getResource();
|
||||
List<Scope> scopes = permission.getScopes();
|
||||
|
||||
if (resource != null) {
|
||||
evaluatePolicies(() -> policyStore.findByResource(resource.getId(), resourceServer.getId()), consumer);
|
||||
policyStore.findByResource(resource.getId(), resourceServer.getId(), policyConsumer);
|
||||
|
||||
if (resource.getType() != null) {
|
||||
evaluatePolicies(() -> {
|
||||
List<Policy> policies = policyStore.findByResourceType(resource.getType(), resourceServer.getId());
|
||||
policyStore.findByResourceType(resource.getType(), resourceServer.getId(), policyConsumer);
|
||||
|
||||
if (!resource.getOwner().equals(resourceServer.getId())) {
|
||||
for (Resource typedResource : resourceStore.findByType(resource.getType(), resourceServer.getId())) {
|
||||
policies.addAll(policyStore.findByResource(typedResource.getId(), resourceServer.getId()));
|
||||
policyStore.findByResource(typedResource.getId(), resourceServer.getId(), policyConsumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return policies;
|
||||
}, consumer);
|
||||
}
|
||||
}
|
||||
List<Scope> scopes = permission.getScopes();
|
||||
|
||||
if (!scopes.isEmpty()) {
|
||||
evaluatePolicies(() -> policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), null, resourceServer.getId()), consumer);
|
||||
policyStore.findByScopeIds(scopes.stream().map(Scope::getId).collect(Collectors.toList()), null, resourceServer.getId(), policyConsumer);
|
||||
}
|
||||
|
||||
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode) && !verified.get()) {
|
||||
createEvaluation(permission, executionContext, decision, null).grant();
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluatePolicies(Supplier<List<Policy>> supplier, Consumer<Policy> consumer) {
|
||||
List<Policy> policies = supplier.get();
|
||||
|
||||
if (!policies.isEmpty()) {
|
||||
policies.forEach(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<Policy> createDecisionConsumer(ResourcePermission permission, EvaluationContext executionContext, Decision decision, AtomicBoolean verified) {
|
||||
return (parentPolicy) -> {
|
||||
if (!hasRequestedScopes(permission, parentPolicy)) {
|
||||
if (!verified.isEmpty()) {
|
||||
decision.onComplete(permission);
|
||||
return;
|
||||
}
|
||||
|
||||
PolicyProvider policyProvider = authorization.getProvider(parentPolicy.getType());
|
||||
if (PolicyEnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
DefaultEvaluation evaluation = new DefaultEvaluation(permission, executionContext, decision, authorizationProvider);
|
||||
evaluation.grant();
|
||||
decision.onComplete(permission);
|
||||
}
|
||||
}
|
||||
|
||||
private Consumer<Policy> createPolicyEvaluator(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Set<Policy> verified, Map<Policy, Map<Object, Decision.Effect>> decisionCache) {
|
||||
return parentPolicy -> {
|
||||
if (!verified.add(parentPolicy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PolicyProvider policyProvider = authorizationProvider.getProvider(parentPolicy.getType());
|
||||
|
||||
if (policyProvider == null) {
|
||||
throw new RuntimeException("Unknown parentPolicy provider for type [" + parentPolicy.getType() + "].");
|
||||
}
|
||||
|
||||
DefaultEvaluation evaluation = createEvaluation(permission, executionContext, decision, parentPolicy);
|
||||
|
||||
policyProvider.evaluate(evaluation);
|
||||
|
||||
verified.compareAndSet(false, true);
|
||||
policyProvider.evaluate(new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorizationProvider, decisionCache));
|
||||
};
|
||||
}
|
||||
|
||||
private DefaultEvaluation createEvaluation(ResourcePermission permission, EvaluationContext executionContext, Decision decision, Policy parentPolicy) {
|
||||
return new DefaultEvaluation(permission, executionContext, parentPolicy, decision, authorization);
|
||||
}
|
||||
|
||||
private boolean hasRequestedScopes(final ResourcePermission permission, final Policy policy) {
|
||||
if (permission.getScopes().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Resource resourcePermission = permission.getResource();
|
||||
Set<Resource> policyResources = policy.getResources();
|
||||
|
||||
if (resourcePermission != null && !policyResources.isEmpty()) {
|
||||
if (!policyResources.stream().filter(resource -> {
|
||||
Iterator<Resource> policyResourceType = policy.getResources().iterator();
|
||||
Resource policyResource = policyResourceType.hasNext() ? policyResourceType.next() : null;
|
||||
return resource.getId().equals(resourcePermission.getId()) || (policyResourceType != null && policyResource.getType() != null && policyResource.getType().equals(resourcePermission.getType()));
|
||||
}).findFirst().isPresent()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Set<Scope> scopes = new HashSet<>(policy.getScopes());
|
||||
|
||||
if (scopes.isEmpty()) {
|
||||
Set<Resource> resources = new HashSet<>();
|
||||
|
||||
resources.addAll(policyResources);
|
||||
|
||||
for (Resource resource : resources) {
|
||||
scopes.addAll(resource.getScopes());
|
||||
}
|
||||
|
||||
if (!resources.isEmpty() && scopes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scopes.isEmpty()) {
|
||||
Resource resource = permission.getResource();
|
||||
String type = resource.getType();
|
||||
|
||||
if (type != null) {
|
||||
List<Resource> resourcesByType = resourceStore.findByType(type, resource.getResourceServer().getId());
|
||||
|
||||
for (Resource resourceType : resourcesByType) {
|
||||
if (resourceType.getOwner().equals(resource.getResourceServer().getId())) {
|
||||
resources.add(resourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Resource resource : resources) {
|
||||
scopes.addAll(resource.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
for (Scope givenScope : scopes) {
|
||||
for (Scope scope : permission.getScopes()) {
|
||||
if (givenScope.getId().equals(scope.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (policyResources.isEmpty() && scopes.isEmpty()) {
|
||||
String defaultResourceType = policy.getConfig().get("defaultResourceType");
|
||||
|
||||
if (defaultResourceType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultResourceType.equals(permission.getResource().getType());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.authorization.policy.evaluation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -41,16 +42,16 @@ import org.keycloak.representations.idm.authorization.PermissionTicketToken;
|
|||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PermissionTicketAwareDecisionResultCollector extends DecisionResultCollector {
|
||||
public class PermissionTicketAwareDecisionResultCollector extends DecisionPermissionCollector {
|
||||
|
||||
private final AuthorizationRequest request;
|
||||
private PermissionTicketToken ticket;
|
||||
private final Identity identity;
|
||||
private ResourceServer resourceServer;
|
||||
private final AuthorizationProvider authorization;
|
||||
private List<Result> results;
|
||||
|
||||
public PermissionTicketAwareDecisionResultCollector(AuthorizationRequest request, PermissionTicketToken ticket, Identity identity, ResourceServer resourceServer, AuthorizationProvider authorization) {
|
||||
super(authorization, resourceServer, request);
|
||||
this.request = request;
|
||||
this.ticket = ticket;
|
||||
this.identity = identity;
|
||||
|
@ -168,13 +169,4 @@ public class PermissionTicketAwareDecisionResultCollector extends DecisionResult
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onComplete(List<Result> results) {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public List<Result> results() {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
|
||||
package org.keycloak.authorization.policy.evaluation;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>A {@link PolicyEvaluator} evaluates authorization policies based on a given {@link ResourcePermission}, sending
|
||||
* the results to a {@link Decision} point through the methods defined in that interface.
|
||||
|
@ -34,5 +38,5 @@ public interface PolicyEvaluator {
|
|||
*
|
||||
* @param decision a {@link Decision} point to where notifications events will be delivered during the evaluation
|
||||
*/
|
||||
void evaluate(ResourcePermission permission, EvaluationContext executionContext, Decision decision);
|
||||
void evaluate(ResourcePermission permission, AuthorizationProvider authorizationProvider, EvaluationContext executionContext, Decision decision, Map<Policy, Map<Object, Decision.Effect>> decisionCache);
|
||||
}
|
||||
|
|
|
@ -22,8 +22,9 @@ import org.keycloak.authorization.Decision.Effect;
|
|||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
|
@ -31,33 +32,29 @@ import java.util.List;
|
|||
public class Result {
|
||||
|
||||
private final ResourcePermission permission;
|
||||
private List<PolicyResult> results = new ArrayList<>();
|
||||
private Effect status;
|
||||
private final Map<String, PolicyResult> results = new HashMap<>();
|
||||
private final Evaluation evaluation;
|
||||
private Effect status = Effect.DENY;
|
||||
|
||||
public Result(ResourcePermission permission) {
|
||||
public Result(ResourcePermission permission, Evaluation evaluation) {
|
||||
this.permission = permission;
|
||||
this.evaluation = evaluation;
|
||||
}
|
||||
|
||||
public ResourcePermission getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public List<PolicyResult> getResults() {
|
||||
return results;
|
||||
public Collection<PolicyResult> getResults() {
|
||||
return results.values();
|
||||
}
|
||||
|
||||
public Evaluation getEvaluation() {
|
||||
return evaluation;
|
||||
}
|
||||
|
||||
public PolicyResult policy(Policy policy) {
|
||||
for (PolicyResult result : this.results) {
|
||||
if (result.getPolicy().equals(policy)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
PolicyResult policyResult = new PolicyResult(policy);
|
||||
|
||||
this.results.add(policyResult);
|
||||
|
||||
return policyResult;
|
||||
return results.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy));
|
||||
}
|
||||
|
||||
public void setStatus(final Effect status) {
|
||||
|
@ -71,50 +68,40 @@ public class Result {
|
|||
public static class PolicyResult {
|
||||
|
||||
private final Policy policy;
|
||||
private List<PolicyResult> associatedPolicies = new ArrayList<>();
|
||||
private Effect status;
|
||||
private final Map<String, PolicyResult> associatedPolicies = new HashMap<>();
|
||||
private Effect effect = Effect.DENY;
|
||||
|
||||
public PolicyResult(Policy policy, Effect status) {
|
||||
this.policy = policy;
|
||||
this.effect = status;
|
||||
}
|
||||
|
||||
public PolicyResult(Policy policy) {
|
||||
this.policy = policy;
|
||||
this(policy, Effect.DENY);
|
||||
}
|
||||
|
||||
public PolicyResult status(Effect status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
public PolicyResult policy(Policy policy, Effect effect) {
|
||||
PolicyResult result = associatedPolicies.computeIfAbsent(policy.getId(), id -> new PolicyResult(policy, effect));
|
||||
|
||||
public PolicyResult policy(Policy policy) {
|
||||
return getPolicy(policy, this.associatedPolicies);
|
||||
}
|
||||
result.setEffect(effect);
|
||||
|
||||
private PolicyResult getPolicy(Policy policy, List<PolicyResult> results) {
|
||||
for (PolicyResult result : results) {
|
||||
if (result.getPolicy().equals(policy)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
PolicyResult policyResult = new PolicyResult(policy);
|
||||
|
||||
results.add(policyResult);
|
||||
|
||||
return policyResult;
|
||||
}
|
||||
|
||||
public Policy getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
|
||||
public List<PolicyResult> getAssociatedPolicies() {
|
||||
return associatedPolicies;
|
||||
public Collection<PolicyResult> getAssociatedPolicies() {
|
||||
return associatedPolicies.values();
|
||||
}
|
||||
|
||||
public Effect getStatus() {
|
||||
return status;
|
||||
public Effect getEffect() {
|
||||
return effect;
|
||||
}
|
||||
|
||||
public void setStatus(final Effect status) {
|
||||
this.status = status;
|
||||
public void setEffect(final Effect status) {
|
||||
this.effect = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.authorization.store;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
|
@ -93,6 +94,8 @@ public interface PolicyStore {
|
|||
*/
|
||||
List<Policy> findByResource(String resourceId, String resourceServerId);
|
||||
|
||||
void findByResource(String resourceId, String resourceServerId, Consumer<Policy> consumer);
|
||||
|
||||
/**
|
||||
* Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given <code>type</code>.
|
||||
*
|
||||
|
@ -121,6 +124,8 @@ public interface PolicyStore {
|
|||
*/
|
||||
List<Policy> findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId);
|
||||
|
||||
void findByScopeIds(List<String> scopeIds, String resourceId, String resourceServerId, Consumer<Policy> consumer);
|
||||
|
||||
/**
|
||||
* Returns a list of {@link Policy} with the given <code>type</code>.
|
||||
*
|
||||
|
@ -138,4 +143,6 @@ public interface PolicyStore {
|
|||
* @return a list of policies that depends on the a policy with the given identifier
|
||||
*/
|
||||
List<Policy> findDependentPolicies(String id, String resourceServerId);
|
||||
|
||||
void findByResourceType(String type, String id, Consumer<Policy> policyConsumer);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.keycloak.scripting;
|
||||
|
||||
import javax.script.ScriptContext;
|
||||
|
||||
import org.keycloak.models.ScriptModel;
|
||||
|
||||
/**
|
||||
|
@ -11,4 +13,5 @@ public interface EvaluatableScriptAdapter {
|
|||
ScriptModel getScriptModel();
|
||||
|
||||
Object eval(ScriptBindingsConfigurer bindingsConfigurer) throws ScriptExecutionException;
|
||||
Object eval(ScriptContext context) throws ScriptExecutionException;
|
||||
}
|
||||
|
|
|
@ -23,13 +23,13 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authorization.policy.evaluation.DefaultPolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProvider;
|
||||
import org.keycloak.authorization.policy.provider.PolicyProviderFactory;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
|
@ -38,6 +38,7 @@ import org.keycloak.provider.ProviderFactory;
|
|||
public class DefaultAuthorizationProviderFactory implements AuthorizationProviderFactory {
|
||||
|
||||
private Map<String, PolicyProviderFactory> policyProviderFactories;
|
||||
private PolicyEvaluator policyEvaluator = new DefaultPolicyEvaluator();
|
||||
|
||||
@Override
|
||||
public AuthorizationProvider create(KeycloakSession session) {
|
||||
|
@ -65,7 +66,7 @@ public class DefaultAuthorizationProviderFactory implements AuthorizationProvide
|
|||
|
||||
@Override
|
||||
public AuthorizationProvider create(KeycloakSession session, RealmModel realm) {
|
||||
return new AuthorizationProvider(session, realm, policyProviderFactories);
|
||||
return new AuthorizationProvider(session, realm, policyProviderFactories, policyEvaluator);
|
||||
}
|
||||
|
||||
private Map<String, PolicyProviderFactory> configurePolicyProviderFactories(KeycloakSessionFactory keycloakSessionFactory) {
|
||||
|
|
|
@ -21,7 +21,6 @@ package org.keycloak.authorization.admin;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -37,6 +36,7 @@ import javax.ws.rs.Produces;
|
|||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder;
|
||||
|
@ -47,23 +47,24 @@ import org.keycloak.authorization.model.Resource;
|
|||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.store.ScopeStore;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.authorization.util.Permissions;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PolicyEvaluationRequest;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
|
@ -78,6 +79,8 @@ import org.keycloak.sessions.AuthenticationSessionModel;
|
|||
*/
|
||||
public class PolicyEvaluationService {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PolicyEvaluationService.class);
|
||||
|
||||
private final AuthorizationProvider authorization;
|
||||
private final AdminPermissionEvaluator auth;
|
||||
private final ResourceServer resourceServer;
|
||||
|
@ -117,14 +120,15 @@ public class PolicyEvaluationService {
|
|||
|
||||
return Response.ok(PolicyEvaluationResponseBuilder.build(evaluate(evaluationRequest, createEvaluationContext(evaluationRequest, identity), request), resourceServer, authorization, identity)).build();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error while evaluating permissions", e);
|
||||
throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "Error while evaluating permissions.", Status.INTERNAL_SERVER_ERROR);
|
||||
} finally {
|
||||
identity.close();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Result> evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
|
||||
return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate();
|
||||
private EvaluationDecisionCollector evaluate(PolicyEvaluationRequest evaluationRequest, EvaluationContext evaluationContext, AuthorizationRequest request) {
|
||||
return authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization, request), evaluationContext).evaluate(new EvaluationDecisionCollector(authorization, resourceServer, request));
|
||||
}
|
||||
|
||||
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
|
||||
|
@ -171,7 +175,7 @@ public class PolicyEvaluationService {
|
|||
|
||||
if (resource.getId() != null) {
|
||||
Resource resourceModel = storeFactory.getResourceStore().findById(resource.getId(), resourceServer.getId());
|
||||
return Arrays.asList(Permissions.createResourcePermissions(resourceModel, scopeNames, authorization, request)).stream();
|
||||
return new ArrayList<>(Arrays.asList(Permissions.createResourcePermissions(resourceModel, scopeNames, authorization, request))).stream();
|
||||
} else if (resource.getType() != null) {
|
||||
return storeFactory.getResourceStore().findByType(resource.getType(), resourceServer.getId()).stream().map(resource1 -> Permissions.createResourcePermissions(resource1, scopeNames, authorization, request));
|
||||
} else {
|
||||
|
@ -180,7 +184,7 @@ public class PolicyEvaluationService {
|
|||
List<ResourcePermission> collect = new ArrayList<>();
|
||||
|
||||
if (!scopes.isEmpty()) {
|
||||
collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, Arrays.asList(scope), resourceServer)).collect(Collectors.toList()));
|
||||
collect.addAll(scopes.stream().map(scope -> new ResourcePermission(null, new ArrayList<>(Arrays.asList(scope)), resourceServer)).collect(Collectors.toList()));
|
||||
} else {
|
||||
collect.addAll(Permissions.all(resourceServer, evaluationContext.getIdentity(), authorization, request));
|
||||
}
|
||||
|
@ -261,4 +265,31 @@ public class PolicyEvaluationService {
|
|||
|
||||
return new CloseableKeycloakIdentity(accessToken, keycloakSession, userSession);
|
||||
}
|
||||
|
||||
public class EvaluationDecisionCollector extends DecisionPermissionCollector {
|
||||
|
||||
public EvaluationDecisionCollector(AuthorizationProvider authorizationProvider, ResourceServer resourceServer, AuthorizationRequest request) {
|
||||
super(authorizationProvider, resourceServer, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isGranted(Result.PolicyResult policyResult) {
|
||||
if (super.isGranted(policyResult)) {
|
||||
policyResult.setEffect(Effect.PERMIT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void grantPermission(AuthorizationProvider authorizationProvider, List<Permission> permissions, ResourcePermission permission, Set<Scope> grantedScopes, ResourceServer resourceServer, AuthorizationRequest request, Result result) {
|
||||
result.setStatus(Effect.PERMIT);
|
||||
result.getPermission().getScopes().retainAll(grantedScopes);
|
||||
super.grantPermission(authorizationProvider, permissions, permission, grantedScopes, resourceServer, request, result);
|
||||
}
|
||||
|
||||
public Collection<Result> getResults() {
|
||||
return results.values();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,13 +18,13 @@ package org.keycloak.authorization.admin.representation;
|
|||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.admin.PolicyEvaluationService;
|
||||
import org.keycloak.authorization.common.KeycloakIdentity;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.util.Permissions;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -38,6 +38,7 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -52,13 +53,13 @@ import java.util.stream.Stream;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PolicyEvaluationResponseBuilder {
|
||||
public static PolicyEvaluationResponse build(List<Result> results, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) {
|
||||
public static PolicyEvaluationResponse build(PolicyEvaluationService.EvaluationDecisionCollector decision, ResourceServer resourceServer, AuthorizationProvider authorization, KeycloakIdentity identity) {
|
||||
PolicyEvaluationResponse response = new PolicyEvaluationResponse();
|
||||
List<PolicyEvaluationResponse.EvaluationResultRepresentation> resultsRep = new ArrayList<>();
|
||||
AccessToken accessToken = identity.getAccessToken();
|
||||
AccessToken.Authorization authorizationData = new AccessToken.Authorization();
|
||||
|
||||
authorizationData.setPermissions(Permissions.permits(results, null, authorization, resourceServer));
|
||||
authorizationData.setPermissions(decision.results());
|
||||
accessToken.setAuthorization(authorizationData);
|
||||
|
||||
ClientModel clientModel = authorization.getRealm().getClientById(resourceServer.getId());
|
||||
|
@ -69,6 +70,8 @@ public class PolicyEvaluationResponseBuilder {
|
|||
|
||||
response.setRpt(accessToken);
|
||||
|
||||
Collection<Result> results = decision.getResults();
|
||||
|
||||
if (results.stream().anyMatch(evaluationResult -> evaluationResult.getEffect().equals(Decision.Effect.DENY))) {
|
||||
response.setStatus(DecisionEffect.DENY);
|
||||
} else {
|
||||
|
@ -217,7 +220,7 @@ public class PolicyEvaluationResponseBuilder {
|
|||
|
||||
policyResultRep.setPolicy(representation);
|
||||
|
||||
if (result.getStatus() == Decision.Effect.DENY) {
|
||||
if (result.getEffect() == Decision.Effect.DENY) {
|
||||
policyResultRep.setStatus(DecisionEffect.DENY);
|
||||
policyResultRep.setScopes(representation.getScopes());
|
||||
} else {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.authorization.authorization;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -46,7 +47,6 @@ import org.keycloak.authorization.model.ResourceServer;
|
|||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.PermissionTicketAwareDecisionResultCollector;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.authorization.store.ScopeStore;
|
||||
|
@ -88,6 +88,9 @@ public class AuthorizationTokenService {
|
|||
public static final String CLAIM_TOKEN_FORMAT_ID_TOKEN = "http://openid.net/specs/openid-connect-core-1_0.html#IDToken";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AuthorizationTokenService.class);
|
||||
private static final String RESPONSE_MODE_DECISION = "decision";
|
||||
private static final String RESPONSE_MODE_PERMISSIONS = "permissions";
|
||||
private static final String RESPONSE_MODE_DECISION_RESULT = "result";
|
||||
private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
|
||||
|
||||
static {
|
||||
|
@ -125,28 +128,20 @@ public class AuthorizationTokenService {
|
|||
});
|
||||
}
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
private final EventBuilder event;
|
||||
private final HttpRequest httpRequest;
|
||||
private final AuthorizationProvider authorization;
|
||||
private final Cors cors;
|
||||
private static final AuthorizationTokenService INSTANCE = new AuthorizationTokenService();
|
||||
|
||||
public AuthorizationTokenService(AuthorizationProvider authorization, TokenManager tokenManager, EventBuilder event, HttpRequest httpRequest, Cors cors) {
|
||||
this.tokenManager = tokenManager;
|
||||
this.event = event;
|
||||
this.httpRequest = httpRequest;
|
||||
this.authorization = authorization;
|
||||
this.cors = cors;
|
||||
public static AuthorizationTokenService instance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public Response authorize(AuthorizationRequest request) {
|
||||
public Response authorize(KeycloakAuthorizationRequest request) {
|
||||
if (request == null) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Invalid authorization request.", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// it is not secure to allow public clients to push arbitrary claims because message can be tampered
|
||||
if (isPublicClientRequestingEntitlementWithClaims(request)) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_GRANT, "Public clients are not allowed to send claims", Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -154,35 +149,46 @@ public class AuthorizationTokenService {
|
|||
|
||||
request.setClaims(ticket.getClaims());
|
||||
|
||||
ResourceServer resourceServer = getResourceServer(ticket);
|
||||
ResourceServer resourceServer = getResourceServer(ticket, request);
|
||||
KeycloakEvaluationContext evaluationContext = createEvaluationContext(request);
|
||||
KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
|
||||
List<Result> evaluation;
|
||||
Collection<Permission> permissions;
|
||||
|
||||
if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
|
||||
evaluation = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
|
||||
} else if(!request.getPermissions().getPermissions().isEmpty()) {
|
||||
evaluation = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
|
||||
if (request.getTicket() != null) {
|
||||
permissions = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
|
||||
} else if (ticket.getPermissions().isEmpty() && request.getRpt() == null) {
|
||||
permissions = evaluateAllPermissions(request, resourceServer, evaluationContext, identity);
|
||||
} else {
|
||||
evaluation = evaluateUserManagedPermissions(request, ticket, resourceServer, evaluationContext, identity);
|
||||
permissions = evaluatePermissions(request, ticket, resourceServer, evaluationContext, identity);
|
||||
}
|
||||
|
||||
List<Permission> permissions = Permissions.permits(evaluation, request.getMetadata(), authorization, resourceServer);
|
||||
|
||||
if (isGranted(ticket, request, permissions)) {
|
||||
ClientModel targetClient = this.authorization.getRealm().getClientById(resourceServer.getId());
|
||||
AuthorizationResponse response = createAuthorizationResponse(identity, permissions, request, targetClient);
|
||||
AuthorizationProvider authorization = request.getAuthorization();
|
||||
ClientModel targetClient = authorization.getRealm().getClientById(resourceServer.getId());
|
||||
Metadata metadata = request.getMetadata();
|
||||
String responseMode = metadata != null ? metadata.getResponseMode() : null;
|
||||
|
||||
return Cors.add(httpRequest, Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
|
||||
.allowedOrigins(getKeycloakSession().getContext().getUri(), targetClient)
|
||||
.allowedMethods(HttpMethod.POST)
|
||||
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
if (responseMode != null) {
|
||||
if (RESPONSE_MODE_DECISION.equals(metadata.getResponseMode())) {
|
||||
Map<String, Object> responseClaims = new HashMap<>();
|
||||
|
||||
responseClaims.put(RESPONSE_MODE_DECISION_RESULT, true);
|
||||
|
||||
return createSuccessfulResponse(responseClaims, targetClient, request);
|
||||
} else if (RESPONSE_MODE_PERMISSIONS.equals(metadata.getResponseMode())) {
|
||||
return createSuccessfulResponse(permissions, targetClient, request);
|
||||
} else {
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Invalid response_mode", Status.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
return createSuccessfulResponse(createAuthorizationResponse(identity, permissions, request, targetClient), targetClient, request);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.isSubmitRequest()) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "request_submitted", Status.FORBIDDEN);
|
||||
} else {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.ACCESS_DENIED, "not_authorized", Status.FORBIDDEN);
|
||||
}
|
||||
} catch (ErrorResponseException | CorsErrorResponseException cause) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
@ -191,45 +197,55 @@ public class AuthorizationTokenService {
|
|||
throw cause;
|
||||
} catch (Exception cause) {
|
||||
logger.error("Unexpected error while evaluating permissions", cause);
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.SERVER_ERROR, "Unexpected error while evaluating permissions", Status.INTERNAL_SERVER_ERROR);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.SERVER_ERROR, "Unexpected error while evaluating permissions", Status.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPublicClientRequestingEntitlementWithClaims(AuthorizationRequest request) {
|
||||
return request.getClaimToken() != null && getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null;
|
||||
private Response createSuccessfulResponse(Object response, ClientModel targetClient, KeycloakAuthorizationRequest request) {
|
||||
return Cors.add(request.getHttpRequest(), Response.status(Status.OK).type(MediaType.APPLICATION_JSON_TYPE).entity(response))
|
||||
.allowedOrigins(request.getKeycloakSession().getContext().getUri(), targetClient)
|
||||
.allowedMethods(HttpMethod.POST)
|
||||
.exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
}
|
||||
|
||||
private 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()
|
||||
.from(createPermissions(ticket, authorizationRequest, resourceServer, identity, authorization), evaluationContext)
|
||||
.evaluate();
|
||||
.from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
|
||||
.evaluate(resourceServer, request);
|
||||
}
|
||||
|
||||
private List<Result> evaluateUserManagedPermissions(AuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
|
||||
private Collection<Permission> evaluateUserManagedPermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
|
||||
AuthorizationProvider authorization = request.getAuthorization();
|
||||
return authorization.evaluators()
|
||||
.from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
|
||||
.evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results();
|
||||
}
|
||||
|
||||
private List<Result> evaluateAllPermissions(AuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
|
||||
private Collection<Permission> evaluateAllPermissions(KeycloakAuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) {
|
||||
AuthorizationProvider authorization = request.getAuthorization();
|
||||
return authorization.evaluators()
|
||||
.from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext)
|
||||
.evaluate();
|
||||
.evaluate(resourceServer, request);
|
||||
}
|
||||
|
||||
private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, List<Permission> entitlements, AuthorizationRequest request, ClientModel targetClient) {
|
||||
KeycloakSession keycloakSession = getKeycloakSession();
|
||||
private AuthorizationResponse createAuthorizationResponse(KeycloakIdentity identity, Collection<Permission> entitlements, KeycloakAuthorizationRequest request, ClientModel targetClient) {
|
||||
KeycloakSession keycloakSession = request.getKeycloakSession();
|
||||
AccessToken accessToken = identity.getAccessToken();
|
||||
UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(getRealm(), accessToken.getSessionState());
|
||||
ClientModel client = getRealm().getClientByClientId(accessToken.getIssuedFor());
|
||||
RealmModel realm = request.getRealm();
|
||||
UserSessionModel userSessionModel = keycloakSession.sessions().getUserSession(realm, accessToken.getSessionState());
|
||||
ClientModel client = realm.getClientByClientId(accessToken.getIssuedFor());
|
||||
AuthenticatedClientSessionModel clientSession = userSessionModel.getAuthenticatedClientSessionByClient(client.getId());
|
||||
|
||||
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionScopeParameter(clientSession);
|
||||
|
||||
AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(getRealm(), clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx)
|
||||
TokenManager tokenManager = request.getTokenManager();
|
||||
EventBuilder event = request.getEvent();
|
||||
AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, keycloakSession, userSessionModel, clientSessionCtx)
|
||||
.generateAccessToken()
|
||||
.generateRefreshToken();
|
||||
|
||||
AccessToken rpt = responseBuilder.getAccessToken();
|
||||
|
||||
rpt.issuedFor(client.getClientId());
|
||||
|
@ -262,7 +278,7 @@ public class AuthorizationTokenService {
|
|||
Authorization previousAuthorization = previousRpt.getAuthorization();
|
||||
|
||||
if (previousAuthorization != null) {
|
||||
List<Permission> previousPermissions = previousAuthorization.getPermissions();
|
||||
Collection<Permission> previousPermissions = previousAuthorization.getPermissions();
|
||||
|
||||
if (previousPermissions != null) {
|
||||
for (Permission previousPermission : previousPermissions) {
|
||||
|
@ -276,7 +292,7 @@ public class AuthorizationTokenService {
|
|||
return true;
|
||||
}
|
||||
|
||||
private PermissionTicketToken getPermissionTicket(AuthorizationRequest request) {
|
||||
private PermissionTicketToken getPermissionTicket(KeycloakAuthorizationRequest request) {
|
||||
// if there is a ticket is because it is a UMA flow and the ticket was sent by the client after obtaining it from the target resource server
|
||||
if (request.getTicket() != null) {
|
||||
return verifyPermissionTicket(request);
|
||||
|
@ -292,32 +308,33 @@ public class AuthorizationTokenService {
|
|||
return permissions;
|
||||
}
|
||||
|
||||
private ResourceServer getResourceServer(PermissionTicketToken ticket) {
|
||||
private ResourceServer getResourceServer(PermissionTicketToken ticket, KeycloakAuthorizationRequest request) {
|
||||
AuthorizationProvider authorization = request.getAuthorization();
|
||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||
ResourceServerStore resourceServerStore = storeFactory.getResourceServerStore();
|
||||
String[] audience = ticket.getAudience();
|
||||
|
||||
if (audience == null || audience.length == 0) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "You must provide the audience", Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "You must provide the audience", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
ClientModel clientModel = getRealm().getClientByClientId(audience[0]);
|
||||
ClientModel clientModel = request.getRealm().getClientByClientId(audience[0]);
|
||||
|
||||
if (clientModel == null) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Unknown resource server id.", Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Unknown resource server id.", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
ResourceServer resourceServer = resourceServerStore.findById(clientModel.getId());
|
||||
|
||||
if (resourceServer == null) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Client does not support permissions", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return resourceServer;
|
||||
}
|
||||
|
||||
private KeycloakEvaluationContext createEvaluationContext(AuthorizationRequest authorizationRequest) {
|
||||
String claimTokenFormat = authorizationRequest.getClaimTokenFormat();
|
||||
private KeycloakEvaluationContext createEvaluationContext(KeycloakAuthorizationRequest request) {
|
||||
String claimTokenFormat = request.getClaimTokenFormat();
|
||||
|
||||
if (claimTokenFormat == null) {
|
||||
claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
|
||||
|
@ -326,13 +343,13 @@ public class AuthorizationTokenService {
|
|||
BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
|
||||
|
||||
if (evaluationContextProvider == null) {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);
|
||||
throw new CorsErrorResponseException(request.getCors(), OAuthErrorException.INVALID_REQUEST, "Claim token format [" + claimTokenFormat + "] not supported", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return evaluationContextProvider.apply(authorizationRequest, authorization);
|
||||
return evaluationContextProvider.apply(request, request.getAuthorization());
|
||||
}
|
||||
|
||||
private List<ResourcePermission> createPermissions(PermissionTicketToken ticket, AuthorizationRequest request, ResourceServer resourceServer, KeycloakIdentity identity, AuthorizationProvider authorization) {
|
||||
private Collection<ResourcePermission> createPermissions(PermissionTicketToken ticket, KeycloakAuthorizationRequest request, ResourceServer resourceServer, KeycloakIdentity identity, AuthorizationProvider authorization) {
|
||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||
Map<String, ResourcePermission> permissionsToEvaluate = new LinkedHashMap<>();
|
||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||
|
@ -340,30 +357,35 @@ public class AuthorizationTokenService {
|
|||
Metadata metadata = request.getMetadata();
|
||||
Integer limit = metadata != null ? metadata.getLimit() : null;
|
||||
|
||||
for (Permission requestedResource : ticket.getPermissions()) {
|
||||
for (Permission permission : ticket.getPermissions()) {
|
||||
if (limit != null && limit <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
Set<String> requestedScopes = requestedResource.getScopes();
|
||||
Set<String> requestedScopes = permission.getScopes();
|
||||
|
||||
if (requestedResource.getScopes() == null) {
|
||||
if (permission.getScopes() == null) {
|
||||
requestedScopes = new HashSet<>();
|
||||
}
|
||||
|
||||
List<Resource> existingResources = new ArrayList<>();
|
||||
String resourceId = permission.getResourceId();
|
||||
|
||||
if (requestedResource.getResourceId() != null) {
|
||||
Resource resource = resourceStore.findById(requestedResource.getResourceId(), resourceServer.getId());
|
||||
if (resourceId != null) {
|
||||
Resource resource = null;
|
||||
|
||||
if (resourceId.indexOf('-') != -1) {
|
||||
resource = resourceStore.findById(resourceId, resourceServer.getId());
|
||||
}
|
||||
|
||||
if (resource != null) {
|
||||
existingResources.add(resource);
|
||||
} else {
|
||||
String resourceName = requestedResource.getResourceId();
|
||||
String resourceName = resourceId;
|
||||
Resource ownerResource = resourceStore.findByName(resourceName, identity.getId(), resourceServer.getId());
|
||||
|
||||
if (ownerResource != null) {
|
||||
requestedResource.setResourceId(ownerResource.getId());
|
||||
permission.setResourceId(ownerResource.getId());
|
||||
existingResources.add(ownerResource);
|
||||
}
|
||||
|
||||
|
@ -371,7 +393,7 @@ public class AuthorizationTokenService {
|
|||
Resource serverResource = resourceStore.findByName(resourceName, resourceServer.getId());
|
||||
|
||||
if (serverResource != null) {
|
||||
requestedResource.setResourceId(serverResource.getId());
|
||||
permission.setResourceId(serverResource.getId());
|
||||
existingResources.add(serverResource);
|
||||
}
|
||||
}
|
||||
|
@ -386,28 +408,28 @@ public class AuthorizationTokenService {
|
|||
|
||||
List<Scope> requestedScopesModel = requestedScopes.stream().map(s -> scopeStore.findByName(s, resourceServer.getId())).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
|
||||
if (requestedResource.getResourceId() != null && !"".equals(requestedResource.getResourceId().trim()) && existingResources.isEmpty()) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_resource", "Resource with id [" + requestedResource.getResourceId() + "] does not exist.", Status.BAD_REQUEST);
|
||||
if (resourceId != null && existingResources.isEmpty()) {
|
||||
throw new CorsErrorResponseException(request.getCors(), "invalid_resource", "Resource with id [" + resourceId + "] does not exist.", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ((requestedResource.getScopes() != null && !requestedResource.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_scope", "One of the given scopes " + requestedResource.getScopes() + " are invalid", Status.BAD_REQUEST);
|
||||
if ((permission.getScopes() != null && !permission.getScopes().isEmpty()) && requestedScopesModel.isEmpty()) {
|
||||
throw new CorsErrorResponseException(request.getCors(), "invalid_scope", "One of the given scopes " + permission.getScopes() + " are invalid", Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!existingResources.isEmpty()) {
|
||||
for (Resource resource : existingResources) {
|
||||
ResourcePermission permission = permissionsToEvaluate.get(resource.getId());
|
||||
ResourcePermission perm = permissionsToEvaluate.get(resource.getId());
|
||||
|
||||
if (permission == null) {
|
||||
permission = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
|
||||
permissionsToEvaluate.put(resource.getId(), permission);
|
||||
if (perm == null) {
|
||||
perm = Permissions.createResourcePermissions(resource, requestedScopes, authorization, request);
|
||||
permissionsToEvaluate.put(resource.getId(), perm);
|
||||
if (limit != null) {
|
||||
limit--;
|
||||
}
|
||||
} else {
|
||||
for (Scope scope : requestedScopesModel) {
|
||||
if (!permission.getScopes().contains(scope)) {
|
||||
permission.getScopes().add(scope);
|
||||
if (!perm.getScopes().contains(scope)) {
|
||||
perm.getScopes().add(scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -415,14 +437,16 @@ public class AuthorizationTokenService {
|
|||
} else {
|
||||
List<Resource> resources = resourceStore.findByScope(requestedScopesModel.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId());
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
|
||||
} else {
|
||||
for (Resource resource : resources) {
|
||||
permissionsToEvaluate.put(resource.getId(), Permissions.createResourcePermissions(resource, requestedScopes, authorization, request));
|
||||
if (limit != null) {
|
||||
limit--;
|
||||
}
|
||||
}
|
||||
|
||||
permissionsToEvaluate.put("$KC_SCOPE_PERMISSION", new ResourcePermission(null, requestedScopesModel, resourceServer, request.getClaims()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -432,7 +456,7 @@ public class AuthorizationTokenService {
|
|||
AccessToken.Authorization authorizationData = rpt.getAuthorization();
|
||||
|
||||
if (authorizationData != null) {
|
||||
List<Permission> permissions = authorizationData.getPermissions();
|
||||
Collection<Permission> permissions = authorizationData.getPermissions();
|
||||
|
||||
if (permissions != null) {
|
||||
for (Permission grantedPermission : permissions) {
|
||||
|
@ -478,30 +502,30 @@ public class AuthorizationTokenService {
|
|||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(permissionsToEvaluate.values());
|
||||
return permissionsToEvaluate.values();
|
||||
}
|
||||
|
||||
private PermissionTicketToken verifyPermissionTicket(AuthorizationRequest request) {
|
||||
private PermissionTicketToken verifyPermissionTicket(KeycloakAuthorizationRequest request) {
|
||||
String ticketString = request.getTicket();
|
||||
|
||||
if (ticketString == null || !Tokens.verifySignature(getKeycloakSession(), getRealm(), ticketString)) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
|
||||
if (ticketString == null || !Tokens.verifySignature(request.getKeycloakSession(), request.getRealm(), ticketString)) {
|
||||
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Ticket verification failed", Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
try {
|
||||
PermissionTicketToken ticket = new JWSInput(ticketString).readJsonContent(PermissionTicketToken.class);
|
||||
|
||||
if (!ticket.isActive()) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
|
||||
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Invalid permission ticket.", Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
return ticket;
|
||||
} catch (JWSInputException e) {
|
||||
throw new CorsErrorResponseException(cors, "invalid_ticket", "Could not parse permission ticket.", Status.FORBIDDEN);
|
||||
throw new CorsErrorResponseException(request.getCors(), "invalid_ticket", "Could not parse permission ticket.", Status.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, List<Permission> permissions) {
|
||||
private boolean isGranted(PermissionTicketToken ticket, AuthorizationRequest request, Collection<Permission> permissions) {
|
||||
List<Permission> requestedPermissions = ticket.getPermissions();
|
||||
|
||||
// denies in case a rpt was provided along with the authorization request but any requested permission was not granted
|
||||
|
@ -512,11 +536,48 @@ public class AuthorizationTokenService {
|
|||
return !permissions.isEmpty();
|
||||
}
|
||||
|
||||
private KeycloakSession getKeycloakSession() {
|
||||
return this.authorization.getKeycloakSession();
|
||||
public static class KeycloakAuthorizationRequest extends AuthorizationRequest {
|
||||
|
||||
private final AuthorizationProvider authorization;
|
||||
private final TokenManager tokenManager;
|
||||
private final EventBuilder event;
|
||||
private final HttpRequest httpRequest;
|
||||
private final Cors cors;
|
||||
|
||||
public KeycloakAuthorizationRequest(AuthorizationProvider authorization, TokenManager tokenManager, EventBuilder event, HttpRequest request, Cors cors) {
|
||||
this.authorization = authorization;
|
||||
this.tokenManager = tokenManager;
|
||||
this.event = event;
|
||||
httpRequest = request;
|
||||
this.cors = cors;
|
||||
}
|
||||
|
||||
private RealmModel getRealm() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ package org.keycloak.authorization.util;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -32,21 +30,17 @@ import java.util.stream.Collectors;
|
|||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.Decision.Effect;
|
||||
import org.keycloak.authorization.identity.Identity;
|
||||
import org.keycloak.authorization.model.PermissionTicket;
|
||||
import org.keycloak.authorization.model.Policy;
|
||||
import org.keycloak.authorization.model.Resource;
|
||||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.policy.evaluation.Result;
|
||||
import org.keycloak.authorization.store.ResourceStore;
|
||||
import org.keycloak.authorization.store.ScopeStore;
|
||||
import org.keycloak.authorization.store.StoreFactory;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest.Metadata;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
|
||||
/**
|
||||
|
@ -54,8 +48,8 @@ import org.keycloak.services.ErrorResponseException;
|
|||
*/
|
||||
public final class Permissions {
|
||||
|
||||
public static List<ResourcePermission> permission(ResourceServer server, Resource resource, Scope scope) {
|
||||
return Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), server));
|
||||
public static ResourcePermission permission(ResourceServer server, Resource resource, Scope scope) {
|
||||
return new ResourcePermission(resource, new ArrayList<>(Arrays.asList(scope)), server);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,22 +67,40 @@ public final class Permissions {
|
|||
List<ResourcePermission> permissions = new ArrayList<>();
|
||||
StoreFactory storeFactory = authorization.getStoreFactory();
|
||||
ResourceStore resourceStore = storeFactory.getResourceStore();
|
||||
Metadata metadata = request.getMetadata();
|
||||
long limit = Long.MAX_VALUE;
|
||||
|
||||
if (metadata != null && metadata.getLimit() != null) {
|
||||
limit = metadata.getLimit();
|
||||
}
|
||||
|
||||
// obtain all resources where owner is the resource server
|
||||
resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
|
||||
resourceStore.findByOwner(resourceServer.getId(), resourceServer.getId()).stream().limit(limit).forEach(resource -> permissions.add(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
|
||||
|
||||
// obtain all resources where owner is the current user
|
||||
resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().forEach(resource -> permissions.addAll(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
|
||||
resourceStore.findByOwner(identity.getId(), resourceServer.getId()).stream().limit(limit).forEach(resource -> permissions.add(createResourcePermissionsWithScopes(resource, new LinkedList(resource.getScopes()), authorization, request)));
|
||||
|
||||
// obtain all resources granted to the user via permission tickets (uma)
|
||||
List<PermissionTicket> tickets = storeFactory.getPermissionTicketStore().findGranted(identity.getId(), resourceServer.getId());
|
||||
|
||||
if (!tickets.isEmpty()) {
|
||||
Map<String, ResourcePermission> userManagedPermissions = new HashMap<>();
|
||||
|
||||
for (PermissionTicket ticket : tickets) {
|
||||
userManagedPermissions.computeIfAbsent(ticket.getResource().getId(), id -> new ResourcePermission(ticket.getResource(), new ArrayList<>(), resourceServer, request.getClaims()));
|
||||
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());
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
@ -131,8 +143,7 @@ public final class Permissions {
|
|||
return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
|
||||
}
|
||||
|
||||
public static List<ResourcePermission> createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
|
||||
List<ResourcePermission> permissions = new ArrayList<>();
|
||||
public static ResourcePermission createResourcePermissionsWithScopes(Resource resource, List<Scope> scopes, AuthorizationProvider authorization, AuthorizationRequest request) {
|
||||
String type = resource.getType();
|
||||
ResourceServer resourceServer = resource.getResourceServer();
|
||||
|
||||
|
@ -152,151 +163,6 @@ public final class Permissions {
|
|||
});
|
||||
}
|
||||
|
||||
permissions.add(new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims()));
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public static List<Permission> permits(List<Result> evaluation, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
|
||||
return permits(evaluation, null, authorizationProvider, resourceServer);
|
||||
}
|
||||
|
||||
public static List<Permission> permits(List<Result> evaluation, Metadata metadata, AuthorizationProvider authorizationProvider, ResourceServer resourceServer) {
|
||||
Map<String, Permission> permissions = new LinkedHashMap<>();
|
||||
|
||||
for (Result result : evaluation) {
|
||||
Set<Scope> deniedScopes = new HashSet<>();
|
||||
Set<Scope> grantedScopes = new HashSet<>();
|
||||
boolean resourceDenied = false;
|
||||
ResourcePermission permission = result.getPermission();
|
||||
List<Result.PolicyResult> results = result.getResults();
|
||||
List<Result.PolicyResult> userManagedPermissions = new ArrayList<>();
|
||||
int deniedCount = results.size();
|
||||
Resource resource = permission.getResource();
|
||||
|
||||
for (Result.PolicyResult policyResult : results) {
|
||||
Policy policy = policyResult.getPolicy();
|
||||
Set<Scope> policyScopes = policy.getScopes();
|
||||
|
||||
if (Effect.PERMIT.equals(policyResult.getStatus())) {
|
||||
if (isScopePermission(policy)) {
|
||||
for (Scope scope : permission.getScopes()) {
|
||||
if (policyScopes.contains(scope)) {
|
||||
// try to grant any scope from a scope-based permission
|
||||
grantedScopes.add(scope);
|
||||
}
|
||||
}
|
||||
} else if (isResourcePermission(policy)) {
|
||||
// we assume that all requested scopes should be granted given that we are processing a resource-based permission.
|
||||
// Later they will be filtered based on any denied scope, if any.
|
||||
// TODO: we could probably provide a configuration option to let users decide whether or not a resource-based permission should grant all scopes associated with the resource.
|
||||
grantedScopes.addAll(permission.getScopes());
|
||||
} if (resource != null && resource.isOwnerManagedAccess() && "uma".equals(policy.getType())) {
|
||||
userManagedPermissions.add(policyResult);
|
||||
}
|
||||
deniedCount--;
|
||||
} else {
|
||||
if (isScopePermission(policy)) {
|
||||
// store all scopes associated with the scope-based permission
|
||||
deniedScopes.addAll(policyScopes);
|
||||
} else if (isResourcePermission(policy)) {
|
||||
resourceDenied = true;
|
||||
deniedScopes.addAll(resource.getScopes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any scope denied from the list of granted scopes
|
||||
if (!deniedScopes.isEmpty()) {
|
||||
grantedScopes.removeAll(deniedScopes);
|
||||
}
|
||||
|
||||
for (Result.PolicyResult policyResult : userManagedPermissions) {
|
||||
Policy policy = policyResult.getPolicy();
|
||||
|
||||
grantedScopes.addAll(policy.getScopes());
|
||||
|
||||
resourceDenied = false;
|
||||
}
|
||||
|
||||
// if there are no policy results is because the permission didn't match any policy.
|
||||
// In this case, if results is empty is because we are in permissive mode.
|
||||
if (!results.isEmpty()) {
|
||||
// update the current permission with the granted scopes
|
||||
permission.getScopes().clear();
|
||||
permission.getScopes().addAll(grantedScopes);
|
||||
}
|
||||
|
||||
if (deniedCount == 0) {
|
||||
result.setStatus(Effect.PERMIT);
|
||||
grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
|
||||
} else {
|
||||
// if a full deny or resource denied or the requested scopes were denied
|
||||
if (deniedCount == results.size() || resourceDenied || (!deniedScopes.isEmpty() && grantedScopes.isEmpty())) {
|
||||
result.setStatus(Effect.DENY);
|
||||
} else {
|
||||
result.setStatus(Effect.PERMIT);
|
||||
grantPermission(authorizationProvider, permissions, permission, resourceServer, metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return permissions.values().stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static boolean isResourcePermission(Policy policy) {
|
||||
return "resource".equals(policy.getType());
|
||||
}
|
||||
|
||||
private static boolean isScopePermission(Policy policy) {
|
||||
return "scope".equals(policy.getType());
|
||||
}
|
||||
|
||||
private static void grantPermission(AuthorizationProvider authorizationProvider, Map<String, Permission> permissions, ResourcePermission permission, ResourceServer resourceServer, Metadata metadata) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
Resource resource = permission.getResource();
|
||||
Set<String> scopes = permission.getScopes().stream().map(Scope::getName).collect(Collectors.toSet());
|
||||
|
||||
if (resource != null) {
|
||||
resources.add(resource);
|
||||
} else {
|
||||
List<Scope> permissionScopes = permission.getScopes();
|
||||
|
||||
if (!permissionScopes.isEmpty()) {
|
||||
ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore();
|
||||
resources.addAll(resourceStore.findByScope(permissionScopes.stream().map(Scope::getId).collect(Collectors.toList()), resourceServer.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!resources.isEmpty()) {
|
||||
for (Resource allowedResource : resources) {
|
||||
String resourceId = allowedResource.getId();
|
||||
String resourceName = metadata == null || metadata.getIncludeResourceName() ? allowedResource.getName() : null;
|
||||
Permission evalPermission = permissions.get(allowedResource.getId());
|
||||
|
||||
if (evalPermission == null) {
|
||||
evalPermission = new Permission(resourceId, resourceName, scopes, permission.getClaims());
|
||||
permissions.put(resourceId, evalPermission);
|
||||
}
|
||||
|
||||
if (scopes != null && !scopes.isEmpty()) {
|
||||
Set<String> finalScopes = evalPermission.getScopes();
|
||||
|
||||
if (finalScopes == null) {
|
||||
finalScopes = new HashSet();
|
||||
evalPermission.setScopes(finalScopes);
|
||||
}
|
||||
|
||||
for (String scopeName : scopes) {
|
||||
if (!finalScopes.contains(scopeName)) {
|
||||
finalScopes.add(scopeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Permission scopePermission = new Permission(null, null, scopes, permission.getClaims());
|
||||
permissions.put(scopePermission.toString(), scopePermission);
|
||||
}
|
||||
return new ResourcePermission(resource, scopes, resource.getResourceServer(), request.getClaims());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.keycloak.OAuthErrorException;
|
|||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.authorization.AuthorizationTokenService;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.authorization.util.Tokens;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.ExchangeExternalToken;
|
||||
|
@ -1070,11 +1069,13 @@ public class TokenEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
AuthorizationRequest authorizationRequest = new AuthorizationRequest(formParams.getFirst("ticket"));
|
||||
AuthorizationTokenService.KeycloakAuthorizationRequest authorizationRequest = new AuthorizationTokenService.KeycloakAuthorizationRequest(session.getProvider(AuthorizationProvider.class), tokenManager, event, this.request, cors);
|
||||
|
||||
authorizationRequest.setTicket(formParams.getFirst("ticket"));
|
||||
authorizationRequest.setClaimToken(claimToken);
|
||||
authorizationRequest.setClaimTokenFormat(claimTokenFormat);
|
||||
authorizationRequest.setPct(formParams.getFirst("pct"));
|
||||
|
||||
String rpt = formParams.getFirst("rpt");
|
||||
|
||||
if (rpt != null) {
|
||||
|
@ -1128,9 +1129,11 @@ public class TokenEndpoint {
|
|||
metadata.setLimit(Integer.parseInt(responsePermissionsLimit));
|
||||
}
|
||||
|
||||
metadata.setResponseMode(formParams.getFirst("response_mode"));
|
||||
|
||||
authorizationRequest.setMetadata(metadata);
|
||||
|
||||
return new AuthorizationTokenService(session.getProvider(AuthorizationProvider.class), tokenManager, event, request, cors).authorize(authorizationRequest);
|
||||
return AuthorizationTokenService.instance().authorize(authorizationRequest);
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636#section-4.1
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.scripting;
|
|||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.CompiledScript;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
|
@ -37,4 +38,13 @@ class CompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapter
|
|||
protected Object eval(final Bindings bindings) throws ScriptException {
|
||||
return compiledScript.eval(bindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(ScriptContext context) throws ScriptExecutionException {
|
||||
try {
|
||||
return compiledScript.eval(context);
|
||||
} catch (ScriptException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.scripting;
|
||||
|
||||
import javax.script.Bindings;
|
||||
import javax.script.ScriptContext;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
|
@ -36,4 +37,12 @@ class UncompiledEvaluatableScriptAdapter extends AbstractEvaluatableScriptAdapte
|
|||
return getEngine().eval(getCode(), bindings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object eval(ScriptContext context) throws ScriptExecutionException {
|
||||
try {
|
||||
return getEngine().eval(getCode(), context);
|
||||
} catch (ScriptException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.Config;
|
||||
import org.keycloak.authorization.AuthorizationProvider;
|
||||
import org.keycloak.authorization.AuthorizationProviderFactory;
|
||||
import org.keycloak.authorization.Decision;
|
||||
import org.keycloak.authorization.common.DefaultEvaluationContext;
|
||||
import org.keycloak.authorization.common.KeycloakIdentity;
|
||||
import org.keycloak.authorization.common.UserModelIdentity;
|
||||
|
@ -30,7 +29,6 @@ import org.keycloak.authorization.model.ResourceServer;
|
|||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.authorization.permission.ResourcePermission;
|
||||
import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
|
||||
import org.keycloak.authorization.policy.evaluation.DecisionResult;
|
||||
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
|
||||
import org.keycloak.authorization.store.ResourceServerStore;
|
||||
import org.keycloak.authorization.util.Permissions;
|
||||
|
@ -45,7 +43,7 @@ import org.keycloak.services.ForbiddenException;
|
|||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.resources.admin.AdminAuth;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -332,15 +330,8 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage
|
|||
RealmModel oldRealm = session.getContext().getRealm();
|
||||
try {
|
||||
session.getContext().setRealm(realm);
|
||||
DecisionResult decisionCollector = new DecisionResult();
|
||||
List<ResourcePermission> permissions = Permissions.permission(resourceServer, resource, scope);
|
||||
PermissionEvaluator from = authz.evaluators().from(permissions, context);
|
||||
from.evaluate(decisionCollector);
|
||||
if (!decisionCollector.completed()) {
|
||||
logger.error("Failed to run permission check", decisionCollector.getError());
|
||||
return false;
|
||||
}
|
||||
return decisionCollector.getResults().get(0).getEffect() == Decision.Effect.PERMIT;
|
||||
ResourcePermission permission = Permissions.permission(resourceServer, resource, scope);
|
||||
return !authz.evaluators().from(Arrays.asList(permission), context).evaluate(resourceServer, null).isEmpty();
|
||||
} finally {
|
||||
session.getContext().setRealm(oldRealm);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
@ -33,8 +34,6 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
|
@ -42,7 +41,6 @@ import org.keycloak.representations.idm.authorization.Permission;
|
|||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RoleBuilder;
|
||||
|
@ -176,7 +174,7 @@ public abstract class AbstractResourceServerTest extends AbstractAuthzTest {
|
|||
}
|
||||
}
|
||||
|
||||
protected void assertPermissions(List<Permission> permissions, String expectedResource, String... expectedScopes) {
|
||||
protected void assertPermissions(Collection<Permission> permissions, String expectedResource, String... expectedScopes) {
|
||||
Iterator<Permission> iterator = permissions.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -174,7 +175,7 @@ public class AuthorizationTest extends AbstractAuthzTest {
|
|||
AuthorizationResponse response = getAuthzClient().authorization(userName, password).authorize(request);
|
||||
AccessToken token = toAccessToken(response.getToken());
|
||||
Authorization authorization = token.getAuthorization();
|
||||
return authorization.getPermissions();
|
||||
return new ArrayList<>(authorization.getPermissions());
|
||||
}
|
||||
|
||||
private void createResourcePermission(ResourceRepresentation resource, String... policies) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -127,7 +128,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
List<Permission> permissions = new ArrayList<>(authorization.getPermissions());
|
||||
|
||||
assertFalse(permissions.isEmpty());
|
||||
assertEquals("Default Resource", permissions.get(0).getResourceName());
|
||||
|
|
|
@ -16,14 +16,17 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.authz;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -44,9 +47,11 @@ import org.keycloak.representations.AccessToken;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PolicyEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopePermissionRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
|
@ -76,14 +81,15 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
|
|||
|
||||
@Before
|
||||
public void configureAuthorization() throws Exception {
|
||||
createResourcesAndScopes();
|
||||
|
||||
RealmResource realm = getRealm();
|
||||
ClientResource client = getClient(realm);
|
||||
|
||||
if (client.authorization().resources().findByName("Resource A").isEmpty()) {
|
||||
createResourcesAndScopes();
|
||||
createPolicies(realm, client);
|
||||
createPermissions(client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Scope Read on Resource A has two conflicting permissions. One is granting access for Marta and the other for Kolo.
|
||||
|
@ -91,24 +97,29 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
|
|||
* <p>Scope Read should not be granted for Marta.
|
||||
*/
|
||||
@Test
|
||||
public void testMartaCanAccessResourceAWithExecuteAndWrite() {
|
||||
List<Permission> permissions = getEntitlements("marta", "password");
|
||||
public void testMartaCanAccessResourceAWithExecuteAndWrite() throws Exception {
|
||||
ClientResource client = getClient(getRealm());
|
||||
AuthorizationResource authorization = client.authorization();
|
||||
ResourceServerRepresentation settings = authorization.getSettings();
|
||||
|
||||
settings.setPolicyEnforcementMode(PolicyEnforcementMode.ENFORCING);
|
||||
|
||||
authorization.update(settings);
|
||||
|
||||
Collection<Permission> permissions = getEntitlements("marta", "password");
|
||||
|
||||
assertEquals(1, permissions.size());
|
||||
|
||||
for (Permission permission : new ArrayList<>(permissions)) {
|
||||
String resourceSetName = permission.getResourceName();
|
||||
|
||||
switch (resourceSetName) {
|
||||
case "Resource A":
|
||||
assertEquals(2, permission.getScopes().size());
|
||||
assertTrue(permission.getScopes().contains("execute"));
|
||||
assertTrue(permission.getScopes().contains("write"));
|
||||
assertThat(permission.getScopes(), containsInAnyOrder("execute", "write"));
|
||||
permissions.remove(permission);
|
||||
break;
|
||||
case "Resource C":
|
||||
assertEquals(3, permission.getScopes().size());
|
||||
assertTrue(permission.getScopes().contains("execute"));
|
||||
assertTrue(permission.getScopes().contains("write"));
|
||||
assertTrue(permission.getScopes().contains("read"));
|
||||
assertThat(permission.getScopes(), containsInAnyOrder("execute", "write", "read"));
|
||||
permissions.remove(permission);
|
||||
break;
|
||||
default:
|
||||
|
@ -119,7 +130,83 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
|
|||
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();
|
||||
AuthorizationResponse response = authzClient.authorization(username, password).authorize();
|
||||
AccessToken accessToken;
|
||||
|
@ -147,7 +234,7 @@ public class ConflictingScopePermissionTest extends AbstractAuthzTest {
|
|||
}
|
||||
|
||||
private void createPermissions(ClientResource client) throws IOException {
|
||||
createResourcePermission("Resource C Only For Marta Permission", "Resource C", Arrays.asList("Only Marta Policy"), client);
|
||||
createResourcePermission("Resource A Only For Marta Permission", "Resource A", Arrays.asList("Only Marta Policy"), client);
|
||||
createScopePermission("Resource A Scope Read Only For Marta Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Marta Policy"), client);
|
||||
createScopePermission("Resource A Scope Read Only For Kolo Permission", "Resource A", Arrays.asList("read"), Arrays.asList("Only Kolo Policy"), client);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import static org.junit.Assert.fail;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -273,7 +274,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
AuthorizationResponse response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
|
||||
AccessToken rpt = toAccessToken(response.getToken());
|
||||
|
||||
List<Permission> permissions = rpt.getAuthorization().getPermissions();
|
||||
List<Permission> permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
|
||||
|
||||
assertEquals(10, permissions.size());
|
||||
|
||||
|
@ -293,7 +294,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
|
||||
rpt = toAccessToken(response.getToken());
|
||||
|
||||
permissions = rpt.getAuthorization().getPermissions();
|
||||
permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
|
||||
|
||||
assertEquals(10, permissions.size());
|
||||
|
||||
|
@ -317,7 +318,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
|
||||
rpt = toAccessToken(response.getToken());
|
||||
|
||||
permissions = rpt.getAuthorization().getPermissions();
|
||||
permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
|
||||
|
||||
assertEquals(10, permissions.size());
|
||||
assertEquals("Resource 16", permissions.get(0).getResourceName());
|
||||
|
@ -340,7 +341,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
response = getAuthzClient(configFile).authorization("marta", "password").authorize(request);
|
||||
rpt = toAccessToken(response.getToken());
|
||||
|
||||
permissions = rpt.getAuthorization().getPermissions();
|
||||
permissions = new ArrayList<>(rpt.getAuthorization().getPermissions());
|
||||
|
||||
assertEquals(5, permissions.size());
|
||||
assertEquals("Resource 16", permissions.get(0).getResourceName());
|
||||
|
@ -442,7 +443,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
|
||||
authorization.resources().resource(resource.getId()).update(resource);
|
||||
|
||||
// the addition of a new scope invalidates the permission previously granted to the resource
|
||||
// the addition of a new scope still grants access to resource and any scope
|
||||
assertFalse(hasPermission("kolo", "password", resource.getId()));
|
||||
|
||||
accessToken = new OAuthClient().realm("authz-test").clientId(RESOURCE_SERVER_TEST).doGrantAccessTokenRequest("secret", "kolo", "password").getAccessToken();
|
||||
|
@ -486,6 +487,39 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
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
|
||||
public void testObtainAllEntitlementsInvalidResource() throws Exception {
|
||||
ClientResource client = getClient(getRealm(), RESOURCE_SERVER_TEST);
|
||||
|
@ -625,7 +659,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
|
||||
AuthorizationResponse response = authzClient.authorization(accessToken).authorize(request);
|
||||
assertNotNull(response.getToken());
|
||||
List<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
|
||||
Collection<Permission> permissions = toAccessToken(response.getToken()).getAuthorization().getPermissions();
|
||||
assertEquals(2, permissions.size());
|
||||
|
||||
for (Permission grantedPermission : permissions) {
|
||||
|
@ -697,7 +731,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
AuthorizationResponse response = getAuthzClient(AUTHZ_CLIENT_CONFIG).authorization(accessToken).authorize(new AuthorizationRequest());
|
||||
AccessToken rpt = toAccessToken(response.getToken());
|
||||
Authorization authz = rpt.getAuthorization();
|
||||
List<Permission> permissions = authz.getPermissions();
|
||||
Collection<Permission> permissions = authz.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertFalse(permissions.isEmpty());
|
||||
|
@ -718,7 +752,7 @@ public class EntitlementAPITest extends AbstractAuthzTest {
|
|||
private void assertResponse(Metadata metadata, Supplier<AuthorizationResponse> responseSupplier) {
|
||||
AccessToken.Authorization authorization = toAccessToken(responseSupplier.get().getToken()).getAuthorization();
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertFalse(permissions.isEmpty());
|
||||
|
|
|
@ -21,7 +21,9 @@ import static org.junit.Assert.assertNotNull;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -128,7 +130,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
|
|||
assertNotNull(response.getToken());
|
||||
AccessToken rpt = toAccessToken(response.getToken());
|
||||
Authorization authorizationClaim = rpt.getAuthorization();
|
||||
List<Permission> permissions = authorizationClaim.getPermissions();
|
||||
List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
|
||||
|
||||
assertEquals(1, permissions.size());
|
||||
|
||||
|
@ -164,7 +166,7 @@ public class PermissionClaimTest extends AbstractAuthzTest {
|
|||
assertNotNull(response.getToken());
|
||||
AccessToken rpt = toAccessToken(response.getToken());
|
||||
Authorization authorizationClaim = rpt.getAuthorization();
|
||||
List<Permission> permissions = authorizationClaim.getPermissions();
|
||||
List<Permission> permissions = new ArrayList<>(authorizationClaim.getPermissions());
|
||||
|
||||
assertEquals(1, permissions.size());
|
||||
|
||||
|
|
|
@ -664,6 +664,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
|
|||
}
|
||||
return baseAttributes;
|
||||
}
|
||||
}, policy, evaluation -> {}, authorization);
|
||||
}, policy, evaluation -> {}, authorization, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.junit.Assert.fail;
|
|||
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
|
@ -98,7 +99,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"}, new String[] {"ScopeC"});
|
||||
AccessToken accessToken = toAccessToken(response.getToken());
|
||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB", "ScopeC");
|
||||
|
@ -110,7 +111,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
@ -132,7 +133,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", null, new String[] {"ScopeA", "ScopeB"});
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertFalse(response.isUpgraded());
|
||||
assertNotNull(permissions);
|
||||
|
@ -155,7 +156,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", "Resource A", new String[] {"ScopeA", "ScopeB"});
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertFalse(response.isUpgraded());
|
||||
assertNotNull(permissions);
|
||||
|
@ -194,7 +195,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", resourceA.getId(), new String[] {"ScopeA", "ScopeB"});
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertFalse(response.isUpgraded());
|
||||
assertNotNull(permissions);
|
||||
|
@ -261,7 +262,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
AuthorizationResponse response = authorize("marta", "password", resourceA.getName(), new String[] {"READ"});
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertFalse(response.isUpgraded());
|
||||
assertNotNull(permissions);
|
||||
|
@ -303,7 +304,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
new PermissionRequest(resourceB.getName(), "ScopeC"));
|
||||
String rpt = response.getToken();
|
||||
AccessToken.Authorization authorization = toAccessToken(rpt).getAuthorization();
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, resourceA.getName(), "ScopeA", "ScopeB");
|
||||
|
@ -324,7 +325,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
@ -346,7 +347,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
@ -366,7 +367,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
@ -447,7 +448,7 @@ public class UmaGrantTypeTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue;
|
|||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -84,7 +85,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
|
||||
|
@ -142,7 +143,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, resource.getName(), "ScopeA", "ScopeB");
|
||||
|
@ -204,7 +205,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
@ -280,7 +281,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A");
|
||||
|
@ -382,7 +383,7 @@ public class UserManagedAccessTest extends AbstractResourceServerTest {
|
|||
|
||||
assertNotNull(authorization);
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
Collection<Permission> permissions = authorization.getPermissions();
|
||||
|
||||
assertNotNull(permissions);
|
||||
assertPermissions(permissions, "Resource A", "ScopeA", "ScopeB");
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue