diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java index cbdcf2c511..45bd53f23a 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/PermissionTicketAdapter.java @@ -42,7 +42,7 @@ public class PermissionTicketAdapter implements PermissionTicket, CachedModel { protected boolean isUpdated() { if (updated != null) return true; if (!invalidated) return false; - updated = cacheSession.getPolicyStoreDelegate().findById(cacheSession.getResourceServerStore().findById(cached.getResourceServerId()), cached.getId()); + updated = cacheSession.getPolicyStoreDelegate().findById(cacheSession.getResourceServerStore().findById(null, cached.getResourceServerId()), cached.getId()); if (updated == null) throw new IllegalStateException("Not found in database"); return true; } @@ -112,7 +112,7 @@ public class PolicyAdapter implements Policy, CachedModel { @Override public ResourceServer getResourceServer() { - return cacheSession.getResourceServerStore().findById(cached.getResourceServerId()); + return cacheSession.getResourceServerStore().findById(null, cached.getResourceServerId()); } @Override @@ -208,7 +208,7 @@ public class PolicyAdapter implements Policy, CachedModel { PolicyStore policyStore = cacheSession.getPolicyStore(); String resourceServerId = cached.getResourceServerId(); for (String id : cached.getAssociatedPoliciesIds(modelSupplier)) { - Policy policy = policyStore.findById(cacheSession.getResourceServerStore().findById(resourceServerId), id); + Policy policy = policyStore.findById(cacheSession.getResourceServerStore().findById(null, resourceServerId), id); cacheSession.cachePolicy(policy); associatedPolicies.add(policy); } @@ -325,6 +325,6 @@ public class PolicyAdapter implements Policy, CachedModel { } private Policy getPolicyModel() { - return cacheSession.getPolicyStoreDelegate().findById(cacheSession.getResourceServerStore().findById(cached.getResourceServerId()), cached.getId()); + return cacheSession.getPolicyStoreDelegate().findById(cacheSession.getResourceServerStore().findById(null, cached.getResourceServerId()), cached.getId()); } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java index b07e9534b0..8286eea8fb 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceAdapter.java @@ -134,7 +134,7 @@ public class ResourceAdapter implements Resource, CachedModel { @Override public ResourceServer getResourceServer() { - return cacheSession.getResourceServerStoreDelegate().findById(cached.getResourceServerId()); + return cacheSession.getResourceServerStoreDelegate().findById(null, cached.getResourceServerId()); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceServerAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceServerAdapter.java index f2217b4b4d..2453b6d485 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceServerAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/ResourceServerAdapter.java @@ -18,6 +18,8 @@ package org.keycloak.models.cache.infinispan.authorization; import org.keycloak.authorization.model.CachedModel; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; import org.keycloak.models.cache.infinispan.authorization.entities.CachedResourceServer; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; @@ -40,7 +42,7 @@ public class ResourceServerAdapter implements ResourceServer, CachedModel { @Override public ResourceServer getResourceServer() { - return cacheSession.getResourceServerStore().findById(cached.getResourceServerId()); + return cacheSession.getResourceServerStore().findById(null, cached.getResourceServerId()); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java index 1974f1414f..258f1c81e2 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/authorization/StoreFactoryCacheSession.java @@ -47,6 +47,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; import org.keycloak.models.cache.infinispan.authorization.entities.CachedPermissionTicket; import org.keycloak.models.cache.infinispan.authorization.entities.CachedPolicy; @@ -309,7 +310,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { return Collections.emptySet(); } - ResourceServer resourceServer = getResourceServerStore().findById(serverId); + ResourceServer resourceServer = getResourceServerStore().findById(null, serverId); return resources.stream().map(resourceId -> { Resource resource = getResourceStore().findById(resourceServer, resourceId); String type = resource.getType(); @@ -450,7 +451,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { public void delete(ClientModel client) { String id = client.getId(); if (id == null) return; - ResourceServer server = findById(id); + ResourceServer server = findById(null, id); if (server == null) return; cache.invalidateObject(id); @@ -461,7 +462,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { } @Override - public ResourceServer findById(String id) { + public ResourceServer findById(RealmModel realm, String id) { if (id == null) return null; CachedResourceServer cached = cache.get(id, CachedResourceServer.class); if (cached != null) { @@ -471,7 +472,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { if (cached == null) { Long loaded = cache.getCurrentRevision(id); if (! modelMightExist(id)) return null; - ResourceServer model = getResourceServerStoreDelegate().findById(id); + ResourceServer model = getResourceServerStoreDelegate().findById(realm, id); if (model == null) { setModelDoesNotExists(id, loaded); return null; @@ -480,7 +481,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { cached = new CachedResourceServer(loaded, model); cache.addRevisioned(cached, startupRevision); } else if (invalidations.contains(id)) { - return getResourceServerStoreDelegate().findById(id); + return getResourceServerStoreDelegate().findById(realm, id); } else if (managedResourceServers.containsKey(id)) { return managedResourceServers.get(id); } @@ -491,7 +492,7 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { @Override public ResourceServer findByClient(ClientModel client) { - return findById(client.getId()); + return findById(null, client.getId()); } } @@ -1238,13 +1239,13 @@ public class StoreFactoryCacheSession implements CachedStoreFactoryProvider { } @Override - public List findGrantedResources(String requester, String name, Integer first, Integer max) { - return getPermissionTicketStoreDelegate().findGrantedResources(requester, name, first, max); + public List findGrantedResources(RealmModel realm, String requester, String name, Integer first, Integer max) { + return getPermissionTicketStoreDelegate().findGrantedResources(realm, requester, name, first, max); } @Override - public List findGrantedOwnerResources(String owner, Integer firstResult, Integer maxResults) { - return getPermissionTicketStoreDelegate().findGrantedOwnerResources(owner, firstResult, maxResults); + public List findGrantedOwnerResources(RealmModel realm, String owner, Integer firstResult, Integer maxResults) { + return getPermissionTicketStoreDelegate().findGrantedOwnerResources(realm, owner, firstResult, maxResults); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java index 9ef7ef83c8..b56d70144b 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPermissionTicketStore.java @@ -41,6 +41,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PermissionTicketStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.common.util.Time; +import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.LockModeType; @@ -290,7 +291,7 @@ public class JPAPermissionTicketStore implements PermissionTicketStore { } @Override - public List findGrantedResources(String requester, String name, Integer first, Integer max) { + public List findGrantedResources(RealmModel realm, String requester, String name, Integer first, Integer max) { TypedQuery query = name == null ? entityManager.createNamedQuery("findGrantedResources", String.class) : entityManager.createNamedQuery("findGrantedResourcesByName", String.class); @@ -318,7 +319,7 @@ public class JPAPermissionTicketStore implements PermissionTicketStore { } @Override - public List findGrantedOwnerResources(String owner, Integer firstResult, Integer maxResults) { + public List findGrantedOwnerResources(RealmModel realm, String owner, Integer firstResult, Integer maxResults) { TypedQuery query = entityManager.createNamedQuery("findGrantedOwnerResources", String.class); query.setFlushMode(FlushModeType.COMMIT); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java index 2c9bc986a3..3f1b2cafbf 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceServerStore.java @@ -26,6 +26,7 @@ import org.keycloak.authorization.jpa.entities.ScopeEntity; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; import org.keycloak.storage.StorageId; import javax.persistence.EntityManager; @@ -58,7 +59,7 @@ public class JPAResourceServerStore implements ResourceServerStore { this.entityManager.persist(entity); - return new ResourceServerAdapter(entity, entityManager, provider.getStoreFactory()); + return new ResourceServerAdapter(client.getRealm(), entity, entityManager, provider.getStoreFactory()); } @Override @@ -122,14 +123,14 @@ public class JPAResourceServerStore implements ResourceServerStore { } @Override - public ResourceServer findById(String id) { + public ResourceServer findById(RealmModel realm, String id) { ResourceServerEntity entity = entityManager.find(ResourceServerEntity.class, id); if (entity == null) return null; - return new ResourceServerAdapter(entity, entityManager, provider.getStoreFactory()); + return new ResourceServerAdapter(provider.getRealm(), entity, entityManager, provider.getStoreFactory()); } @Override public ResourceServer findByClient(ClientModel client) { - return findById(client.getId()); + return findById(null, client.getId()); } } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java index 4b3ce67539..3e549ad48b 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/PermissionTicketAdapter.java @@ -20,7 +20,6 @@ import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy; import javax.persistence.EntityManager; -import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.jpa.entities.PermissionTicketEntity; import org.keycloak.authorization.jpa.entities.PolicyEntity; import org.keycloak.authorization.jpa.entities.ScopeEntity; @@ -91,7 +90,7 @@ public class PermissionTicketAdapter implements PermissionTicket, JpaModel, AuthorizationStoreFactory, EnvironmentDependentProviderFactory { +public class MapAuthorizationStoreFactory implements AmphibianProviderFactory, AuthorizationStoreFactory, EnvironmentDependentProviderFactory, InvalidationHandler { public static final String PROVIDER_ID = AbstractMapProviderFactory.PROVIDER_ID; private Config.Scope storageConfigScope; + private final String uniqueKey = MapAuthorizationStoreFactory.class.getName() + uniqueCounter.incrementAndGet(); @Override public StoreFactory create(KeycloakSession session) { + MapAuthorizationStore authzStore = session.getAttribute(uniqueKey, MapAuthorizationStore.class); + + if (authzStore != null) return authzStore; + MapStorageProviderFactory storageProviderFactory = (MapStorageProviderFactory) getComponentFactory(session.getKeycloakSessionFactory(), MapStorageProvider.class, storageConfigScope, MapStorageSpi.NAME); final MapStorageProvider mapStorageProvider = storageProviderFactory.create(session); AuthorizationProvider provider = session.getProvider(AuthorizationProvider.class); + MapStorage permissionTicketStore = mapStorageProvider.getStorage(PermissionTicket.class); + MapStorage policyStore = mapStorageProvider.getStorage(Policy.class); + MapStorage resourceServerStore = mapStorageProvider.getStorage(ResourceServer.class); + MapStorage resourceStore = mapStorageProvider.getStorage(Resource.class); + MapStorage scopeStore = mapStorageProvider.getStorage(Scope.class); - MapStorage permissionTicketStore; - MapStorage policyStore; - MapStorage resourceServerStore; - MapStorage resourceStore; - MapStorage scopeStore; + authzStore = new MapAuthorizationStore(session, + permissionTicketStore, + policyStore, + resourceServerStore, + resourceStore, + scopeStore, + provider + ); - permissionTicketStore = mapStorageProvider.getStorage(PermissionTicket.class); - policyStore = mapStorageProvider.getStorage(Policy.class); - resourceServerStore = mapStorageProvider.getStorage(ResourceServer.class); - resourceStore = mapStorageProvider.getStorage(Resource.class); - scopeStore = mapStorageProvider.getStorage(Scope.class); - - return new MapAuthorizationStore(session, - permissionTicketStore, - policyStore, - resourceServerStore, - resourceStore, - scopeStore, - provider - ); + session.setAttribute(uniqueKey, authzStore); + return authzStore; } @Override @@ -86,11 +89,6 @@ public class MapAuthorizationStoreFactory implements AmphibianProviderFactory tx; + private final KeycloakSession session; public MapPermissionTicketStore(KeycloakSession session, MapStorage permissionTicketStore, AuthorizationProvider provider) { this.authorizationProvider = provider; this.tx = permissionTicketStore.createTransaction(session); session.getTransactionManager().enlist(tx); + this.session = session; } - private PermissionTicket entityToAdapter(MapPermissionTicketEntity origEntity) { - if (origEntity == null) return null; - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return new MapPermissionTicketAdapter(origEntity, authorizationProvider.getStoreFactory()); + private Function entityToAdapterFunc(ResourceServer resourceServer) { + return origEntity -> new MapPermissionTicketAdapter(resourceServer == null ? findResourceServer(origEntity) : resourceServer, origEntity, authorizationProvider.getStoreFactory()); + } + + private ResourceServer findResourceServer(MapPermissionTicketEntity entity) { + RealmModel realm = session.realms().getRealm(entity.getRealmId()); + return authorizationProvider.getStoreFactory().getResourceServerStore().findById(realm, entity.getResourceServerId()); } private DefaultModelCriteria forResourceServer(ResourceServer resourceServer) { @@ -124,10 +130,11 @@ public class MapPermissionTicketStore implements PermissionTicketStore { entity.setOwner(owner); entity.setResourceServerId(resourceServer.getId()); + entity.setRealmId(resourceServer.getRealm().getId()); entity = tx.create(entity); - return entityToAdapter(entity); + return entity == null ? null : entityToAdapterFunc(resourceServer).apply(entity); } @Override @@ -148,7 +155,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -157,7 +164,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); return tx.read(withCriteria(forResourceServer(resourceServer))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -167,7 +174,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.OWNER, Operator.EQ, owner))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -177,7 +184,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -187,7 +194,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.SCOPE_ID, Operator.EQ, scope.getId()))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -216,7 +223,7 @@ public class MapPermissionTicketStore implements PermissionTicketStore { ); return tx.read(withCriteria(mcb).pagination(firstResult, maxResult, SearchableFields.ID)) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -272,10 +279,11 @@ public class MapPermissionTicketStore implements PermissionTicketStore { } @Override - public List findGrantedResources(String requester, String name, Integer first, Integer max) { + public List findGrantedResources(RealmModel realm, String requester, String name, Integer first, Integer max) { DefaultModelCriteria mcb = criteria(); mcb = mcb.compare(SearchableFields.REQUESTER, Operator.EQ, requester) - .compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS); + .compare(SearchableFields.GRANTED_TIMESTAMP, Operator.EXISTS) + .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); Function ticketResourceMapper; @@ -288,13 +296,13 @@ public class MapPermissionTicketStore implements PermissionTicketStore { filterOptionMap.put(Resource.FilterOption.ID, new String[] {ticket.getResourceId()}); filterOptionMap.put(Resource.FilterOption.NAME, new String[] {name}); - List resource = resourceStore.findByResourceServer(resourceServerStore.findById(ticket.getResourceServerId()), filterOptionMap, -1, 1); + List resource = resourceStore.findByResourceServer(resourceServerStore.findById(realm, ticket.getResourceServerId()), filterOptionMap, -1, 1); return resource.isEmpty() ? null : resource.get(0); }; } else { ticketResourceMapper = ticket -> resourceStore - .findById(resourceServerStore.findById(ticket.getResourceServerId()), ticket.getResourceId()); + .findById(resourceServerStore.findById(realm, ticket.getResourceServerId()), ticket.getResourceId()); } return paginatedStream(tx.read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) @@ -305,16 +313,32 @@ public class MapPermissionTicketStore implements PermissionTicketStore { } @Override - public List findGrantedOwnerResources(String owner, Integer firstResult, Integer maxResults) { + public List findGrantedOwnerResources(RealmModel realm, String owner, Integer firstResult, Integer maxResults) { DefaultModelCriteria mcb = criteria(); - mcb = mcb.compare(SearchableFields.OWNER, Operator.EQ, owner); + mcb = mcb.compare(SearchableFields.OWNER, Operator.EQ, owner) + .compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); ResourceServerStore resourceServerStore = authorizationProvider.getStoreFactory().getResourceServerStore(); return paginatedStream(tx.read(withCriteria(mcb).orderBy(SearchableFields.RESOURCE_ID, ASCENDING)) .filter(distinctByKey(MapPermissionTicketEntity::getResourceId)), firstResult, maxResults) - .map(ticket -> resourceStore.findById(resourceServerStore.findById(ticket.getResourceServerId()), ticket.getResourceId())) + .map(ticket -> resourceStore.findById(resourceServerStore.findById(realm, ticket.getResourceServerId()), ticket.getResourceId())) .collect(Collectors.toList()); } + + public void preRemove(RealmModel realm) { + LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + tx.delete(withCriteria(mcb)); + } + + public void preRemove(ResourceServer resourceServer) { + LOG.tracef("preRemove(%s)%s", resourceServer, getShortStackTrace()); + + tx.delete(withCriteria(forResourceServer(resourceServer))); + } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java index 9b78fc1784..caebc233e9 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapPolicyStore.java @@ -27,6 +27,7 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; import org.keycloak.models.map.authorization.adapter.MapPolicyAdapter; import org.keycloak.models.map.authorization.entity.MapPolicyEntity; import org.keycloak.models.map.authorization.entity.MapPolicyEntityImpl; @@ -41,6 +42,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; @@ -52,17 +54,22 @@ public class MapPolicyStore implements PolicyStore { private static final Logger LOG = Logger.getLogger(MapPolicyStore.class); private final AuthorizationProvider authorizationProvider; final MapKeycloakTransaction tx; + private final KeycloakSession session; public MapPolicyStore(KeycloakSession session, MapStorage policyStore, AuthorizationProvider provider) { this.authorizationProvider = provider; this.tx = policyStore.createTransaction(session); session.getTransactionManager().enlist(tx); + this.session = session; } - private Policy entityToAdapter(MapPolicyEntity origEntity) { - if (origEntity == null) return null; - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return new MapPolicyAdapter(origEntity, authorizationProvider.getStoreFactory()); + private Function entityToAdapterFunc(ResourceServer resourceServer) { + return origEntity -> new MapPolicyAdapter(resourceServer == null ? findResourceServer(origEntity) : resourceServer, origEntity, authorizationProvider.getStoreFactory()); + } + + private ResourceServer findResourceServer(MapPolicyEntity entity) { + RealmModel realm = session.realms().getRealm(entity.getRealmId()); + return authorizationProvider.getStoreFactory().getResourceServerStore().findById(realm, entity.getResourceServerId()); } private DefaultModelCriteria forResourceServer(ResourceServer resourceServer) { @@ -92,10 +99,11 @@ public class MapPolicyStore implements PolicyStore { entity.setType(representation.getType()); entity.setName(representation.getName()); entity.setResourceServerId(resourceServer.getId()); - + entity.setRealmId(resourceServer.getRealm().getId()); + entity = tx.create(entity); - return entityToAdapter(entity); + return entity == null ? null : entityToAdapterFunc(resourceServer).apply(entity); } @Override @@ -111,7 +119,7 @@ public class MapPolicyStore implements PolicyStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -122,7 +130,7 @@ public class MapPolicyStore implements PolicyStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.NAME, Operator.EQ, name))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -131,7 +139,7 @@ public class MapPolicyStore implements PolicyStore { LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); return tx.read(withCriteria(forResourceServer(resourceServer))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -201,7 +209,7 @@ public class MapPolicyStore implements PolicyStore { tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.RESOURCE_ID, Operator.EQ, resource.getId()))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } @@ -209,7 +217,7 @@ public class MapPolicyStore implements PolicyStore { public void findByResourceType(ResourceServer resourceServer, String type, Consumer policyConsumer) { tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.CONFIG, Operator.LIKE, (Object[]) new String[]{"defaultResourceType", type}))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(policyConsumer); } @@ -217,7 +225,7 @@ public class MapPolicyStore implements PolicyStore { public List findByScopes(ResourceServer resourceServer, List scopes) { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes.stream().map(Scope::getId)))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -235,14 +243,14 @@ public class MapPolicyStore implements PolicyStore { .compare(SearchableFields.CONFIG, Operator.NOT_EXISTS, (Object[]) new String[] {"defaultResourceType"}); } - tx.read(withCriteria(mcb)).map(this::entityToAdapter).forEach(consumer); + tx.read(withCriteria(mcb)).map(entityToAdapterFunc(resourceServer)).forEach(consumer); } @Override public List findByType(ResourceServer resourceServer, String type) { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -250,7 +258,22 @@ public class MapPolicyStore implements PolicyStore { public List findDependentPolicies(ResourceServer resourceServer, String id) { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.ASSOCIATED_POLICY_ID, Operator.EQ, id))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } + + public void preRemove(RealmModel realm) { + LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + tx.delete(withCriteria(mcb)); + } + + public void preRemove(ResourceServer resourceServer) { + LOG.tracef("preRemove(%s)%s", resourceServer, getShortStackTrace()); + + tx.delete(withCriteria(forResourceServer(resourceServer))); + } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java index f95d0043e6..ebe0e0de61 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceServerStore.java @@ -19,28 +19,31 @@ package org.keycloak.models.map.authorization; import org.jboss.logging.Logger; import org.keycloak.authorization.AuthorizationProvider; -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.store.PermissionTicketStore; -import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.model.ResourceServer.SearchableFields; import org.keycloak.authorization.store.ResourceServerStore; -import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.authorization.store.ScopeStore; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelException; +import org.keycloak.models.RealmModel; import org.keycloak.models.map.authorization.adapter.MapResourceServerAdapter; import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; import org.keycloak.models.map.authorization.entity.MapResourceServerEntityImpl; import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapStorage; +import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; +import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; import org.keycloak.storage.StorageId; +import java.util.Objects; +import java.util.function.Function; + import static org.keycloak.common.util.StackUtil.getShortStackTrace; +import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.RESOURCE_SERVER_AFTER_REMOVE; +import static org.keycloak.models.map.common.AbstractMapProviderFactory.MapProviderObjectType.RESOURCE_SERVER_BEFORE_REMOVE; +import static org.keycloak.models.map.storage.QueryParameters.withCriteria; +import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria; public class MapResourceServerStore implements ResourceServerStore { @@ -54,10 +57,8 @@ public class MapResourceServerStore implements ResourceServerStore { session.getTransactionManager().enlist(tx); } - private ResourceServer entityToAdapter(MapResourceServerEntity origEntity) { - if (origEntity == null) return null; - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return new MapResourceServerAdapter(origEntity, authorizationProvider.getStoreFactory()); + private Function entityToAdapterFunc(RealmModel realmModel) { + return origEntity -> new MapResourceServerAdapter(realmModel, origEntity, authorizationProvider.getStoreFactory()); } @Override @@ -76,47 +77,29 @@ public class MapResourceServerStore implements ResourceServerStore { } MapResourceServerEntity entity = new MapResourceServerEntityImpl(); - entity.setId(clientId); + entity.setClientId(clientId); + entity.setRealmId(client.getRealm().getId()); entity = tx.create(entity); - return entityToAdapter(entity); + return entity == null ? null : entityToAdapterFunc(client.getRealm()).apply(entity); } @Override public void delete(ClientModel client) { - LOG.tracef("delete(%s, %s)%s", client.getClientId(), getShortStackTrace()); + LOG.tracef("delete(%s)%s", client.getClientId(), getShortStackTrace()); ResourceServer resourceServer = findByClient(client); if (resourceServer == null) return; - String id = resourceServer.getId(); + authorizationProvider.getKeycloakSession().invalidate(RESOURCE_SERVER_BEFORE_REMOVE, resourceServer); - // TODO: Simplify the following, ideally by leveraging triggers, stored procedures or ref integrity - PolicyStore policyStore = authorizationProvider.getStoreFactory().getPolicyStore(); - policyStore.findByResourceServer(resourceServer).stream() - .map(Policy::getId) - .forEach(policyStore::delete); + tx.delete(resourceServer.getId()); - PermissionTicketStore permissionTicketStore = authorizationProvider.getStoreFactory().getPermissionTicketStore(); - permissionTicketStore.findByResourceServer(resourceServer).stream() - .map(PermissionTicket::getId) - .forEach(permissionTicketStore::delete); - - ResourceStore resourceStore = authorizationProvider.getStoreFactory().getResourceStore(); - resourceStore.findByResourceServer(resourceServer).stream() - .map(Resource::getId) - .forEach(resourceStore::delete); - - ScopeStore scopeStore = authorizationProvider.getStoreFactory().getScopeStore(); - scopeStore.findByResourceServer(resourceServer).stream() - .map(Scope::getId) - .forEach(scopeStore::delete); - - tx.delete(id); + authorizationProvider.getKeycloakSession().invalidate(RESOURCE_SERVER_AFTER_REMOVE, resourceServer); } @Override - public ResourceServer findById(String id) { + public ResourceServer findById(RealmModel realm, String id) { LOG.tracef("findById(%s)%s", id, getShortStackTrace()); if (id == null) { @@ -124,11 +107,29 @@ public class MapResourceServerStore implements ResourceServerStore { } MapResourceServerEntity entity = tx.read(id); - return entityToAdapter(entity); + return (entity == null || !Objects.equals(realm.getId(), entity.getRealmId())) ? null : entityToAdapterFunc(realm).apply(entity); } @Override public ResourceServer findByClient(ClientModel client) { - return findById(client.getId()); + LOG.tracef("findByClient(%s) in realm(%s)%s", client.getClientId(), client.getRealm().getName(), getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.CLIENT_ID, Operator.EQ, client.getId()); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, client.getRealm().getId()); + + return tx.read(withCriteria(mcb)) + .map(entityToAdapterFunc(client.getRealm())) + .findFirst() + .orElse(null); + } + + public void preRemove(RealmModel realm) { + LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + tx.delete(withCriteria(mcb)); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java index f0655da6b2..c1c6b42748 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapResourceStore.java @@ -26,20 +26,22 @@ import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; import org.keycloak.models.map.authorization.adapter.MapResourceAdapter; import org.keycloak.models.map.authorization.entity.MapResourceEntity; import org.keycloak.models.map.authorization.entity.MapResourceEntityImpl; import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapStorage; - import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; + import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; @@ -51,17 +53,22 @@ public class MapResourceStore implements ResourceStore { private static final Logger LOG = Logger.getLogger(MapResourceStore.class); private final AuthorizationProvider authorizationProvider; final MapKeycloakTransaction tx; + private final KeycloakSession session; public MapResourceStore(KeycloakSession session, MapStorage resourceStore, AuthorizationProvider provider) { this.tx = resourceStore.createTransaction(session); session.getTransactionManager().enlist(tx); authorizationProvider = provider; + this.session = session; } - private Resource entityToAdapter(MapResourceEntity origEntity) { - if (origEntity == null) return null; - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return new MapResourceAdapter(origEntity, authorizationProvider.getStoreFactory()); + private Function entityToAdapterFunc(final ResourceServer resourceServer) { + return origEntity -> new MapResourceAdapter(resourceServer == null ? findResourceServer(origEntity) : resourceServer, origEntity, authorizationProvider.getStoreFactory()); + } + + private ResourceServer findResourceServer(MapResourceEntity entity) { + RealmModel realm = session.realms().getRealm(entity.getRealmId()); + return authorizationProvider.getStoreFactory().getResourceServerStore().findById(realm, entity.getResourceServerId()); } private DefaultModelCriteria forResourceServer(ResourceServer resourceServer) { @@ -90,10 +97,11 @@ public class MapResourceStore implements ResourceStore { entity.setName(name); entity.setResourceServerId(resourceServer.getId()); entity.setOwner(owner); + entity.setRealmId(resourceServer.getRealm().getId()); entity = tx.create(entity); - return entityToAdapter(entity); + return entity == null ? null : entityToAdapterFunc(resourceServer).apply(entity); } @Override @@ -110,7 +118,7 @@ public class MapResourceStore implements ResourceStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -124,7 +132,7 @@ public class MapResourceStore implements ResourceStore { tx.read(withCriteria(forResourceServer(resourceServer).compare(SearchableFields.OWNER, Operator.EQ, ownerId)) .pagination(firstResult, maxResult, SearchableFields.ID) - ).map(this::entityToAdapter) + ).map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } @@ -143,7 +151,7 @@ public class MapResourceStore implements ResourceStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.URI, Operator.EQ, uri))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -152,7 +160,7 @@ public class MapResourceStore implements ResourceStore { LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); return tx.read(withCriteria(forResourceServer(resourceServer))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -166,7 +174,7 @@ public class MapResourceStore implements ResourceStore { ); return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -203,7 +211,7 @@ public class MapResourceStore implements ResourceStore { tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.SCOPE_ID, Operator.IN, scopes.stream().map(Scope::getId)))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } @@ -214,7 +222,7 @@ public class MapResourceStore implements ResourceStore { .compare(SearchableFields.OWNER, Operator.EQ, ownerId) .compare(SearchableFields.NAME, Operator.EQ, name))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -223,7 +231,7 @@ public class MapResourceStore implements ResourceStore { LOG.tracef("findByType(%s, %s, %s)%s", type, resourceServer, consumer, getShortStackTrace()); tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } @@ -239,7 +247,7 @@ public class MapResourceStore implements ResourceStore { } tx.read(withCriteria(mcb)) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } @@ -249,7 +257,22 @@ public class MapResourceStore implements ResourceStore { tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.OWNER, Operator.NE, resourceServer.getClientId()) .compare(SearchableFields.TYPE, Operator.EQ, type))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .forEach(consumer); } + + public void preRemove(RealmModel realm) { + LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + tx.delete(withCriteria(mcb)); + } + + public void preRemove(ResourceServer resourceServer) { + LOG.tracef("preRemove(%s)%s", resourceServer, getShortStackTrace()); + + tx.delete(withCriteria(forResourceServer(resourceServer))); + } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java b/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java index 5a6579e594..b27ed6990e 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/MapScopeStore.java @@ -25,17 +25,19 @@ import org.keycloak.authorization.model.Scope.SearchableFields; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; import org.keycloak.models.map.authorization.adapter.MapScopeAdapter; import org.keycloak.models.map.authorization.entity.MapScopeEntity; import org.keycloak.models.map.authorization.entity.MapScopeEntityImpl; import org.keycloak.models.map.storage.MapKeycloakTransaction; import org.keycloak.models.map.storage.MapStorage; - import org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator; import org.keycloak.models.map.storage.criteria.DefaultModelCriteria; + import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import static org.keycloak.common.util.StackUtil.getShortStackTrace; @@ -47,17 +49,22 @@ public class MapScopeStore implements ScopeStore { private static final Logger LOG = Logger.getLogger(MapScopeStore.class); private final AuthorizationProvider authorizationProvider; final MapKeycloakTransaction tx; + private final KeycloakSession session; public MapScopeStore(KeycloakSession session, MapStorage scopeStore, AuthorizationProvider provider) { this.authorizationProvider = provider; this.tx = scopeStore.createTransaction(session); session.getTransactionManager().enlist(tx); + this.session = session; } - private Scope entityToAdapter(MapScopeEntity origEntity) { - if (origEntity == null) return null; - // Clone entity before returning back, to avoid giving away a reference to the live object to the caller - return new MapScopeAdapter(origEntity, authorizationProvider.getStoreFactory()); + private Function entityToAdapterFunc(ResourceServer resourceServer) { + return origEntity -> new MapScopeAdapter(resourceServer == null ? findResourceServer(origEntity) : resourceServer, origEntity, authorizationProvider.getStoreFactory()); + } + + private ResourceServer findResourceServer(MapScopeEntity entity) { + RealmModel realm = session.realms().getRealm(entity.getRealmId()); + return authorizationProvider.getStoreFactory().getResourceServerStore().findById(realm, entity.getResourceServerId()); } private DefaultModelCriteria forResourceServer(ResourceServer resourceServer) { @@ -86,10 +93,11 @@ public class MapScopeStore implements ScopeStore { entity.setId(id); entity.setName(name); entity.setResourceServerId(resourceServer.getId()); + entity.setRealmId(resourceServer.getRealm().getId()); entity = tx.create(entity); - return entityToAdapter(entity); + return entity == null ? null : entityToAdapterFunc(resourceServer).apply(entity); } @Override @@ -105,7 +113,7 @@ public class MapScopeStore implements ScopeStore { return tx.read(withCriteria(forResourceServer(resourceServer) .compare(SearchableFields.ID, Operator.EQ, id))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -116,7 +124,7 @@ public class MapScopeStore implements ScopeStore { return tx.read(withCriteria(forResourceServer(resourceServer).compare(SearchableFields.NAME, Operator.EQ, name))) .findFirst() - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .orElse(null); } @@ -125,7 +133,7 @@ public class MapScopeStore implements ScopeStore { LOG.tracef("findByResourceServer(%s)%s", resourceServer, getShortStackTrace()); return tx.read(withCriteria(forResourceServer(resourceServer))) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } @@ -149,7 +157,22 @@ public class MapScopeStore implements ScopeStore { } return tx.read(withCriteria(mcb).pagination(firstResult, maxResults, SearchableFields.NAME)) - .map(this::entityToAdapter) + .map(entityToAdapterFunc(resourceServer)) .collect(Collectors.toList()); } + + public void preRemove(RealmModel realm) { + LOG.tracef("preRemove(%s)%s", realm, getShortStackTrace()); + + DefaultModelCriteria mcb = criteria(); + mcb = mcb.compare(SearchableFields.REALM_ID, Operator.EQ, realm.getId()); + + tx.delete(withCriteria(mcb)); + } + + public void preRemove(ResourceServer resourceServer) { + LOG.tracef("preRemove(%s)%s", resourceServer, getShortStackTrace()); + + tx.delete(withCriteria(forResourceServer(resourceServer))); + } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java index a15eb479fb..23570d426c 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapPermissionTicketAdapter.java @@ -18,6 +18,7 @@ package org.keycloak.models.map.authorization.adapter; +import java.util.Objects; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; @@ -29,9 +30,13 @@ import org.keycloak.models.map.authorization.entity.MapPermissionTicketEntity; import static org.keycloak.authorization.UserManagedPermissionUtil.updatePolicy; public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel { - - public MapPermissionTicketAdapter(MapPermissionTicketEntity entity, StoreFactory storeFactory) { + + private final ResourceServer resourceServer; + + public MapPermissionTicketAdapter(ResourceServer resourceServer, MapPermissionTicketEntity entity, StoreFactory storeFactory) { super(entity, storeFactory); + Objects.requireNonNull(resourceServer); + this.resourceServer = resourceServer; } @Override @@ -51,13 +56,13 @@ public class MapPermissionTicketAdapter extends AbstractPermissionTicketModel { - - public MapPolicyAdapter(MapPolicyEntity entity, StoreFactory storeFactory) { + + private final ResourceServer resourceServer; + + public MapPolicyAdapter(ResourceServer resourceServer, MapPolicyEntity entity, StoreFactory storeFactory) { super(entity, storeFactory); + Objects.requireNonNull(resourceServer); + this.resourceServer = resourceServer; } @Override @@ -119,21 +124,19 @@ public class MapPolicyAdapter extends AbstractPolicyModel { @Override public ResourceServer getResourceServer() { - return storeFactory.getResourceServerStore().findById(entity.getResourceServerId()); + return resourceServer; } @Override public Set getAssociatedPolicies() { - String resourceServerId = entity.getResourceServerId(); Set ids = entity.getAssociatedPolicyIds(); return ids == null ? Collections.emptySet() : ids.stream() - .map(policyId -> storeFactory.getPolicyStore().findById(storeFactory.getResourceServerStore().findById(resourceServerId), policyId)) + .map(policyId -> storeFactory.getPolicyStore().findById(resourceServer, policyId)) .collect(Collectors.toSet()); } @Override public Set getResources() { - ResourceServer resourceServer = getResourceServer(); Set ids = entity.getResourceIds(); return ids == null ? Collections.emptySet() : ids.stream() .map(resourceId -> storeFactory.getResourceStore().findById(resourceServer, resourceId)) @@ -142,7 +145,6 @@ public class MapPolicyAdapter extends AbstractPolicyModel { @Override public Set getScopes() { - ResourceServer resourceServer = getResourceServer(); Set ids = entity.getScopeIds(); return ids == null ? Collections.emptySet() : ids.stream() .map(scopeId -> storeFactory.getScopeStore().findById(resourceServer, scopeId)) diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java index d4708bcf1e..7ccd3f95a4 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java @@ -29,13 +29,18 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; public class MapResourceAdapter extends AbstractResourceModel { - public MapResourceAdapter(MapResourceEntity entity, StoreFactory storeFactory) { + private final ResourceServer resourceServer; + + public MapResourceAdapter(ResourceServer resourceServer, MapResourceEntity entity, StoreFactory storeFactory) { super(entity, storeFactory); + Objects.requireNonNull(resourceServer); + this.resourceServer = resourceServer; } @Override @@ -111,7 +116,7 @@ public class MapResourceAdapter extends AbstractResourceModel @Override public ResourceServer getResourceServer() { - return storeFactory.getResourceServerStore().findById(entity.getResourceServerId()); + return resourceServer; } @Override @@ -143,13 +148,13 @@ public class MapResourceAdapter extends AbstractResourceModel // The scope^ was removed from the Resource // Remove permission tickets based on the scope - List permissions = permissionStore.findByScope(getResourceServer(), scope); + List permissions = permissionStore.findByScope(resourceServer, scope); for (PermissionTicket permission : permissions) { permissionStore.delete(permission.getId()); } // Remove the scope from each Policy for this Resource - policyStore.findByResource(getResourceServer(), this, policy -> policy.removeScope(scope)); + policyStore.findByResource(resourceServer, this, policy -> policy.removeScope(scope)); } } diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java index 72c90e03d1..42a49d8f2f 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceServerAdapter.java @@ -18,15 +18,21 @@ package org.keycloak.models.map.authorization.adapter; +import java.util.Objects; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.RealmModel; import org.keycloak.models.map.authorization.entity.MapResourceServerEntity; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; public class MapResourceServerAdapter extends AbstractResourceServerModel { - public MapResourceServerAdapter(MapResourceServerEntity entity, StoreFactory storeFactory) { + private final RealmModel realmModel; + + public MapResourceServerAdapter(RealmModel realmModel, MapResourceServerEntity entity, StoreFactory storeFactory) { super(entity, storeFactory); + Objects.requireNonNull(realmModel); + this.realmModel = realmModel; } @Override @@ -70,6 +76,16 @@ public class MapResourceServerAdapter extends AbstractResourceServerModel { - public MapScopeAdapter(MapScopeEntity entity, StoreFactory storeFactory) { + private final ResourceServer resourceServer; + + public MapScopeAdapter(ResourceServer resourceServer, MapScopeEntity entity, StoreFactory storeFactory) { super(entity, storeFactory); + Objects.requireNonNull(resourceServer); + this.resourceServer = resourceServer; } @Override @@ -68,7 +73,7 @@ public class MapScopeAdapter extends AbstractScopeModel { @Override public ResourceServer getResourceServer() { - return storeFactory.getResourceServerStore().findById(entity.getResourceServerId()); + return resourceServer; } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java index 58b5241be6..0da8549b0f 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPermissionTicketEntity.java @@ -45,6 +45,9 @@ public interface MapPermissionTicketEntity extends UpdatableEntity, AbstractEnti } } + String getRealmId(); + void setRealmId(String realmId); + String getOwner(); void setOwner(String owner); diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java index 917c6054ee..d69cf58279 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapPolicyEntity.java @@ -50,6 +50,9 @@ public interface MapPolicyEntity extends UpdatableEntity, AbstractEntity { } } + String getRealmId(); + void setRealmId(String realmId); + String getName(); void setName(String name); diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java index 12161996f4..e74a21db52 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java @@ -48,6 +48,9 @@ public interface MapResourceEntity extends UpdatableEntity, AbstractEntity, Enti } } + String getRealmId(); + void setRealmId(String realmId); + String getName(); void setName(String name); diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java index 6a762c28c8..72774268e8 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceServerEntity.java @@ -47,6 +47,12 @@ public interface MapResourceServerEntity extends UpdatableEntity, AbstractEntity } } + String getRealmId(); + void setRealmId(String realmId); + + String getClientId(); + void setClientId(String clientId); + Boolean isAllowRemoteResourceManagement(); void setAllowRemoteResourceManagement(Boolean allowRemoteResourceManagement); diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java index 2f3eb70035..ef1ffc2059 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapScopeEntity.java @@ -45,6 +45,9 @@ public interface MapScopeEntity extends UpdatableEntity, AbstractEntity { } } + String getRealmId(); + void setRealmId(String realmId); + String getName(); void setName(String name); diff --git a/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java b/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java index c3b5f5da66..ae82410e1c 100644 --- a/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java +++ b/model/map/src/main/java/org/keycloak/models/map/common/AbstractMapProviderFactory.java @@ -67,6 +67,8 @@ public abstract class AbstractMapProviderFactorytype. * * @param type the type of the policy provider - * @param the expected type of the provider * @return a {@link PolicyProviderFactory} with the given type */ public PolicyProviderFactory getProviderFactory(String type) { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java index 88df34966b..814e0e161f 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/PermissionTicket.java @@ -32,6 +32,7 @@ public interface PermissionTicket { public static final SearchableModelField SCOPE_ID = new SearchableModelField<>("scopeId", String.class); public static final SearchableModelField POLICY_ID = new SearchableModelField<>("policyId", String.class); public static final SearchableModelField GRANTED_TIMESTAMP = new SearchableModelField<>("grantedTimestamp", String.class); + public static final SearchableModelField REALM_ID = new SearchableModelField<>("realmId", String.class); } public static enum FilterOption { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java index 109e31afd1..4945223e7b 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Policy.java @@ -42,6 +42,7 @@ public interface Policy { public static final SearchableModelField OWNER = new SearchableModelField<>("owner", String.class); public static final SearchableModelField CONFIG = new SearchableModelField<>("config", String.class); public static final SearchableModelField ASSOCIATED_POLICY_ID = new SearchableModelField<>("associatedPolicyId", String.class); + public static final SearchableModelField REALM_ID = new SearchableModelField<>("realmId", String.class); } public static enum FilterOption { @@ -100,7 +101,7 @@ public interface Policy { /** * Sets the {DecisionStrategy} for this policy. * - * @return the decision strategy for this policy + * @param decisionStrategy for this policy */ void setDecisionStrategy(DecisionStrategy decisionStrategy); @@ -114,7 +115,7 @@ public interface Policy { /** * Sets the {Logic} for this policy. * - * @return the decision strategy for this policy + * @param logic for this policy */ void setLogic(Logic logic); @@ -129,7 +130,7 @@ public interface Policy { /** * Sets a {@link Map} with string-based key/value pairs representing any additional configuration for this policy. * - * @return a map with any additional configuration for this policy. + * @param config a map with any additional configuration for this policy. */ void setConfig(Map config); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java index ee6b1333b3..5f5d2c7aae 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Resource.java @@ -37,6 +37,7 @@ public interface Resource { public static final SearchableModelField RESOURCE_SERVER_ID = new SearchableModelField<>("resourceServerId", String.class); public static final SearchableModelField OWNER = new SearchableModelField<>("owner", String.class); public static final SearchableModelField TYPE = new SearchableModelField<>("type", String.class); + public static final SearchableModelField REALM_ID = new SearchableModelField<>("realmId", String.class); public static final SearchableModelField URI = new SearchableModelField<>("uris", String.class); public static final SearchableModelField SCOPE_ID = new SearchableModelField<>("scope", String.class); @@ -133,7 +134,7 @@ public interface Resource { /** * Sets a string representing the type of this resource. * - * @return the type of this resource or null if not defined + * @param type the type of this resource or null if not defined */ void setType(String type); @@ -154,7 +155,7 @@ public interface Resource { /** * Sets an icon {@link java.net.URI} for this resource. * - * @return a uri for an icon + * @param iconUri an uri for an icon */ void setIconUri(String iconUri); @@ -203,6 +204,7 @@ public interface Resource { /** * Returns the first value of an attribute with the given name * + * @param name of the attribute * @return the first value of an attribute */ String getSingleAttribute(String name); @@ -210,6 +212,7 @@ public interface Resource { /** * Returns the values of an attribute with the given name * + * @param name of the attribute * @return the values of an attribute */ List getAttribute(String name); @@ -218,8 +221,7 @@ public interface Resource { * Sets an attribute with the given name and values. * * @param name the attribute name - * @param value the attribute values - * @return a map holding the attributes associated with this resource + * @param values the attribute values */ void setAttribute(String name, List values); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java index 0a01550ad0..0a5a02987f 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/ResourceServer.java @@ -18,18 +18,11 @@ package org.keycloak.authorization.model; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; -import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; import org.keycloak.storage.SearchableModelField; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; /** * Represents a resource server, whose resources are managed and protected. A resource server is basically an existing @@ -40,7 +33,10 @@ import java.util.stream.Stream; public interface ResourceServer { public static class SearchableFields { - public static final SearchableModelField ID = new SearchableModelField<>("id", String.class); + public static final SearchableModelField ID = new SearchableModelField<>("id", String.class); + /** ID of the client (not the clientId) associated with resource server*/ + public static final SearchableModelField CLIENT_ID = new SearchableModelField<>("clientId", String.class); + public static final SearchableModelField REALM_ID = new SearchableModelField<>("realmId", String.class); } /** @@ -53,7 +49,7 @@ public interface ResourceServer { /** * Indicates if the resource server is allowed to manage its own resources remotely using the Protection API. * - * {@code true} if the resource server is allowed to managed them remotely + * @return {@code true} if the resource server is allowed to managed them remotely */ boolean isAllowRemoteResourceManagement(); @@ -95,8 +91,14 @@ public interface ResourceServer { /** * Returns id of a client that this {@link ResourceServer} is associated with + * @return id of client */ - default String getClientId() { - return getId(); - } + String getClientId(); + + /** + * Returns reference of a realm that this {@link ResourceServer} belongs to. + * + * @return reference of a realm + */ + RealmModel getRealm(); } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java b/server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java index 955b348288..75b655c481 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/model/Scope.java @@ -32,6 +32,7 @@ public interface Scope { public static final SearchableModelField ID = new SearchableModelField<>("id", String.class); public static final SearchableModelField NAME = new SearchableModelField<>("name", String.class); public static final SearchableModelField RESOURCE_SERVER_ID = new SearchableModelField<>("resourceServerId", String.class); + public static final SearchableModelField REALM_ID = new SearchableModelField<>("resourceServerId", String.class); } public static enum FilterOption { diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java index 3db192519d..2e72ce1438 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PermissionTicketStore.java @@ -24,6 +24,7 @@ import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; +import org.keycloak.models.RealmModel; /** * A {@link PermissionTicketStore} is responsible to manage the persistence of {@link org.keycloak.authorization.model.PermissionTicket} instances. @@ -145,21 +146,25 @@ public interface PermissionTicketStore { /** * Returns a list of {@link Resource} granted to the given {@code requester} * + * + * @param realm * @param requester the requester * @param name the keyword to query resources by name or null if any resource * @param firstResult first result to return. Ignored if negative or {@code null}. * @param maxResults maximum number of results to return. Ignored if negative or {@code null}. * @return a list of {@link Resource} granted to the given {@code requester} */ - List findGrantedResources(String requester, String name, Integer firstResult, Integer maxResults); + List findGrantedResources(RealmModel realm, String requester, String name, Integer firstResult, Integer maxResults); /** * Returns a list of {@link Resource} granted by the owner to other users * + * + * @param realm * @param owner the owner * @param firstResult first result to return. Ignored if negative or {@code null}. * @param maxResults maximum number of results to return. Ignored if negative or {@code null}. * @return a list of {@link Resource} granted by the owner */ - List findGrantedOwnerResources(String owner, Integer firstResult, Integer maxResults); + List findGrantedOwnerResources(RealmModel realm, String owner, Integer firstResult, Integer maxResults); } diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java index 68f4907978..6762639c18 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/ResourceServerStore.java @@ -20,6 +20,7 @@ package org.keycloak.authorization.store; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; /** * A {@link ResourceServerStore} is responsible to manage the persistence of {@link ResourceServer} instances. @@ -47,11 +48,13 @@ public interface ResourceServerStore { /** * Returns a {@link ResourceServer} instance based on its identifier. * + * + * @param realm * @param id the identifier of an existing resource server instance * * @return the resource server instance with the given identifier or null if no instance was found */ - ResourceServer findById(String id); + ResourceServer findById(RealmModel realm, String id); /** * Returns a {@link ResourceServer} instance based on a client. diff --git a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java index bedf60dc5d..8fbc72cc4c 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourcesService.java @@ -90,7 +90,7 @@ public class ResourcesService extends AbstractResourceService { public Response getSharedWithMe(@QueryParam("name") String name, @QueryParam("first") Integer first, @QueryParam("max") Integer max) { - return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getUser().getId(), name, f, m), false) + return queryResponse((f, m) -> toPermissions(ticketStore.findGrantedResources(auth.getRealm(), auth.getUser().getId(), name, f, m), false) .stream(), first, max); } @@ -108,7 +108,7 @@ public class ResourcesService extends AbstractResourceService { @Produces(MediaType.APPLICATION_JSON) public Response getSharedWithOthers(@QueryParam("first") Integer first, @QueryParam("max") Integer max) { return queryResponse( - (f, m) -> toPermissions(ticketStore.findGrantedOwnerResources(auth.getUser().getId(), f, m), true) + (f, m) -> toPermissions(ticketStore.findGrantedOwnerResources(auth.getRealm(), auth.getUser().getId(), f, m), true) .stream(), first, max); } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/RealmModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/RealmModelTest.java index 697bb47dca..7133e841dc 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/RealmModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/RealmModelTest.java @@ -17,22 +17,31 @@ package org.keycloak.testsuite.model; import org.junit.Test; +import org.keycloak.authorization.AuthorizationProvider; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; +import java.util.function.Consumer; +import java.util.function.Function; + import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.notNullValue; @RequireProvider(RealmProvider.class) public class RealmModelTest extends KeycloakModelTest { private String realmId; + private String realm1Id; + private String realm2Id; @Override public void createEnvironment(KeycloakSession s) { @@ -44,6 +53,8 @@ public class RealmModelTest extends KeycloakModelTest { @Override public void cleanEnvironment(KeycloakSession s) { s.realms().removeRealm(realmId); + if (realm1Id != null) s.realms().removeRealm(realm1Id); + if (realm2Id != null) s.realms().removeRealm(realm2Id); } @Test @@ -93,6 +104,40 @@ public class RealmModelTest extends KeycloakModelTest { return null; }); + } + @Test + public void testRealmPreRemoveDoesntRemoveEntitiesFromOtherRealms() { + realm1Id = inComittedTransaction((Function) session -> { + RealmModel realm = session.realms().createRealm("realm1"); + realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); + return realm.getId(); + }); + realm2Id = inComittedTransaction((Function) session -> { + RealmModel realm = session.realms().createRealm("realm2"); + realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); + return realm.getId(); + }); + + // Create client with resource server + String clientRealm1 = withRealm(realm1Id, (keycloakSession, realmModel) -> { + ClientModel clientRealm = realmModel.addClient("clientRealm1"); + AuthorizationProvider provider = keycloakSession.getProvider(AuthorizationProvider.class); + provider.getStoreFactory().getResourceServerStore().create(clientRealm); + + return clientRealm.getId(); + }); + + // Remove realm 2 + inComittedTransaction( (Consumer) keycloakSession -> keycloakSession.realms().removeRealm(realm2Id)); + + + // ResourceServer in realm1 must still exist + ResourceServer resourceServer = withRealm(realm1Id, (keycloakSession, realmModel) -> { + ClientModel client1 = realmModel.getClientById(clientRealm1); + return keycloakSession.getProvider(AuthorizationProvider.class).getStoreFactory().getResourceServerStore().findByClient(client1); + }); + + assertThat(resourceServer, notNullValue()); } }