[KEYCLOAK-7605] - Make sure Evaluation API is read-only

This commit is contained in:
Pedro Igor 2018-09-25 10:39:23 -03:00
parent c3f1bd5a25
commit 79ca722b49
16 changed files with 230 additions and 91 deletions

View file

@ -146,6 +146,16 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
return permissionTicketCache; return permissionTicketCache;
} }
@Override
public void setReadOnly(boolean readOnly) {
getDelegate().setReadOnly(readOnly);
}
@Override
public boolean isReadOnly() {
return getDelegate().isReadOnly();
}
public void close() { public void close() {
if (delegate != null) { if (delegate != null) {
delegate.close(); delegate.close();
@ -724,7 +734,14 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
Long loaded = cache.getCurrentRevision(cacheKey); Long loaded = cache.getCurrentRevision(cacheKey);
List<R> model = resultSupplier.get(); List<R> model = resultSupplier.get();
if (model == null) return null; if (model == null) return null;
if (invalidations.contains(cacheKey)) return model; if (invalidations.contains(cacheKey)) {
if (consumer != null) {
for (R policy: model) {
consumer.accept(policy);
}
}
return model;
};
query = querySupplier.apply(loaded, model); query = querySupplier.apply(loaded, model);
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
if (consumer != null) { if (consumer != null) {
@ -930,7 +947,14 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
Long loaded = cache.getCurrentRevision(cacheKey); Long loaded = cache.getCurrentRevision(cacheKey);
List<R> model = resultSupplier.get(); List<R> model = resultSupplier.get();
if (model == null) return null; if (model == null) return null;
if (invalidations.contains(cacheKey)) return model; if (invalidations.contains(cacheKey)) {
if (consumer != null) {
for (R policy: model) {
consumer.accept(policy);
}
}
return model;
};
query = querySupplier.apply(loaded, model); query = querySupplier.apply(loaded, model);
cache.addRevisioned(query, startupRevision); cache.addRevisioned(query, startupRevision);
if (consumer != null) { if (consumer != null) {

View file

@ -38,6 +38,7 @@ public class JPAStoreFactory implements StoreFactory {
private final ResourceStore resourceStore; private final ResourceStore resourceStore;
private final ScopeStore scopeStore; private final ScopeStore scopeStore;
private final JPAPermissionTicketStore permissionTicketStore; private final JPAPermissionTicketStore permissionTicketStore;
private boolean readOnly;
public JPAStoreFactory(EntityManager entityManager, AuthorizationProvider provider) { public JPAStoreFactory(EntityManager entityManager, AuthorizationProvider provider) {
policyStore = new JPAPolicyStore(entityManager, provider); policyStore = new JPAPolicyStore(entityManager, provider);
@ -76,4 +77,14 @@ public class JPAStoreFactory implements StoreFactory {
public void close() { public void close() {
} }
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
} }

View file

@ -19,6 +19,7 @@ package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.jpa.entities.PolicyEntity; import org.keycloak.authorization.jpa.entities.PolicyEntity;
import org.keycloak.authorization.jpa.entities.ResourceEntity; import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity; import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.AbstractAuthorizationModel;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
@ -39,12 +40,13 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> { public class PolicyAdapter extends AbstractAuthorizationModel implements Policy, JpaModel<PolicyEntity> {
private PolicyEntity entity; private PolicyEntity entity;
private EntityManager em; private EntityManager em;
private StoreFactory storeFactory; private StoreFactory storeFactory;
public PolicyAdapter(PolicyEntity entity, EntityManager em, StoreFactory storeFactory) { public PolicyAdapter(PolicyEntity entity, EntityManager em, StoreFactory storeFactory) {
super(storeFactory);
this.entity = entity; this.entity = entity;
this.em = em; this.em = em;
this.storeFactory = storeFactory; this.storeFactory = storeFactory;
@ -72,6 +74,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void setDecisionStrategy(DecisionStrategy decisionStrategy) { public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
throwExceptionIfReadonly();
entity.setDecisionStrategy(decisionStrategy); entity.setDecisionStrategy(decisionStrategy);
} }
@ -83,6 +86,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void setLogic(Logic logic) { public void setLogic(Logic logic) {
throwExceptionIfReadonly();
entity.setLogic(logic); entity.setLogic(logic);
} }
@ -95,6 +99,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void setConfig(Map<String, String> config) { public void setConfig(Map<String, String> config) {
throwExceptionIfReadonly();
if (entity.getConfig() == null) { if (entity.getConfig() == null) {
entity.setConfig(new HashMap<>()); entity.setConfig(new HashMap<>());
} else { } else {
@ -105,6 +110,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void removeConfig(String name) { public void removeConfig(String name) {
throwExceptionIfReadonly();
if (entity.getConfig() == null) { if (entity.getConfig() == null) {
return; return;
} }
@ -113,6 +119,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void putConfig(String name, String value) { public void putConfig(String name, String value) {
throwExceptionIfReadonly();
if (entity.getConfig() == null) { if (entity.getConfig() == null) {
entity.setConfig(new HashMap<>()); entity.setConfig(new HashMap<>());
} }
@ -127,6 +134,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void setName(String name) { public void setName(String name) {
throwExceptionIfReadonly();
entity.setName(name); entity.setName(name);
} }
@ -138,6 +146,7 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void setDescription(String description) { public void setDescription(String description) {
throwExceptionIfReadonly();
entity.setDescription(description); entity.setDescription(description);
} }
@ -176,38 +185,45 @@ public class PolicyAdapter implements Policy, JpaModel<PolicyEntity> {
@Override @Override
public void addScope(Scope scope) { public void addScope(Scope scope) {
throwExceptionIfReadonly();
entity.getScopes().add(ScopeAdapter.toEntity(em, scope)); entity.getScopes().add(ScopeAdapter.toEntity(em, scope));
} }
@Override @Override
public void removeScope(Scope scope) { public void removeScope(Scope scope) {
throwExceptionIfReadonly();
entity.getScopes().remove(ScopeAdapter.toEntity(em, scope)); entity.getScopes().remove(ScopeAdapter.toEntity(em, scope));
} }
@Override @Override
public void addAssociatedPolicy(Policy associatedPolicy) { public void addAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly();
entity.getAssociatedPolicies().add(toEntity(em, associatedPolicy)); entity.getAssociatedPolicies().add(toEntity(em, associatedPolicy));
} }
@Override @Override
public void removeAssociatedPolicy(Policy associatedPolicy) { public void removeAssociatedPolicy(Policy associatedPolicy) {
throwExceptionIfReadonly();
entity.getAssociatedPolicies().remove(toEntity(em, associatedPolicy)); entity.getAssociatedPolicies().remove(toEntity(em, associatedPolicy));
} }
@Override @Override
public void addResource(Resource resource) { public void addResource(Resource resource) {
throwExceptionIfReadonly();
entity.getResources().add(ResourceAdapter.toEntity(em, resource)); entity.getResources().add(ResourceAdapter.toEntity(em, resource));
} }
@Override @Override
public void removeResource(Resource resource) { public void removeResource(Resource resource) {
throwExceptionIfReadonly();
entity.getResources().remove(ResourceAdapter.toEntity(em, resource)); entity.getResources().remove(ResourceAdapter.toEntity(em, resource));
} }
@Override @Override
public void setOwner(String owner) { public void setOwner(String owner) {
throwExceptionIfReadonly();
entity.setOwner(owner); entity.setOwner(owner);
} }

View file

@ -19,6 +19,7 @@ package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.jpa.entities.ResourceAttributeEntity; import org.keycloak.authorization.jpa.entities.ResourceAttributeEntity;
import org.keycloak.authorization.jpa.entities.ResourceEntity; import org.keycloak.authorization.jpa.entities.ResourceEntity;
import org.keycloak.authorization.jpa.entities.ScopeEntity; import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.AbstractAuthorizationModel;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
@ -43,13 +44,14 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> { public class ResourceAdapter extends AbstractAuthorizationModel implements Resource, JpaModel<ResourceEntity> {
private ResourceEntity entity; private ResourceEntity entity;
private EntityManager em; private EntityManager em;
private StoreFactory storeFactory; private StoreFactory storeFactory;
public ResourceAdapter(ResourceEntity entity, EntityManager em, StoreFactory storeFactory) { public ResourceAdapter(ResourceEntity entity, EntityManager em, StoreFactory storeFactory) {
super(storeFactory);
this.entity = entity; this.entity = entity;
this.em = em; this.em = em;
this.storeFactory = storeFactory; this.storeFactory = storeFactory;
@ -77,6 +79,7 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void setDisplayName(String name) { public void setDisplayName(String name) {
throwExceptionIfReadonly();
entity.setDisplayName(name); entity.setDisplayName(name);
} }
@ -87,11 +90,13 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void updateUris(Set<String> uri) { public void updateUris(Set<String> uri) {
throwExceptionIfReadonly();
entity.setUris(uri); entity.setUris(uri);
} }
@Override @Override
public void setName(String name) { public void setName(String name) {
throwExceptionIfReadonly();
entity.setName(name); entity.setName(name);
} }
@ -103,6 +108,7 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void setType(String type) { public void setType(String type) {
throwExceptionIfReadonly();
entity.setType(type); entity.setType(type);
} }
@ -124,6 +130,7 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void setIconUri(String iconUri) { public void setIconUri(String iconUri) {
throwExceptionIfReadonly();
entity.setIconUri(iconUri); entity.setIconUri(iconUri);
} }
@ -145,11 +152,13 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void setOwnerManagedAccess(boolean ownerManagedAccess) { public void setOwnerManagedAccess(boolean ownerManagedAccess) {
throwExceptionIfReadonly();
entity.setOwnerManagedAccess(ownerManagedAccess); entity.setOwnerManagedAccess(ownerManagedAccess);
} }
@Override @Override
public void updateScopes(Set<Scope> toUpdate) { public void updateScopes(Set<Scope> toUpdate) {
throwExceptionIfReadonly();
Set<String> ids = new HashSet<>(); Set<String> ids = new HashSet<>();
for (Scope scope : toUpdate) { for (Scope scope : toUpdate) {
ids.add(scope.getId()); ids.add(scope.getId());
@ -171,7 +180,7 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
for (ResourceAttributeEntity attr : entity.getAttributes()) { for (ResourceAttributeEntity attr : entity.getAttributes()) {
result.add(attr.getName(), attr.getValue()); result.add(attr.getName(), attr.getValue());
} }
return result; return Collections.unmodifiableMap(result);
} }
@Override @Override
@ -213,6 +222,7 @@ public class ResourceAdapter implements Resource, JpaModel<ResourceEntity> {
@Override @Override
public void removeAttribute(String name) { public void removeAttribute(String name) {
throwExceptionIfReadonly();
Query query = em.createNamedQuery("deleteResourceAttributesByNameAndResource"); Query query = em.createNamedQuery("deleteResourceAttributesByNameAndResource");
query.setParameter("name", name); query.setParameter("name", name);

View file

@ -17,6 +17,7 @@
package org.keycloak.authorization.jpa.store; package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.jpa.entities.ResourceServerEntity; import org.keycloak.authorization.jpa.entities.ResourceServerEntity;
import org.keycloak.authorization.model.AbstractAuthorizationModel;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.jpa.JpaModel; import org.keycloak.models.jpa.JpaModel;
@ -28,12 +29,13 @@ import javax.persistence.EntityManager;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ResourceServerAdapter implements ResourceServer, JpaModel<ResourceServerEntity> { public class ResourceServerAdapter extends AbstractAuthorizationModel implements ResourceServer, JpaModel<ResourceServerEntity> {
private ResourceServerEntity entity; private ResourceServerEntity entity;
private EntityManager em; private EntityManager em;
private StoreFactory storeFactory; private StoreFactory storeFactory;
public ResourceServerAdapter(ResourceServerEntity entity, EntityManager em, StoreFactory storeFactory) { public ResourceServerAdapter(ResourceServerEntity entity, EntityManager em, StoreFactory storeFactory) {
super(storeFactory);
this.entity = entity; this.entity = entity;
this.em = em; this.em = em;
this.storeFactory = storeFactory; this.storeFactory = storeFactory;
@ -56,6 +58,7 @@ public class ResourceServerAdapter implements ResourceServer, JpaModel<ResourceS
@Override @Override
public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) { public void setAllowRemoteResourceManagement(boolean allowRemoteResourceManagement) {
throwExceptionIfReadonly();
entity.setAllowRemoteResourceManagement(allowRemoteResourceManagement); entity.setAllowRemoteResourceManagement(allowRemoteResourceManagement);
} }
@ -67,6 +70,7 @@ public class ResourceServerAdapter implements ResourceServer, JpaModel<ResourceS
@Override @Override
public void setPolicyEnforcementMode(PolicyEnforcementMode enforcementMode) { public void setPolicyEnforcementMode(PolicyEnforcementMode enforcementMode) {
throwExceptionIfReadonly();
entity.setPolicyEnforcementMode(enforcementMode); entity.setPolicyEnforcementMode(enforcementMode);
} }

View file

@ -17,6 +17,7 @@
package org.keycloak.authorization.jpa.store; package org.keycloak.authorization.jpa.store;
import org.keycloak.authorization.jpa.entities.ScopeEntity; import org.keycloak.authorization.jpa.entities.ScopeEntity;
import org.keycloak.authorization.model.AbstractAuthorizationModel;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
@ -28,12 +29,13 @@ import javax.persistence.EntityManager;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class ScopeAdapter implements Scope, JpaModel<ScopeEntity> { public class ScopeAdapter extends AbstractAuthorizationModel implements Scope, JpaModel<ScopeEntity> {
private ScopeEntity entity; private ScopeEntity entity;
private EntityManager em; private EntityManager em;
private StoreFactory storeFactory; private StoreFactory storeFactory;
public ScopeAdapter(ScopeEntity entity, EntityManager em, StoreFactory storeFactory) { public ScopeAdapter(ScopeEntity entity, EntityManager em, StoreFactory storeFactory) {
super(storeFactory);
this.entity = entity; this.entity = entity;
this.em = em; this.em = em;
this.storeFactory = storeFactory; this.storeFactory = storeFactory;
@ -56,6 +58,7 @@ public class ScopeAdapter implements Scope, JpaModel<ScopeEntity> {
@Override @Override
public void setName(String name) { public void setName(String name) {
throwExceptionIfReadonly();
entity.setName(name); entity.setName(name);
} }
@ -67,6 +70,7 @@ public class ScopeAdapter implements Scope, JpaModel<ScopeEntity> {
@Override @Override
public void setDisplayName(String name) { public void setDisplayName(String name) {
throwExceptionIfReadonly();
entity.setDisplayName(name); entity.setDisplayName(name);
} }
@ -77,6 +81,7 @@ public class ScopeAdapter implements Scope, JpaModel<ScopeEntity> {
@Override @Override
public void setIconUri(String iconUri) { public void setIconUri(String iconUri) {
throwExceptionIfReadonly();
entity.setIconUri(iconUri); entity.setIconUri(iconUri);
} }

View file

@ -224,6 +224,16 @@ public final class AuthorizationProvider implements Provider {
public void close() { public void close() {
storeFactory.close(); storeFactory.close();
} }
@Override
public void setReadOnly(boolean readOnly) {
storeFactory.setReadOnly(readOnly);
}
@Override
public boolean isReadOnly() {
return storeFactory.isReadOnly();
}
}; };
} }

View file

@ -0,0 +1,34 @@
/*
* 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.model;
import org.keycloak.authorization.store.StoreFactory;
public abstract class AbstractAuthorizationModel {
private StoreFactory storeFactory;
public AbstractAuthorizationModel(StoreFactory storeFactory) {
this.storeFactory = storeFactory;
}
protected void throwExceptionIfReadonly() {
if (storeFactory.isReadOnly()) {
throw new IllegalStateException("Instance marked as read-only");
}
}
}

View file

@ -30,6 +30,7 @@ import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector; import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.PolicyEvaluator; import org.keycloak.authorization.policy.evaluation.PolicyEvaluator;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.representations.idm.authorization.AuthorizationRequest; import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.Permission;
@ -52,9 +53,13 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
@Override @Override
public Decision evaluate(Decision decision) { public Decision evaluate(Decision decision) {
StoreFactory storeFactory = authorizationProvider.getStoreFactory();
try { try {
Map<Policy, Map<Object, Decision.Effect>> decisionCache = new HashMap<>(); Map<Policy, Map<Object, Decision.Effect>> decisionCache = new HashMap<>();
storeFactory.setReadOnly(true);
while (this.permissions.hasNext()) { while (this.permissions.hasNext()) {
this.policyEvaluator.evaluate(this.permissions.next(), authorizationProvider, executionContext, decision, decisionCache); this.policyEvaluator.evaluate(this.permissions.next(), authorizationProvider, executionContext, decision, decisionCache);
} }
@ -62,7 +67,10 @@ class IterablePermissionEvaluator implements PermissionEvaluator {
decision.onComplete(); decision.onComplete();
} catch (Throwable cause) { } catch (Throwable cause) {
decision.onError(cause); decision.onError(cause);
} finally {
storeFactory.setReadOnly(false);
} }
return decision; return decision;
} }

View file

@ -263,8 +263,7 @@ public class DefaultEvaluation implements Evaluation {
@Override @Override
public Map<String, List<String>> getUserAttributes(String id) { public Map<String, List<String>> getUserAttributes(String id) {
Map<String, List<String>> attributes = getUser(id, authorizationProvider.getKeycloakSession()).getAttributes(); return Collections.unmodifiableMap(getUser(id, authorizationProvider.getKeycloakSession()).getAttributes());
return attributes;
} }
}; };
} }

View file

@ -64,4 +64,19 @@ public interface StoreFactory extends Provider {
* @return the permission ticket store * @return the permission ticket store
*/ */
PermissionTicketStore getPermissionTicketStore(); PermissionTicketStore getPermissionTicketStore();
/**
* Sets whether or not changes to instances returned from this factory are supported. Once marked as read-only, any attempt to
* change state will throw an {@link IllegalStateException}.
*
* @param readOnly if true, changes are not supported
*/
void setReadOnly(boolean readOnly);
/**
* Indicates if instances returned from storage are read-only.
*
* @return if true, instances only support reads.
*/
boolean isReadOnly();
} }

View file

@ -41,14 +41,13 @@ import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder; import org.keycloak.authorization.admin.representation.PolicyEvaluationResponseBuilder;
import org.keycloak.authorization.attribute.Attributes; import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.DefaultEvaluationContext;
import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector; import org.keycloak.authorization.policy.evaluation.DecisionPermissionCollector;
import org.keycloak.authorization.policy.evaluation.DecisionResultCollector;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.Result; import org.keycloak.authorization.policy.evaluation.Result;
import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.ScopeStore;
@ -132,7 +131,7 @@ public class PolicyEvaluationService {
} }
private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) { private EvaluationContext createEvaluationContext(PolicyEvaluationRequest representation, KeycloakIdentity identity) {
return new KeycloakEvaluationContext(identity, this.authorization.getKeycloakSession()) { return new DefaultEvaluationContext(identity, this.authorization.getKeycloakSession()) {
@Override @Override
public Attributes getAttributes() { public Attributes getAttributes() {
Map<String, Collection<String>> attributes = new HashMap<>(super.getAttributes().toMap()); Map<String, Collection<String>> attributes = new HashMap<>(super.getAttributes().toMap());

View file

@ -42,12 +42,13 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.common.KeycloakEvaluationContext; import org.keycloak.authorization.common.DefaultEvaluationContext;
import org.keycloak.authorization.common.KeycloakIdentity; import org.keycloak.authorization.common.KeycloakIdentity;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.authorization.policy.evaluation.PermissionTicketAwareDecisionResultCollector; import org.keycloak.authorization.policy.evaluation.PermissionTicketAwareDecisionResultCollector;
import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ResourceStore;
@ -92,7 +93,7 @@ public class AuthorizationTokenService {
private static final String RESPONSE_MODE_DECISION = "decision"; private static final String RESPONSE_MODE_DECISION = "decision";
private static final String RESPONSE_MODE_PERMISSIONS = "permissions"; private static final String RESPONSE_MODE_PERMISSIONS = "permissions";
private static final String RESPONSE_MODE_DECISION_RESULT = "result"; private static final String RESPONSE_MODE_DECISION_RESULT = "result";
private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS; private static Map<String, BiFunction<AuthorizationRequest, AuthorizationProvider, EvaluationContext>> SUPPORTED_CLAIM_TOKEN_FORMATS;
static { static {
SUPPORTED_CLAIM_TOKEN_FORMATS = new HashMap<>(); SUPPORTED_CLAIM_TOKEN_FORMATS = new HashMap<>();
@ -103,7 +104,7 @@ public class AuthorizationTokenService {
try { try {
Map claims = JsonSerialization.readValue(Base64Url.decode(authorizationRequest.getClaimToken()), Map.class); Map claims = JsonSerialization.readValue(Base64Url.decode(authorizationRequest.getClaimToken()), Map.class);
authorizationRequest.setClaims(claims); authorizationRequest.setClaims(claims);
return new KeycloakEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getSubjectToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession()); return new DefaultEvaluationContext(new KeycloakIdentity(authorization.getKeycloakSession(), Tokens.getAccessToken(authorizationRequest.getSubjectToken(), authorization.getKeycloakSession())), claims, authorization.getKeycloakSession());
} catch (IOException cause) { } catch (IOException cause) {
throw new RuntimeException("Failed to map claims from claim token [" + claimToken + "]", cause); throw new RuntimeException("Failed to map claims from claim token [" + claimToken + "]", cause);
} }
@ -114,7 +115,6 @@ public class AuthorizationTokenService {
SUPPORTED_CLAIM_TOKEN_FORMATS.put(CLAIM_TOKEN_FORMAT_ID_TOKEN, (authorizationRequest, authorization) -> { SUPPORTED_CLAIM_TOKEN_FORMATS.put(CLAIM_TOKEN_FORMAT_ID_TOKEN, (authorizationRequest, authorization) -> {
try { try {
KeycloakSession keycloakSession = authorization.getKeycloakSession(); KeycloakSession keycloakSession = authorization.getKeycloakSession();
RealmModel realm = authorization.getRealm();
String accessToken = authorizationRequest.getSubjectToken(); String accessToken = authorizationRequest.getSubjectToken();
if (accessToken == null) { if (accessToken == null) {
@ -122,7 +122,7 @@ public class AuthorizationTokenService {
} }
IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, accessToken); IDToken idToken = new TokenManager().verifyIDTokenSignature(keycloakSession, accessToken);
return new KeycloakEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), authorizationRequest.getClaims(), keycloakSession); return new DefaultEvaluationContext(new KeycloakIdentity(keycloakSession, idToken), authorizationRequest.getClaims(), keycloakSession);
} catch (OAuthErrorException cause) { } catch (OAuthErrorException cause) {
throw new RuntimeException("Failed to verify ID token", cause); throw new RuntimeException("Failed to verify ID token", cause);
} }
@ -151,7 +151,7 @@ public class AuthorizationTokenService {
request.setClaims(ticket.getClaims()); request.setClaims(ticket.getClaims());
ResourceServer resourceServer = getResourceServer(ticket, request); ResourceServer resourceServer = getResourceServer(ticket, request);
KeycloakEvaluationContext evaluationContext = createEvaluationContext(request); EvaluationContext evaluationContext = createEvaluationContext(request);
KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity()); KeycloakIdentity identity = KeycloakIdentity.class.cast(evaluationContext.getIdentity());
Collection<Permission> permissions; Collection<Permission> permissions;
@ -213,21 +213,21 @@ public class AuthorizationTokenService {
return request.getClaimToken() != null && request.getKeycloakSession().getContext().getClient().isPublicClient() && request.getTicket() == null; 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) { private Collection<Permission> evaluatePermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, EvaluationContext evaluationContext, KeycloakIdentity identity) {
AuthorizationProvider authorization = request.getAuthorization(); AuthorizationProvider authorization = request.getAuthorization();
return authorization.evaluators() return authorization.evaluators()
.from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext) .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
.evaluate(resourceServer, request); .evaluate(resourceServer, request);
} }
private Collection<Permission> evaluateUserManagedPermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) { private Collection<Permission> evaluateUserManagedPermissions(KeycloakAuthorizationRequest request, PermissionTicketToken ticket, ResourceServer resourceServer, EvaluationContext evaluationContext, KeycloakIdentity identity) {
AuthorizationProvider authorization = request.getAuthorization(); AuthorizationProvider authorization = request.getAuthorization();
return authorization.evaluators() return authorization.evaluators()
.from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext) .from(createPermissions(ticket, request, resourceServer, identity, authorization), evaluationContext)
.evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results(); .evaluate(new PermissionTicketAwareDecisionResultCollector(request, ticket, identity, resourceServer, authorization)).results();
} }
private Collection<Permission> evaluateAllPermissions(KeycloakAuthorizationRequest request, ResourceServer resourceServer, KeycloakEvaluationContext evaluationContext, KeycloakIdentity identity) { private Collection<Permission> evaluateAllPermissions(KeycloakAuthorizationRequest request, ResourceServer resourceServer, EvaluationContext evaluationContext, KeycloakIdentity identity) {
AuthorizationProvider authorization = request.getAuthorization(); AuthorizationProvider authorization = request.getAuthorization();
return authorization.evaluators() return authorization.evaluators()
.from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext) .from(Permissions.all(resourceServer, identity, authorization, request), evaluationContext)
@ -340,14 +340,14 @@ public class AuthorizationTokenService {
return resourceServer; return resourceServer;
} }
private KeycloakEvaluationContext createEvaluationContext(KeycloakAuthorizationRequest request) { private EvaluationContext createEvaluationContext(KeycloakAuthorizationRequest request) {
String claimTokenFormat = request.getClaimTokenFormat(); String claimTokenFormat = request.getClaimTokenFormat();
if (claimTokenFormat == null) { if (claimTokenFormat == null) {
claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN; claimTokenFormat = CLAIM_TOKEN_FORMAT_ID_TOKEN;
} }
BiFunction<AuthorizationRequest, AuthorizationProvider, KeycloakEvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat); BiFunction<AuthorizationRequest, AuthorizationProvider, EvaluationContext> evaluationContextProvider = SUPPORTED_CLAIM_TOKEN_FORMATS.get(claimTokenFormat);
if (evaluationContextProvider == null) { if (evaluationContextProvider == null) {
throw new CorsErrorResponseException(request.getCors(), 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);

View file

@ -22,6 +22,7 @@ import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.identity.Identity; import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.policy.evaluation.EvaluationContext; import org.keycloak.authorization.policy.evaluation.EvaluationContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.AccessToken;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
@ -40,6 +41,7 @@ public class DefaultEvaluationContext implements EvaluationContext {
protected final KeycloakSession keycloakSession; protected final KeycloakSession keycloakSession;
protected final Identity identity; protected final Identity identity;
private final Map<String, List<String>> claims; private final Map<String, List<String>> claims;
private Attributes attributes;
public DefaultEvaluationContext(Identity identity, KeycloakSession keycloakSession) { public DefaultEvaluationContext(Identity identity, KeycloakSession keycloakSession) {
this(identity, null, keycloakSession); this(identity, null, keycloakSession);
@ -56,7 +58,7 @@ public class DefaultEvaluationContext implements EvaluationContext {
return identity; return identity;
} }
public Map<String, Collection<String>> getBaseAttributes() { protected Map<String, Collection<String>> getBaseAttributes() {
Map<String, Collection<String>> attributes = new HashMap<>(); Map<String, Collection<String>> attributes = new HashMap<>();
attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))); attributes.put("kc.time.date_time", Arrays.asList(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())));
@ -77,11 +79,22 @@ public class DefaultEvaluationContext implements EvaluationContext {
} }
} }
if (KeycloakIdentity.class.isInstance(identity)) {
AccessToken accessToken = KeycloakIdentity.class.cast(this.identity).getAccessToken();
if (accessToken != null) {
attributes.put("kc.client.id", Arrays.asList(accessToken.getIssuedFor()));
}
}
return attributes; return attributes;
} }
@Override @Override
public Attributes getAttributes() { public Attributes getAttributes() {
return Attributes.from(getBaseAttributes()); if (attributes == null) {
attributes = Attributes.from(getBaseAttributes());
}
return attributes;
} }
} }

View file

@ -1,61 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2016 Red Hat, Inc., and individual 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.common;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.AccessToken;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakEvaluationContext extends DefaultEvaluationContext {
private final KeycloakIdentity identity;
public KeycloakEvaluationContext(KeycloakIdentity identity, KeycloakSession keycloakSession) {
this(identity, null, keycloakSession);
}
public KeycloakEvaluationContext(KeycloakIdentity identity, Map<String, List<String>> claims, KeycloakSession keycloakSession) {
super(identity, claims, keycloakSession);
this.identity = identity;
}
@Override
public Identity getIdentity() {
return this.identity;
}
@Override
public Map<String, Collection<String>> getBaseAttributes() {
Map<String, Collection<String>> attributes = super.getBaseAttributes();
AccessToken accessToken = this.identity.getAccessToken();
if (accessToken != null) {
attributes.put("kc.client.id", Arrays.asList(accessToken.getIssuedFor()));
}
return attributes;
}
}

View file

@ -19,8 +19,10 @@ package org.keycloak.testsuite.authz;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -28,11 +30,9 @@ import java.util.stream.Collectors;
import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.Decision;
import org.keycloak.authorization.Decision.Effect; import org.keycloak.authorization.Decision.Effect;
import org.keycloak.authorization.attribute.Attributes; import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.common.DefaultEvaluationContext; import org.keycloak.authorization.common.DefaultEvaluationContext;
@ -40,9 +40,10 @@ import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.authorization.permission.ResourcePermission; import org.keycloak.authorization.permission.ResourcePermission;
import org.keycloak.authorization.permission.evaluator.PermissionEvaluator;
import org.keycloak.authorization.policy.evaluation.DefaultEvaluation; import org.keycloak.authorization.policy.evaluation.DefaultEvaluation;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider; import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.authorization.store.StoreFactory; import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -57,6 +58,7 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.authorization.JSPolicyRepresentation; import org.keycloak.representations.idm.authorization.JSPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation;
import org.keycloak.representations.idm.authorization.TimePolicyRepresentation; import org.keycloak.representations.idm.authorization.TimePolicyRepresentation;
import org.keycloak.testsuite.runonserver.RunOnServerDeployment; import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
@ -630,6 +632,52 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
Assert.assertEquals(Effect.PERMIT, evaluation.getEffect()); Assert.assertEquals(Effect.PERMIT, evaluation.getEffect());
} }
@Test
public void testCheckReadOnlyInstances() {
testingClient.server().run(PolicyEvaluationTest::testCheckReadOnlyInstances);
}
public static void testCheckReadOnlyInstances(KeycloakSession session) {
session.getContext().setRealm(session.realms().getRealmByName("authz-test"));
AuthorizationProvider authorization = session.getProvider(AuthorizationProvider.class);
ClientModel clientModel = session.realms().getClientByClientId("resource-server-test", session.getContext().getRealm());
StoreFactory storeFactory = authorization.getStoreFactory();
ResourceServer resourceServer = storeFactory.getResourceServerStore().findById(clientModel.getId());
JSPolicyRepresentation policyRepresentation = new JSPolicyRepresentation();
policyRepresentation.setName("testCheckReadOnlyInstances");
StringBuilder builder = new StringBuilder();
builder.append("$evaluation.getPermission().getResource().setName('test')");
policyRepresentation.setCode(builder.toString());
Policy policy = storeFactory.getPolicyStore().create(policyRepresentation, resourceServer);
Resource resource = storeFactory.getResourceStore().create("Resource A", resourceServer, resourceServer.getId());
Scope scope = storeFactory.getScopeStore().create("Scope A", resourceServer);
resource.updateScopes(new HashSet<>(Arrays.asList(scope)));
ResourcePermissionRepresentation permission = new ResourcePermissionRepresentation();
permission.setName("testCheckReadOnlyInstances permission");
permission.addPolicy(policy.getId());
permission.addResource(resource.getId());
storeFactory.getPolicyStore().create(permission, resourceServer);
session.getTransactionManager().commit();
PermissionEvaluator evaluator = authorization.evaluators().from(Arrays.asList(new ResourcePermission(resource, Arrays.asList(scope), resourceServer)), createEvaluationContext(session, Collections.emptyMap()));
try {
evaluator.evaluate(resourceServer, null);
Assert.fail("Instances should be marked as read-only");
} catch (Exception ignore) {
}
}
private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, ResourceServer resourceServer, Policy policy) { private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, ResourceServer resourceServer, Policy policy) {
return createEvaluation(session, authorization, null, resourceServer, policy); return createEvaluation(session, authorization, null, resourceServer, policy);
} }
@ -641,7 +689,11 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization, private static DefaultEvaluation createEvaluation(KeycloakSession session, AuthorizationProvider authorization,
Resource resource, ResourceServer resourceServer, Policy policy, Resource resource, ResourceServer resourceServer, Policy policy,
Map<String, Collection<String>> contextAttributes) { Map<String, Collection<String>> contextAttributes) {
return new DefaultEvaluation(new ResourcePermission(resource, null, resourceServer), new DefaultEvaluationContext(new Identity() { return new DefaultEvaluation(new ResourcePermission(resource, null, resourceServer), createEvaluationContext(session, contextAttributes), policy, evaluation -> {}, authorization, null);
}
private static DefaultEvaluationContext createEvaluationContext(KeycloakSession session, Map<String, Collection<String>> contextAttributes) {
return new DefaultEvaluationContext(new Identity() {
@Override @Override
public String getId() { public String getId() {
return null; return null;
@ -664,6 +716,6 @@ public class PolicyEvaluationTest extends AbstractAuthzTest {
} }
return baseAttributes; return baseAttributes;
} }
}, policy, evaluation -> {}, authorization, null); };
} }
} }