/*
* 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.models.cache.infinispan.authorization;
import org.jboss.logging.Logger;
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.ResourceServerStore;
import org.keycloak.authorization.store.ResourceStore;
import org.keycloak.authorization.store.ScopeStore;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResource;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer;
import org.keycloak.models.cache.infinispan.authorization.entities.CachedScope;
import org.keycloak.models.cache.infinispan.authorization.entities.PolicyListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ResourceServerListQuery;
import org.keycloak.models.cache.infinispan.authorization.entities.ScopeListQuery;
import org.keycloak.models.cache.infinispan.authorization.events.PolicyRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.PolicyUpdatedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ResourceRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ResourceServerRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ResourceServerUpdatedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ResourceUpdatedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ScopeRemovedEvent;
import org.keycloak.models.cache.infinispan.authorization.events.ScopeUpdatedEvent;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class StoreFactoryCacheSession implements CachedStoreFactoryProvider {
protected static final Logger logger = Logger.getLogger(StoreFactoryCacheSession.class);
protected StoreFactoryCacheManager cache;
protected boolean transactionActive;
protected boolean setRollbackOnly;
protected Map<String, ResourceServerAdapter> managedResourceServers = new HashMap<>();
protected Map<String, ScopeAdapter> managedScopes = new HashMap<>();
protected Map<String, ResourceAdapter> managedResources = new HashMap<>();
protected Map<String, PolicyAdapter> managedPolicies = new HashMap<>();
protected Set<String> invalidations = new HashSet<>();
protected Set<InvalidationEvent> invalidationEvents = new HashSet<>(); // Events to be sent across cluster
protected boolean clearAll;
protected final long startupRevision;
protected StoreFactory delegate;
protected KeycloakSession session;
protected ResourceServerCache resourceServerCache;
protected ScopeCache scopeCache;
protected ResourceCache resourceCache;
protected PolicyCache policyCache;
public StoreFactoryCacheSession(StoreFactoryCacheManager cache, KeycloakSession session) {
this.cache = cache;
this.startupRevision = cache.getCurrentCounter();
this.session = session;
this.resourceServerCache = new ResourceServerCache();
this.scopeCache = new ScopeCache();
this.resourceCache = new ResourceCache();
this.policyCache = new PolicyCache();
session.getTransactionManager().enlistPrepare(getPrepareTransaction());
session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
}
@Override
public ResourceServerStore getResourceServerStore() {
return resourceServerCache;
}
@Override
public ScopeStore getScopeStore() {
return scopeCache;
}
@Override
public ResourceStore getResourceStore() {
return resourceCache;
}
@Override
public PolicyStore getPolicyStore() {
return policyCache;
}
public void close() {
}
private KeycloakTransaction getPrepareTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
}
@Override
public void rollback() {
setRollbackOnly = true;
transactionActive = false;
}
@Override
public void setRollbackOnly() {
setRollbackOnly = true;
}
@Override
public boolean getRollbackOnly() {
return setRollbackOnly;
}
@Override
public boolean isActive() {
return transactionActive;
}
};
}
private KeycloakTransaction getAfterTransaction() {
return new KeycloakTransaction() {
@Override
public void begin() {
transactionActive = true;
}
@Override
public void commit() {
try {
if (getDelegate() == null) return;
if (clearAll) {
cache.clear();
}
runInvalidations();
transactionActive = false;
} finally {
cache.endRevisionBatch();
}
}
@Override
public void rollback() {
try {
setRollbackOnly = true;
runInvalidations();
transactionActive = false;
} finally {
cache.endRevisionBatch();
}
}
@Override
public void setRollbackOnly() {
setRollbackOnly = true;
}
@Override
public boolean getRollbackOnly() {
return setRollbackOnly;
}
@Override
public boolean isActive() {
return transactionActive;
}
};
}
protected void runInvalidations() {
for (String id : invalidations) {
cache.invalidateObject(id);
}
cache.sendInvalidationEvents(session, invalidationEvents);
}
public long getStartupRevision() {
return startupRevision;
}
public boolean isInvalid(String id) {
return invalidations.contains(id);
}
public void registerResourceServerInvalidation(String id, String clientId) {
cache.resourceServerUpdated(id, clientId, invalidations);
ResourceServerAdapter adapter = managedResourceServers.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ResourceServerUpdatedEvent.create(id, clientId));
}
public void registerScopeInvalidation(String id, String name, String serverId) {
cache.scopeUpdated(id, name, serverId, invalidations);
ScopeAdapter adapter = managedScopes.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ScopeUpdatedEvent.create(id, name, serverId));
}
public void registerResourceInvalidation(String id, String name, String serverId) {
cache.resourceUpdated(id, name, serverId, invalidations);
ResourceAdapter adapter = managedResources.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(ResourceUpdatedEvent.create(id, name, serverId));
}
public void registerPolicyInvalidation(String id, String name, String serverId) {
cache.policyUpdated(id, name, serverId, invalidations);
PolicyAdapter adapter = managedPolicies.get(id);
if (adapter != null) adapter.invalidateFlag();
invalidationEvents.add(PolicyUpdatedEvent.create(id, name, serverId));
}
public ResourceServerStore getResourceServerStoreDelegate() {
return getDelegate().getResourceServerStore();
}
public ScopeStore getScopeStoreDelegate() {
return getDelegate().getScopeStore();
}
public ResourceStore getResourceStoreDelegate() {
return getDelegate().getResourceStore();
}
public PolicyStore getPolicyStoreDelegate() {
return getDelegate().getPolicyStore();
}
public static String getResourceServerByClientCacheKey(String clientId) {
return "resource.server.client.id." + clientId;
}
public static String getScopeByNameCacheKey(String name, String serverId) {
return "scope.name." + name + "." + serverId;
}
public static String getResourceByNameCacheKey(String name, String serverId) {
return "resource.name." + name + "." + serverId;
}
public static String getPolicyByNameCacheKey(String name, String serverId) {
return "policy.name." + name + "." + serverId;
}
public StoreFactory getDelegate() {
if (delegate != null) return delegate;
delegate = session.getProvider(StoreFactory.class);
return delegate;
}
protected class ResourceServerCache implements ResourceServerStore {
@Override
public ResourceServer create(String clientId) {
ResourceServer server = getResourceServerStoreDelegate().create(clientId);
registerResourceServerInvalidation(server.getId(), server.getClientId());
return server;
}
@Override
public void delete(String id) {
if (id == null) return;
ResourceServer server = findById(id);
if (server == null) return;
cache.invalidateObject(id);
invalidationEvents.add(ResourceServerRemovedEvent.create(id, server.getClientId()));
cache.resourceServerRemoval(id, server.getClientId(), invalidations);
getResourceServerStoreDelegate().delete(id);
}
@Override
public ResourceServer findById(String id) {
if (id == null) return null;
CachedResourceServer cached = cache.get(id, CachedResourceServer.class);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getId());
}
boolean wasCached = false;
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
ResourceServer model = getResourceServerStoreDelegate().findById(id);
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedResourceServer(loaded, model);
cache.addRevisioned(cached, startupRevision);
wasCached =true;
} else if (invalidations.contains(id)) {
return getResourceServerStoreDelegate().findById(id);
} else if (managedResourceServers.containsKey(id)) {
return managedResourceServers.get(id);
}
ResourceServerAdapter adapter = new ResourceServerAdapter(cached, StoreFactoryCacheSession.this);
managedResourceServers.put(id, adapter);
return adapter;
}
@Override
public ResourceServer findByClient(String clientId) {
String cacheKey = getResourceServerByClientCacheKey(clientId);
ResourceServerListQuery query = cache.get(cacheKey, ResourceServerListQuery.class);
if (query != null) {
logger.tracev("ResourceServer by clientId cache hit: {0}", clientId);
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
ResourceServer model = getResourceServerStoreDelegate().findByClient(clientId);
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new ResourceServerListQuery(loaded, cacheKey, model.getId());
cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getResourceServerStoreDelegate().findByClient(clientId);
} else {
String serverId = query.getResourceServers().iterator().next();
if (invalidations.contains(serverId)) {
return getResourceServerStoreDelegate().findByClient(clientId);
}
return findById(serverId);
}
}
}
protected class ScopeCache implements ScopeStore {
@Override
public Scope create(String name, ResourceServer resourceServer) {
Scope scope = getScopeStoreDelegate().create(name, resourceServer);
registerScopeInvalidation(scope.getId(), scope.getName(), resourceServer.getId());
return scope;
}
@Override
public void delete(String id) {
if (id == null) return;
Scope scope = findById(id, null);
if (scope == null) return;
cache.invalidateObject(id);
invalidationEvents.add(ScopeRemovedEvent.create(id, scope.getName(), scope.getResourceServer().getId()));
cache.scopeRemoval(id, scope.getName(), scope.getResourceServer().getId(), invalidations);
getScopeStoreDelegate().delete(id);
}
@Override
public Scope findById(String id, String resourceServerId) {
if (id == null) return null;
CachedScope cached = cache.get(id, CachedScope.class);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getId());
}
boolean wasCached = false;
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
Scope model = getScopeStoreDelegate().findById(id, resourceServerId);
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedScope(loaded, model);
cache.addRevisioned(cached, startupRevision);
wasCached =true;
} else if (invalidations.contains(id)) {
return getScopeStoreDelegate().findById(id, resourceServerId);
} else if (managedScopes.containsKey(id)) {
return managedScopes.get(id);
}
ScopeAdapter adapter = new ScopeAdapter(cached, StoreFactoryCacheSession.this);
managedScopes.put(id, adapter);
return adapter;
}
@Override
public Scope findByName(String name, String resourceServerId) {
if (name == null) return null;
String cacheKey = getScopeByNameCacheKey(name, resourceServerId);
ScopeListQuery query = cache.get(cacheKey, ScopeListQuery.class);
if (query != null) {
logger.tracev("scope by name cache hit: {0}", name);
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
Scope model = getScopeStoreDelegate().findByName(name, resourceServerId);
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new ScopeListQuery(loaded, cacheKey, model.getId(), resourceServerId);
cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getScopeStoreDelegate().findByName(name, resourceServerId);
} else {
String id = query.getScopes().iterator().next();
if (invalidations.contains(id)) {
return getScopeStoreDelegate().findByName(name, resourceServerId);
}
return findById(id, query.getResourceServerId());
}
}
@Override
public List<Scope> findByResourceServer(String id) {
return getScopeStoreDelegate().findByResourceServer(id);
}
@Override
public List<Scope> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getScopeStoreDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
}
protected class ResourceCache implements ResourceStore {
@Override
public Resource create(String name, ResourceServer resourceServer, String owner) {
Resource resource = getResourceStoreDelegate().create(name, resourceServer, owner);
registerResourceInvalidation(resource.getId(), resource.getName(), resourceServer.getId());
return resource;
}
@Override
public void delete(String id) {
if (id == null) return;
Resource resource = findById(id, null);
if (resource == null) return;
cache.invalidateObject(id);
invalidationEvents.add(ResourceRemovedEvent.create(id, resource.getName(), resource.getResourceServer().getId()));
cache.resourceRemoval(id, resource.getName(), resource.getResourceServer().getId(), invalidations);
getResourceStoreDelegate().delete(id);
}
@Override
public Resource findById(String id, String resourceServerId) {
if (id == null) return null;
CachedResource cached = cache.get(id, CachedResource.class);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getId());
}
boolean wasCached = false;
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
Resource model = getResourceStoreDelegate().findById(id, resourceServerId);
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedResource(loaded, model);
cache.addRevisioned(cached, startupRevision);
wasCached =true;
} else if (invalidations.contains(id)) {
return getResourceStoreDelegate().findById(id, resourceServerId);
} else if (managedResources.containsKey(id)) {
return managedResources.get(id);
}
ResourceAdapter adapter = new ResourceAdapter(cached, StoreFactoryCacheSession.this);
managedResources.put(id, adapter);
return adapter;
}
@Override
public Resource findByName(String name, String resourceServerId) {
if (name == null) return null;
String cacheKey = getResourceByNameCacheKey(name, resourceServerId);
ResourceListQuery query = cache.get(cacheKey, ResourceListQuery.class);
if (query != null) {
logger.tracev("resource by name cache hit: {0}", name);
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
Resource model = getResourceStoreDelegate().findByName(name, resourceServerId);
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new ResourceListQuery(loaded, cacheKey, model.getId(), resourceServerId);
cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getResourceStoreDelegate().findByName(name, resourceServerId);
} else {
String id = query.getResources().iterator().next();
if (invalidations.contains(id)) {
return getResourceStoreDelegate().findByName(name, resourceServerId);
}
return findById(id, query.getResourceServerId());
}
}
@Override
public List<Resource> findByOwner(String ownerId, String resourceServerId) {
return getResourceStoreDelegate().findByOwner(ownerId, resourceServerId);
}
@Override
public List<Resource> findByUri(String uri, String resourceServerId) {
return getResourceStoreDelegate().findByUri(uri, resourceServerId);
}
@Override
public List<Resource> findByResourceServer(String resourceServerId) {
return getResourceStoreDelegate().findByResourceServer(resourceServerId);
}
@Override
public List<Resource> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getResourceStoreDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<Resource> findByScope(List<String> ids, String resourceServerId) {
return getResourceStoreDelegate().findByScope(ids, resourceServerId);
}
@Override
public List<Resource> findByType(String type, String resourceServerId) {
return getResourceStoreDelegate().findByType(type, resourceServerId);
}
}
protected class PolicyCache implements PolicyStore {
@Override
public Policy create(AbstractPolicyRepresentation representation, ResourceServer resourceServer) {
Policy resource = getPolicyStoreDelegate().create(representation, resourceServer);
registerPolicyInvalidation(resource.getId(), resource.getName(), resourceServer.getId());
return resource;
}
@Override
public void delete(String id) {
if (id == null) return;
Policy policy = findById(id, null);
if (policy == null) return;
cache.invalidateObject(id);
invalidationEvents.add(PolicyRemovedEvent.create(id, policy.getName(), policy.getResourceServer().getId()));
cache.policyRemoval(id, policy.getName(), policy.getResourceServer().getId(), invalidations);
getPolicyStoreDelegate().delete(id);
}
@Override
public Policy findById(String id, String resourceServerId) {
if (id == null) return null;
CachedPolicy cached = cache.get(id, CachedPolicy.class);
if (cached != null) {
logger.tracev("by id cache hit: {0}", cached.getId());
}
boolean wasCached = false;
if (cached == null) {
Long loaded = cache.getCurrentRevision(id);
Policy model = getPolicyStoreDelegate().findById(id, resourceServerId);
if (model == null) return null;
if (invalidations.contains(id)) return model;
cached = new CachedPolicy(loaded, model);
cache.addRevisioned(cached, startupRevision);
wasCached =true;
} else if (invalidations.contains(id)) {
return getPolicyStoreDelegate().findById(id, resourceServerId);
} else if (managedPolicies.containsKey(id)) {
return managedPolicies.get(id);
}
PolicyAdapter adapter = new PolicyAdapter(cached, StoreFactoryCacheSession.this);
managedPolicies.put(id, adapter);
return adapter;
}
@Override
public Policy findByName(String name, String resourceServerId) {
if (name == null) return null;
String cacheKey = getPolicyByNameCacheKey(name, resourceServerId);
PolicyListQuery query = cache.get(cacheKey, PolicyListQuery.class);
if (query != null) {
logger.tracev("policy by name cache hit: {0}", name);
}
if (query == null) {
Long loaded = cache.getCurrentRevision(cacheKey);
Policy model = getPolicyStoreDelegate().findByName(name, resourceServerId);
if (model == null) return null;
if (invalidations.contains(model.getId())) return model;
query = new PolicyListQuery(loaded, cacheKey, model.getId(), resourceServerId);
cache.addRevisioned(query, startupRevision);
return model;
} else if (invalidations.contains(cacheKey)) {
return getPolicyStoreDelegate().findByName(name, resourceServerId);
} else {
String id = query.getPolicies().iterator().next();
if (invalidations.contains(id)) {
return getPolicyStoreDelegate().findByName(name, resourceServerId);
}
return findById(id, query.getResourceServerId());
}
}
@Override
public List<Policy> findByResourceServer(String resourceServerId) {
return getPolicyStoreDelegate().findByResourceServer(resourceServerId);
}
@Override
public List<Policy> findByResourceServer(Map<String, String[]> attributes, String resourceServerId, int firstResult, int maxResult) {
return getPolicyStoreDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult);
}
@Override
public List<Policy> findByResource(String resourceId, String resourceServerId) {
return getPolicyStoreDelegate().findByResource(resourceId, resourceServerId);
}
@Override
public List<Policy> findByResourceType(String resourceType, String resourceServerId) {
return getPolicyStoreDelegate().findByResourceType(resourceType, resourceServerId);
}
@Override
public List<Policy> findByScopeIds(List<String> scopeIds, String resourceServerId) {
return getPolicyStoreDelegate().findByScopeIds(scopeIds, resourceServerId);
}
@Override
public List<Policy> findByType(String type, String resourceServerId) {
return getPolicyStoreDelegate().findByType(type, resourceServerId);
}
@Override
public List<Policy> findDependentPolicies(String id, String resourceServerId) {
return getPolicyStoreDelegate().findDependentPolicies(id, resourceServerId);
}
}
}