/*
* 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.models.authorization.infinispan;
import org.infinispan.Cache;
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.store.PolicyStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction;
import org.keycloak.models.authorization.infinispan.entities.CachedPolicy;
import org.keycloak.representations.idm.authorization.DecisionStrategy;
import org.keycloak.representations.idm.authorization.Logic;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class CachedPolicyStore implements PolicyStore {
private static final String POLICY_ID_CACHE_PREFIX = "policy-id-";
private final Cache<String, List> cache;
private final KeycloakSession session;
private final CacheTransaction transaction;
private StoreFactory storeFactory;
private PolicyStore delegate;
public CachedPolicyStore(KeycloakSession session, CacheTransaction transaction) {
this.session = session;
this.transaction = transaction;
InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME);
}
@Override
public Policy create(String name, String type, ResourceServer resourceServer) {
Policy policy = getDelegate().create(name, type, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()));
this.transaction.whenRollback(() -> cache.remove(getCacheKeyForPolicy(policy.getId())));
return createAdapter(new CachedPolicy(policy));
}
@Override
public void delete(String id) {
getDelegate().delete(id);
this.transaction.whenCommit(() -> cache.remove(getCacheKeyForPolicy(id)));
}
@Override
public Policy findById(String id) {
String cacheKeyForPolicy = getCacheKeyForPolicy(id);
List<CachedPolicy> cached = this.cache.get(cacheKeyForPolicy);
if (cached == null) {
Policy policy = getDelegate().findById(id);
if (policy != null) {
return createAdapter(updatePolicyCache(policy));
}
return null;
}
return createAdapter(cached.get(0));
}
@Override
public Policy findByName(String name, String resourceServerId) {
return getDelegate().findByName(name, resourceServerId);
}
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<Policy> findByResource(String resourceId) {
List<Policy> cache = new ArrayList<>();
for (Entry entry : this.cache.entrySet()) {
String cacheKey = (String) entry.getKey();
if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) {
List<CachedPolicy> value = (List<CachedPolicy>) entry.getValue();
CachedPolicy policy = value.get(0);
if (policy.getResourcesIds().contains(resourceId)) {
cache.add(findById(policy.getId()));
}
}
}
if (cache.isEmpty()) {
getDelegate().findByResource(resourceId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId())));
}
return cache;
}
@Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
List<Policy> cache = new ArrayList<>();
for (Entry entry : this.cache.entrySet()) {
String cacheKey = (String) entry.getKey();
if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) {
List<CachedPolicy> value = (List<CachedPolicy>) entry.getValue();
CachedPolicy policy = value.get(0);
if (policy.getResourceServerId().equals(resourceServerId) && policy.getConfig().getOrDefault("defaultResourceType", "").equals(resourceType)) {
cache.add(findById(policy.getId()));
}
}
}
if (cache.isEmpty()) {
getDelegate().findByResourceType(resourceType, resourceServerId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId())));
}
return cache;
}
@Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
List<Policy> cache = new ArrayList<>();
for (Entry entry : this.cache.entrySet()) {
String cacheKey = (String) entry.getKey();
if (cacheKey.startsWith(POLICY_ID_CACHE_PREFIX)) {
List<CachedPolicy> value = (List<CachedPolicy>) entry.getValue();
CachedPolicy policy = value.get(0);
for (String scopeId : policy.getScopesIds()) {
if (scopeIds.contains(scopeId)) {
cache.add(findById(policy.getId()));
break;
}
}
}
}
if (cache.isEmpty()) {
getDelegate().findByScopeIds(scopeIds, resourceServerId).forEach(policy -> cache.add(findById(updatePolicyCache(policy).getId())));
}
return cache;
}
@Override
public List<Policy> findByType(String type) {
return getDelegate().findByType(type).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
@Override
public List<Policy> findDependentPolicies(String id) {
return getDelegate().findDependentPolicies(id).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList());
}
private String getCacheKeyForPolicy(String policyId) {
return POLICY_ID_CACHE_PREFIX + policyId;
}
private StoreFactory getStoreFactory() {
if (this.storeFactory == null) {
this.storeFactory = this.session.getProvider(StoreFactory.class);
}
return this.storeFactory;
}
private PolicyStore getDelegate() {
if (this.delegate == null) {
this.delegate = getStoreFactory().getPolicyStore();
}
return this.delegate;
}
private Policy createAdapter(CachedPolicy cached) {
return new Policy() {
private Policy updated;
@Override
public String getId() {
return cached.getId();
}
@Override
public String getType() {
return cached.getType();
}
@Override
public DecisionStrategy getDecisionStrategy() {
return cached.getDecisionStrategy();
}
@Override
public void setDecisionStrategy(DecisionStrategy decisionStrategy) {
getDelegateForUpdate().setDecisionStrategy(decisionStrategy);
cached.setDecisionStrategy(decisionStrategy);
}
@Override
public Logic getLogic() {
return cached.getLogic();
}
@Override
public void setLogic(Logic logic) {
getDelegateForUpdate().setLogic(logic);
cached.setLogic(logic);
}
@Override
public Map<String, String> getConfig() {
return cached.getConfig();
}
@Override
public void setConfig(Map<String, String> config) {
getDelegateForUpdate().setConfig(config);
cached.setConfig(config);
}
@Override
public String getName() {
return cached.getName();
}
@Override
public void setName(String name) {
getDelegateForUpdate().setName(name);
cached.setName(name);
}
@Override
public String getDescription() {
return cached.getDescription();
}
@Override
public void setDescription(String description) {
getDelegateForUpdate().setDescription(description);
cached.setDescription(description);
}
@Override
public ResourceServer getResourceServer() {
return getStoreFactory().getResourceServerStore().findById(cached.getResourceServerId());
}
@Override
public void addScope(Scope scope) {
getDelegateForUpdate().addScope(getStoreFactory().getScopeStore().findById(scope.getId()));
cached.addScope(scope);
}
@Override
public void removeScope(Scope scope) {
getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId()));
cached.removeScope(scope);
}
@Override
public void addAssociatedPolicy(Policy associatedPolicy) {
getDelegateForUpdate().addAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId()));
cached.addAssociatedPolicy(associatedPolicy);
}
@Override
public void removeAssociatedPolicy(Policy associatedPolicy) {
getDelegateForUpdate().removeAssociatedPolicy(getStoreFactory().getPolicyStore().findById(associatedPolicy.getId()));
cached.removeAssociatedPolicy(associatedPolicy);
}
@Override
public void addResource(Resource resource) {
getDelegateForUpdate().addResource(getStoreFactory().getResourceStore().findById(resource.getId()));
cached.addResource(resource);
}
@Override
public void removeResource(Resource resource) {
getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId()));
cached.removeResource(resource);
}
@Override
public Set<Policy> getAssociatedPolicies() {
Set<Policy> associatedPolicies = new HashSet<>();
for (String id : cached.getAssociatedPoliciesIds()) {
Policy cached = findById(id);
if (cached != null) {
associatedPolicies.add(cached);
}
}
return associatedPolicies;
}
@Override
public Set<Resource> getResources() {
Set<Resource> resources = new HashSet<>();
for (String id : cached.getResourcesIds()) {
Resource cached = getStoreFactory().getResourceStore().findById(id);
if (cached != null) {
resources.add(cached);
}
}
return resources;
}
@Override
public Set<Scope> getScopes() {
Set<Scope> scopes = new HashSet<>();
for (String id : cached.getScopesIds()) {
Scope cached = getStoreFactory().getScopeStore().findById(id);
if (cached != null) {
scopes.add(cached);
}
}
return scopes;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (getId() == null) return false;
if (!Policy.class.isInstance(o)) return false;
Policy that = (Policy) o;
if (!getId().equals(that.getId())) return false;
return true;
}
@Override
public int hashCode() {
return getId()!=null ? getId().hashCode() : super.hashCode();
}
private Policy getDelegateForUpdate() {
if (this.updated == null) {
this.updated = getDelegate().findById(getId());
if (this.updated == null) throw new IllegalStateException("Not found in database");
transaction.whenCommit(() -> cache.remove(getCacheKeyForPolicy(getId())));
}
return this.updated;
}
};
}
private CachedPolicy updatePolicyCache(Policy policy) {
CachedPolicy cached = new CachedPolicy(policy);
List<Policy> cache = new ArrayList<>();
cache.add(cached);
this.cache.put(getCacheKeyForPolicy(policy.getId()), cache);
return cached;
}
}