From 36f76a37ad1609b210fba94287582888c15244e0 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 18 Feb 2022 21:10:10 +0100 Subject: [PATCH] Move realms, clients, groups, roles, clientscopes into legacy module - Introduces Datastore SPI for isolating data store methods - Introduces implementation of the datastore for legacy storage - Updates DefaultKeycloakSession to leverage Datastore SPI instead of direct creating of area providers by the session --- .../cache/infinispan/RealmCacheSession.java | 14 +- .../storage/AbstractStorageManager.java | 247 ++++++++++++++++++ .../storage/ClientScopeStorageManager.java | 14 +- .../storage/ClientStorageManager.java | 36 +-- .../keycloak/storage/GroupStorageManager.java | 30 ++- .../keycloak/storage/RoleStorageManager.java | 36 +-- ....keycloak.storage.DatastoreProviderFactory | 18 ++ .../datastore/LegacyDatastoreProvider.java | 157 +++++++++++ .../LegacyDatastoreProviderFactory.java | 48 ++++ .../org/keycloak/utils/ServicesUtils.java | 134 ++++++++++ .../models/utils/ModelToRepresentation.java | 4 + .../keycloak/storage/DatastoreProvider.java | 22 ++ .../storage/DatastoreProviderFactory.java | 7 + .../org/keycloak/storage/DatastoreSpi.java | 27 ++ .../services/org.keycloak.provider.Spi | 1 + .../org/keycloak/models/KeycloakSession.java | 2 + .../protocol/oidc/utils/RedirectUtils.java | 2 +- .../services/DefaultKeycloakSession.java | 131 ++-------- .../resources/admin/RealmAdminResource.java | 9 +- .../org/keycloak/utils/ServicesUtils.java | 5 +- testsuite/model/pom.xml | 60 ++--- .../parameters/{Jpa.java => LegacyJpa.java} | 10 +- ...deration.java => LegacyJpaFederation.java} | 10 +- 23 files changed, 816 insertions(+), 208 deletions(-) create mode 100644 model/legacy-private/src/main/java/org/keycloak/storage/AbstractStorageManager.java rename {services => model/legacy-private}/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java (86%) rename {services => model/legacy-private}/src/main/java/org/keycloak/storage/ClientStorageManager.java (89%) rename {services => model/legacy-private}/src/main/java/org/keycloak/storage/GroupStorageManager.java (79%) rename {services => model/legacy-private}/src/main/java/org/keycloak/storage/RoleStorageManager.java (88%) create mode 100644 model/legacy-private/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory create mode 100644 model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProvider.java create mode 100644 model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java create mode 100644 model/legacy/src/main/java/org/keycloak/utils/ServicesUtils.java create mode 100644 server-spi-private/src/main/java/org/keycloak/storage/DatastoreProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/storage/DatastoreProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/storage/DatastoreSpi.java rename testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/{Jpa.java => LegacyJpa.java} (93%) rename testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/{JpaFederation.java => LegacyJpaFederation.java} (93%) diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index f632ae319d..c119f7e4b3 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -26,8 +26,10 @@ import org.keycloak.models.cache.CachedRealmModel; import org.keycloak.models.cache.infinispan.entities.*; import org.keycloak.models.cache.infinispan.events.*; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.StorageId; import org.keycloak.storage.client.ClientStorageProviderModel; +import org.keycloak.storage.datastore.LegacyDatastoreProvider; import java.util.*; import java.util.stream.Collectors; @@ -119,11 +121,13 @@ public class RealmCacheSession implements CacheRealmProvider { protected boolean clearAll; protected final long startupRevision; + private final LegacyDatastoreProvider datastoreProvider; public RealmCacheSession(RealmCacheManager cache, KeycloakSession session) { this.cache = cache; this.session = session; this.startupRevision = cache.getCurrentCounter(); + this.datastoreProvider = (LegacyDatastoreProvider) session.getProvider(DatastoreProvider.class); session.getTransactionManager().enlistPrepare(getPrepareTransaction()); session.getTransactionManager().enlistAfterCompletion(getAfterTransaction()); } @@ -146,31 +150,31 @@ public class RealmCacheSession implements CacheRealmProvider { public RealmProvider getRealmDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (realmDelegate != null) return realmDelegate; - realmDelegate = session.realmLocalStorage(); + realmDelegate = session.getProvider(RealmProvider.class); return realmDelegate; } public ClientProvider getClientDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (clientDelegate != null) return clientDelegate; - clientDelegate = session.clientStorageManager(); + clientDelegate = this.datastoreProvider.clientStorageManager(); return clientDelegate; } public ClientScopeProvider getClientScopeDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (clientScopeDelegate != null) return clientScopeDelegate; - clientScopeDelegate = session.clientScopeStorageManager(); + clientScopeDelegate = this.datastoreProvider.clientScopeStorageManager(); return clientScopeDelegate; } public RoleProvider getRoleDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (roleDelegate != null) return roleDelegate; - roleDelegate = session.roleStorageManager(); + roleDelegate = this.datastoreProvider.roleStorageManager(); return roleDelegate; } public GroupProvider getGroupDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (groupDelegate != null) return groupDelegate; - groupDelegate = session.groupStorageManager(); + groupDelegate = this.datastoreProvider.groupStorageManager(); return groupDelegate; } diff --git a/model/legacy-private/src/main/java/org/keycloak/storage/AbstractStorageManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/AbstractStorageManager.java new file mode 100644 index 0000000000..4412723faa --- /dev/null +++ b/model/legacy-private/src/main/java/org/keycloak/storage/AbstractStorageManager.java @@ -0,0 +1,247 @@ +/* + * Copyright 2020 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.storage; + +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.common.util.reflections.Types; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.utils.ServicesUtils; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * + * @param This type will be used for looking for factories that produce instances of desired providers + * @param Type of model used for creating provider, it needs to extend + * CacheableStorageProviderModel as it has {@code isEnabled()} method and also extend + * PrioritizedComponentModel which is required for sorting providers based on its + * priorities + */ +public abstract class AbstractStorageManager { + + private static final Logger LOG = Logger.getLogger(AbstractStorageManager.class); + + /** + * Timeouts are used as time boundary for obtaining models from an external storage. Default value is set + * to 3000 milliseconds and it's configurable. + */ + private static final Long STORAGE_PROVIDER_DEFAULT_TIMEOUT = 3000L; + protected final KeycloakSession session; + private final Class providerTypeClass; + private final Class factoryTypeClass; + private final Function toStorageProviderModelTypeFunction; + private final String configScope; + private Long storageProviderTimeout; + + public AbstractStorageManager(KeycloakSession session, Class factoryTypeClass, Class providerTypeClass, Function toStorageProviderModelTypeFunction, String configScope) { + this.session = session; + this.providerTypeClass = providerTypeClass; + this.factoryTypeClass = factoryTypeClass; + this.toStorageProviderModelTypeFunction = toStorageProviderModelTypeFunction; + this.configScope = configScope; + } + + protected Long getStorageProviderTimeout() { + if (storageProviderTimeout == null) { + storageProviderTimeout = Config.scope(configScope).getLong("storageProviderTimeout", STORAGE_PROVIDER_DEFAULT_TIMEOUT); + } + return storageProviderTimeout; + } + + /** + * Returns a factory with the providerId, which produce instances of type CreatedProviderType + * @param providerId id of factory that produce desired instances + * @return A factory that implements {@code ComponentFactory} + */ + protected ComponentFactory getStorageProviderFactory(String providerId) { + return (ComponentFactory) session.getKeycloakSessionFactory() + .getProviderFactory(providerTypeClass, providerId); + } + + /** + * Returns stream of all storageProviders within the realm that implements the capabilityInterface. + * + * @param realm realm + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @return enabled storage providers for realm and @{code getProviderTypeClass()} + */ + protected Stream getEnabledStorageProviders(RealmModel realm, Class capabilityInterface) { + return getStorageProviderModels(realm, providerTypeClass) + .map(toStorageProviderModelTypeFunction) + .filter(StorageProviderModelType::isEnabled) + .sorted(StorageProviderModelType.comparator) + .map(storageProviderModelType -> getStorageProviderInstance(storageProviderModelType, capabilityInterface, false)) + .filter(Objects::nonNull); + } + + /** + * Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of + * them and then join the results together. + * + * !! Each StorageProvider has a limited time to respond, if it fails to do it, empty stream is returned !! + * + * @param realm realm + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @param applyFunction function that is applied on StorageProviders + * @param result of applyFunction + * @return a stream with all results from all StorageProviders + */ + protected Stream flatMapEnabledStorageProvidersWithTimeout(RealmModel realm, Class capabilityInterface, Function> applyFunction) { + return getEnabledStorageProviders(realm, capabilityInterface) + .flatMap(ServicesUtils.timeBound(session, getStorageProviderTimeout(), applyFunction)); + } + + /** + * Gets all enabled StorageProviders that implements the capabilityInterface, applies applyFunction on each of + * them and returns the stream. + * + * !! Each StorageProvider has a limited time to respond, if it fails to do it, null is returned !! + * + * @param realm realm + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @param applyFunction function that is applied on StorageProviders + * @param Result of applyFunction + * @return First result from StorageProviders + */ + protected Stream mapEnabledStorageProvidersWithTimeout(RealmModel realm, Class capabilityInterface, Function applyFunction) { + return getEnabledStorageProviders(realm, capabilityInterface) + .map(ServicesUtils.timeBoundOne(session, getStorageProviderTimeout(), applyFunction)) + .filter(Objects::nonNull); + } + + /** + * Gets all enabled StorageProviders that implements the capabilityInterface and call applyFunction on each + * + * !! Each StorageProvider has a limited time for consuming !! + * + * @param realm realm + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @param consumer function that is applied on StorageProviders + */ + protected void consumeEnabledStorageProvidersWithTimeout(RealmModel realm, Class capabilityInterface, Consumer consumer) { + getEnabledStorageProviders(realm, capabilityInterface) + .forEachOrdered(ServicesUtils.consumeWithTimeBound(session, getStorageProviderTimeout(), consumer)); + } + + + protected T getStorageProviderInstance(RealmModel realm, String providerId, Class capabilityInterface) { + return getStorageProviderInstance(realm, providerId, capabilityInterface, false); + } + + /** + * Returns an instance of provider with the providerId within the realm or null if storage provider with providerId + * doesn't implement capabilityInterface. + * + * @param realm realm + * @param providerId id of ComponentModel within database/storage + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @return an instance of type CreatedProviderType or null if storage provider with providerId doesn't implement capabilityInterface + */ + protected T getStorageProviderInstance(RealmModel realm, String providerId, Class capabilityInterface, boolean includeDisabled) { + if (providerId == null || capabilityInterface == null) return null; + return getStorageProviderInstance(getStorageProviderModel(realm, providerId), capabilityInterface, includeDisabled); + } + + /** + * Returns an instance of StorageProvider model corresponding realm and providerId + * @param realm Realm. + * @param providerId Id of desired provider. + * @return An instance of type StorageProviderModelType + */ + protected StorageProviderModelType getStorageProviderModel(RealmModel realm, String providerId) { + ComponentModel componentModel = realm.getComponent(providerId); + if (componentModel == null) { + return null; + } + + return toStorageProviderModelTypeFunction.apply(componentModel); + } + + /** + * Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface. + * + * @param model StorageProviderModel obtained from database/storage + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @param Required capability interface type + * @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface. + */ + protected T getStorageProviderInstance(StorageProviderModelType model, Class capabilityInterface) { + return getStorageProviderInstance(model, capabilityInterface, false); + } + + /** + * Returns an instance of provider for the model or null if storage provider based on the model doesn't implement capabilityInterface. + * + * @param model StorageProviderModel obtained from database/storage + * @param capabilityInterface class of desired capabilityInterface. + * For example, {@code GroupLookupProvider} or {@code UserQueryProvider} + * @param includeDisabled If set to true, the method will return also disabled providers. + * @return an instance of type T or null if storage provider based on the model doesn't exist or doesn't implement the capabilityInterface. + */ + protected T getStorageProviderInstance(StorageProviderModelType model, Class capabilityInterface, boolean includeDisabled) { + if (model == null || (!model.isEnabled() && !includeDisabled) || capabilityInterface == null) { + return null; + } + + @SuppressWarnings("unchecked") + ProviderType instance = (ProviderType) session.getAttribute(model.getId()); + if (instance != null && capabilityInterface.isAssignableFrom(instance.getClass())) return capabilityInterface.cast(instance); + + ComponentFactory factory = getStorageProviderFactory(model.getProviderId()); + if (factory == null) { + LOG.warnv("Configured StorageProvider {0} of provider id {1} does not exist", model.getName(), model.getProviderId()); + return null; + } + if (!Types.supports(capabilityInterface, factory, factoryTypeClass)) { + return null; + } + + instance = factory.create(session, model); + if (instance == null) { + throw new IllegalStateException("StorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance"); + } + session.enlistForClose(instance); + session.setAttribute(model.getId(), instance); + return capabilityInterface.cast(instance); + } + + /** + * Stream of ComponentModels of storageType. + * @param realm Realm. + * @param storageType Type. + * @return Stream of ComponentModels + */ + public static Stream getStorageProviderModels(RealmModel realm, Class storageType) { + return realm.getStorageProviders(storageType); + } +} diff --git a/services/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java similarity index 86% rename from services/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java rename to model/legacy-private/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java index 6af7c32b9d..439ebbd556 100644 --- a/services/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/ClientScopeStorageManager.java @@ -33,13 +33,17 @@ public class ClientScopeStorageManager extends AbstractStorageManager getClientScopesStream(RealmModel realm) { - return session.clientScopeLocalStorage().getClientScopesStream(realm); + return localStorage().getClientScopesStream(realm); } @Override public ClientScopeModel addClientScope(RealmModel realm, String id, String name) { - return session.clientScopeLocalStorage().addClientScope(realm, id, name); + return localStorage().addClientScope(realm, id, name); } @Override public boolean removeClientScope(RealmModel realm, String id) { - return session.clientScopeLocalStorage().removeClientScope(realm, id); + return localStorage().removeClientScope(realm, id); } @Override public void removeClientScopes(RealmModel realm) { - session.clientScopeLocalStorage().removeClientScopes(realm); + localStorage().removeClientScopes(realm); } @Override diff --git a/services/src/main/java/org/keycloak/storage/ClientStorageManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/ClientStorageManager.java similarity index 89% rename from services/src/main/java/org/keycloak/storage/ClientStorageManager.java rename to model/legacy-private/src/main/java/org/keycloak/storage/ClientStorageManager.java index 60fbe40525..b5e7fe48c7 100644 --- a/services/src/main/java/org/keycloak/storage/ClientStorageManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/ClientStorageManager.java @@ -50,6 +50,10 @@ public class ClientStorageManager implements ClientProvider { private long clientStorageProviderTimeout; + private ClientProvider localStorage() { + return session.getProvider(ClientProvider.class); + } + public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) { ClientStorageProviderModel model = getStorageProviderModel(realm, providerId); return model.isEnabled(); @@ -131,7 +135,7 @@ public class ClientStorageManager implements ClientProvider { public ClientModel getClientById(RealmModel realm, String id) { StorageId storageId = new StorageId(id); if (storageId.getProviderId() == null) { - return session.clientLocalStorage().getClientById(realm, id); + return localStorage().getClientById(realm, id); } ClientLookupProvider provider = (ClientLookupProvider)getStorageProvider(session, realm, storageId.getProviderId()); if (provider == null) return null; @@ -141,7 +145,7 @@ public class ClientStorageManager implements ClientProvider { @Override public ClientModel getClientByClientId(RealmModel realm, String clientId) { - ClientModel client = session.clientLocalStorage().getClientByClientId(realm, clientId); + ClientModel client = localStorage().getClientByClientId(realm, clientId); if (client != null) { return client; } @@ -174,7 +178,7 @@ public class ClientStorageManager implements ClientProvider { // how many results there will be; i.e. we need to query the clients without paginating them and perform pagination // later at this level if (hasEnabledStorageProviders(session, realm, ClientLookupProvider.class)) { - Stream providersStream = Stream.concat(Stream.of(session.clientLocalStorage()), getEnabledStorageProviders(session, realm, ClientLookupProvider.class)); + Stream providersStream = Stream.concat(Stream.of(localStorage()), getEnabledStorageProviders(session, realm, ClientLookupProvider.class)); /* Obtaining clients from an external client storage is time-bounded. In case the external client storage @@ -196,7 +200,7 @@ public class ClientStorageManager implements ClientProvider { return paginatedStream(res, firstResult, maxResults); } else { - return paginatedQuery.query(session.clientLocalStorage(), firstResult, maxResults); + return paginatedQuery.query(localStorage(), firstResult, maxResults); } } @@ -204,7 +208,7 @@ public class ClientStorageManager implements ClientProvider { public Map getClientScopes(RealmModel realm, ClientModel client, boolean defaultScopes) { StorageId storageId = new StorageId(client.getId()); if (storageId.getProviderId() == null) { - return session.clientLocalStorage().getClientScopes(realm, client, defaultScopes); + return localStorage().getClientScopes(realm, client, defaultScopes); } ClientLookupProvider provider = (ClientLookupProvider)getStorageProvider(session, client.getRealm(), storageId.getProviderId()); if (provider == null) return null; @@ -214,37 +218,37 @@ public class ClientStorageManager implements ClientProvider { @Override public ClientModel addClient(RealmModel realm, String clientId) { - return session.clientLocalStorage().addClient(realm, clientId); + return localStorage().addClient(realm, clientId); } @Override public ClientModel addClient(RealmModel realm, String id, String clientId) { - return session.clientLocalStorage().addClient(realm, id, clientId); + return localStorage().addClient(realm, id, clientId); } @Override public Stream getClientsStream(RealmModel realm, Integer firstResult, Integer maxResults) { - return session.clientLocalStorage().getClientsStream(realm, firstResult, maxResults); + return localStorage().getClientsStream(realm, firstResult, maxResults); } @Override public Stream getClientsStream(RealmModel realm) { - return session.clientLocalStorage().getClientsStream(realm); + return localStorage().getClientsStream(realm); } @Override public long getClientsCount(RealmModel realm) { - return session.clientLocalStorage().getClientsCount(realm); + return localStorage().getClientsCount(realm); } @Override public Stream getAlwaysDisplayInConsoleClientsStream(RealmModel realm) { - return session.clientLocalStorage().getAlwaysDisplayInConsoleClientsStream(realm); + return localStorage().getAlwaysDisplayInConsoleClientsStream(realm); } @Override public void removeClients(RealmModel realm) { - session.clientLocalStorage().removeClients(realm); + localStorage().removeClients(realm); } @Override @@ -252,7 +256,7 @@ public class ClientStorageManager implements ClientProvider { if (!StorageId.isLocalStorage(client.getId())) { throw new RuntimeException("Federated clients do not support this operation"); } - session.clientLocalStorage().addClientScopes(realm, client, clientScopes, defaultScope); + localStorage().addClientScopes(realm, client, clientScopes, defaultScope); } @Override @@ -260,12 +264,12 @@ public class ClientStorageManager implements ClientProvider { if (!StorageId.isLocalStorage(client.getId())) { throw new RuntimeException("Federated clients do not support this operation"); } - session.clientLocalStorage().removeClientScope(realm, client, clientScope); + localStorage().removeClientScope(realm, client, clientScope); } @Override public Map> getAllRedirectUrisOfEnabledClients(RealmModel realm) { - return session.clientLocalStorage().getAllRedirectUrisOfEnabledClients(realm); + return localStorage().getAllRedirectUrisOfEnabledClients(realm); } @Override @@ -278,7 +282,7 @@ public class ClientStorageManager implements ClientProvider { if (!StorageId.isLocalStorage(id)) { throw new RuntimeException("Federated clients do not support this operation"); } - return session.clientLocalStorage().removeClient(realm, id); + return localStorage().removeClient(realm, id); } diff --git a/services/src/main/java/org/keycloak/storage/GroupStorageManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/GroupStorageManager.java similarity index 79% rename from services/src/main/java/org/keycloak/storage/GroupStorageManager.java rename to model/legacy-private/src/main/java/org/keycloak/storage/GroupStorageManager.java index 24ea6b4f29..dfd1959d9d 100644 --- a/services/src/main/java/org/keycloak/storage/GroupStorageManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/GroupStorageManager.java @@ -37,11 +37,15 @@ public class GroupStorageManager extends AbstractStorageManager searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { - Stream local = session.groupLocalStorage().searchForGroupByNameStream(realm, search, firstResult, maxResults); + Stream local = localStorage().searchForGroupByNameStream(realm, search, firstResult, maxResults); Stream ext = flatMapEnabledStorageProvidersWithTimeout(realm, GroupLookupProvider.class, p -> p.searchForGroupByNameStream(realm, search, firstResult, maxResults)); @@ -71,57 +75,57 @@ public class GroupStorageManager extends AbstractStorageManager getGroupsStream(RealmModel realm) { - return session.groupLocalStorage().getGroupsStream(realm); + return localStorage().getGroupsStream(realm); } @Override public Stream getGroupsStream(RealmModel realm, Stream ids, String search, Integer first, Integer max) { - return session.groupLocalStorage().getGroupsStream(realm, ids, search, first, max); + return localStorage().getGroupsStream(realm, ids, search, first, max); } @Override public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) { - return session.groupLocalStorage().getGroupsCount(realm, onlyTopGroups); + return localStorage().getGroupsCount(realm, onlyTopGroups); } @Override public Long getGroupsCountByNameContaining(RealmModel realm, String search) { - return session.groupLocalStorage().getGroupsCountByNameContaining(realm, search); + return localStorage().getGroupsCountByNameContaining(realm, search); } @Override public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, Integer firstResult, Integer maxResults) { - return session.groupLocalStorage().getGroupsByRoleStream(realm, role, firstResult, maxResults); + return localStorage().getGroupsByRoleStream(realm, role, firstResult, maxResults); } @Override public Stream getTopLevelGroupsStream(RealmModel realm) { - return session.groupLocalStorage().getTopLevelGroupsStream(realm); + return localStorage().getTopLevelGroupsStream(realm); } @Override public Stream getTopLevelGroupsStream(RealmModel realm, Integer firstResult, Integer maxResults) { - return session.groupLocalStorage().getTopLevelGroupsStream(realm, firstResult, maxResults); + return localStorage().getTopLevelGroupsStream(realm, firstResult, maxResults); } @Override public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) { - return session.groupLocalStorage().createGroup(realm, id, name, toParent); + return localStorage().createGroup(realm, id, name, toParent); } @Override public boolean removeGroup(RealmModel realm, GroupModel group) { - return session.groupLocalStorage().removeGroup(realm, group); + return localStorage().removeGroup(realm, group); } @Override public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) { - session.groupLocalStorage().moveGroup(realm, group, toParent); + localStorage().moveGroup(realm, group, toParent); } @Override public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) { - session.groupLocalStorage().addTopLevelGroup(realm, subGroup); + localStorage().addTopLevelGroup(realm, subGroup); } @Override diff --git a/services/src/main/java/org/keycloak/storage/RoleStorageManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/RoleStorageManager.java similarity index 88% rename from services/src/main/java/org/keycloak/storage/RoleStorageManager.java rename to model/legacy-private/src/main/java/org/keycloak/storage/RoleStorageManager.java index 95c3aacfc2..f3dddf329b 100644 --- a/services/src/main/java/org/keycloak/storage/RoleStorageManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/RoleStorageManager.java @@ -45,6 +45,10 @@ public class RoleStorageManager implements RoleProvider { this.roleStorageProviderTimeout = roleStorageProviderTimeout; } + private RoleProvider localStorage() { + return session.getProvider(RoleProvider.class); + } + public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) { RoleStorageProviderModel model = getStorageProviderModel(realm, providerId); return model.isEnabled(); @@ -114,17 +118,17 @@ public class RoleStorageManager implements RoleProvider { @Override public RoleModel addRealmRole(RealmModel realm, String name) { - return session.roleLocalStorage().addRealmRole(realm, name); + return localStorage().addRealmRole(realm, name); } @Override public RoleModel addRealmRole(RealmModel realm, String id, String name) { - return session.roleLocalStorage().addRealmRole(realm, id, name); + return localStorage().addRealmRole(realm, id, name); } @Override public RoleModel getRealmRole(RealmModel realm, String name) { - RoleModel realmRole = session.roleLocalStorage().getRealmRole(realm, name); + RoleModel realmRole = localStorage().getRealmRole(realm, name); if (realmRole != null) return realmRole; return getEnabledStorageProviders(session, realm, RoleLookupProvider.class) .map(provider -> provider.getRealmRole(realm, name)) @@ -137,7 +141,7 @@ public class RoleStorageManager implements RoleProvider { public RoleModel getRoleById(RealmModel realm, String id) { StorageId storageId = new StorageId(id); if (storageId.getProviderId() == null) { - return session.roleLocalStorage().getRoleById(realm, id); + return localStorage().getRoleById(realm, id); } RoleLookupProvider provider = (RoleLookupProvider)getStorageProvider(session, realm, storageId.getProviderId()); if (provider == null) return null; @@ -147,12 +151,12 @@ public class RoleStorageManager implements RoleProvider { @Override public Stream getRealmRolesStream(RealmModel realm, Integer first, Integer max) { - return session.roleLocalStorage().getRealmRolesStream(realm, first, max); + return localStorage().getRealmRolesStream(realm, first, max); } @Override public Stream getRolesStream(RealmModel realm, Stream ids, String search, Integer first, Integer max) { - return session.roleLocalStorage().getRolesStream(realm, ids, search, first, max); + return localStorage().getRolesStream(realm, ids, search, first, max); } /** @@ -164,7 +168,7 @@ public class RoleStorageManager implements RoleProvider { */ @Override public Stream searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) { - Stream local = session.roleLocalStorage().searchForRolesStream(realm, search, first, max); + Stream local = localStorage().searchForRolesStream(realm, search, first, max); Stream ext = getEnabledStorageProviders(session, realm, RoleLookupProvider.class) .flatMap(ServicesUtils.timeBound(session, roleStorageProviderTimeout, @@ -178,32 +182,32 @@ public class RoleStorageManager implements RoleProvider { if (!StorageId.isLocalStorage(role.getId())) { throw new RuntimeException("Federated roles do not support this operation"); } - return session.roleLocalStorage().removeRole(role); + return localStorage().removeRole(role); } @Override public void removeRoles(RealmModel realm) { - session.roleLocalStorage().removeRoles(realm); + localStorage().removeRoles(realm); } @Override public void removeRoles(ClientModel client) { - session.roleLocalStorage().removeRoles(client); + localStorage().removeRoles(client); } @Override public RoleModel addClientRole(ClientModel client, String name) { - return session.roleLocalStorage().addClientRole(client, name); + return localStorage().addClientRole(client, name); } @Override public RoleModel addClientRole(ClientModel client, String id, String name) { - return session.roleLocalStorage().addClientRole(client, id, name); + return localStorage().addClientRole(client, id, name); } @Override public RoleModel getClientRole(ClientModel client, String name) { - RoleModel clientRole = session.roleLocalStorage().getClientRole(client, name); + RoleModel clientRole = localStorage().getClientRole(client, name); if (clientRole != null) return clientRole; return getEnabledStorageProviders(session, client.getRealm(), RoleLookupProvider.class) .map(provider -> provider.getClientRole(client, name)) @@ -214,12 +218,12 @@ public class RoleStorageManager implements RoleProvider { @Override public Stream getClientRolesStream(ClientModel client) { - return session.roleLocalStorage().getClientRolesStream(client); + return localStorage().getClientRolesStream(client); } @Override public Stream getClientRolesStream(ClientModel client, Integer first, Integer max) { - return session.roleLocalStorage().getClientRolesStream(client, first, max); + return localStorage().getClientRolesStream(client, first, max); } /** @@ -231,7 +235,7 @@ public class RoleStorageManager implements RoleProvider { */ @Override public Stream searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) { - Stream local = session.roleLocalStorage().searchForClientRolesStream(client, search, first, max); + Stream local = localStorage().searchForClientRolesStream(client, search, first, max); Stream ext = getEnabledStorageProviders(session, client.getRealm(), RoleLookupProvider.class) .flatMap(ServicesUtils.timeBound(session, roleStorageProviderTimeout, diff --git a/model/legacy-private/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory b/model/legacy-private/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory new file mode 100644 index 0000000000..f0a25ec0fb --- /dev/null +++ b/model/legacy-private/src/main/resources/META-INF/services/org.keycloak.storage.DatastoreProviderFactory @@ -0,0 +1,18 @@ +# +# Copyright 2022 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. +# + +org.keycloak.storage.datastore.LegacyDatastoreProviderFactory \ No newline at end of file diff --git a/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProvider.java b/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProvider.java new file mode 100644 index 0000000000..01a1227d0e --- /dev/null +++ b/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProvider.java @@ -0,0 +1,157 @@ +package org.keycloak.storage.datastore; + +import org.keycloak.models.ClientProvider; +import org.keycloak.models.ClientScopeProvider; +import org.keycloak.models.GroupProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmProvider; +import org.keycloak.models.RoleProvider; +import org.keycloak.models.cache.CacheRealmProvider; +import org.keycloak.storage.ClientScopeStorageManager; +import org.keycloak.storage.ClientStorageManager; +import org.keycloak.storage.DatastoreProvider; +import org.keycloak.storage.GroupStorageManager; +import org.keycloak.storage.RoleStorageManager; + +public class LegacyDatastoreProvider implements DatastoreProvider { + + private final LegacyDatastoreProviderFactory factory; + private final KeycloakSession session; + + private ClientProvider clientProvider; + private ClientScopeProvider clientScopeProvider; + private GroupProvider groupProvider; + private RealmProvider realmProvider; + private RoleProvider roleProvider; + + private ClientScopeStorageManager clientScopeStorageManager; + private RoleStorageManager roleStorageManager; + private GroupStorageManager groupStorageManager; + private ClientStorageManager clientStorageManager; + + public LegacyDatastoreProvider(LegacyDatastoreProviderFactory factory, KeycloakSession session) { + this.factory = factory; + this.session = session; + } + + @Override + public void close() { + } + + public ClientProvider clientStorageManager() { + if (clientStorageManager == null) { + clientStorageManager = new ClientStorageManager(session, factory.getClientStorageProviderTimeout()); + } + return clientStorageManager; + } + + public ClientScopeProvider clientScopeStorageManager() { + if (clientScopeStorageManager == null) { + clientScopeStorageManager = new ClientScopeStorageManager(session); + } + return clientScopeStorageManager; + } + + public RoleProvider roleStorageManager() { + if (roleStorageManager == null) { + roleStorageManager = new RoleStorageManager(session, factory.getRoleStorageProviderTimeout()); + } + return roleStorageManager; + } + + public GroupProvider groupStorageManager() { + if (groupStorageManager == null) { + groupStorageManager = new GroupStorageManager(session); + } + return groupStorageManager; + } + + private ClientProvider getClientProvider() { + // TODO: Extract ClientProvider from CacheRealmProvider and use that instead + ClientProvider cache = session.getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { + return clientStorageManager(); + } + } + + private ClientScopeProvider getClientScopeProvider() { + // TODO: Extract ClientScopeProvider from CacheRealmProvider and use that instead + ClientScopeProvider cache = session.getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { + return clientScopeStorageManager(); + } + } + + private GroupProvider getGroupProvider() { + // TODO: Extract GroupProvider from CacheRealmProvider and use that instead + GroupProvider cache = session.getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { + return groupStorageManager(); + } + } + + private RealmProvider getRealmProvider() { + CacheRealmProvider cache = session.getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { + return session.getProvider(RealmProvider.class); + } + } + + private RoleProvider getRoleProvider() { + // TODO: Extract RoleProvider from CacheRealmProvider and use that instead + RoleProvider cache = session.getProvider(CacheRealmProvider.class); + if (cache != null) { + return cache; + } else { + return roleStorageManager(); + } + } + + @Override + public ClientProvider clients() { + if (clientProvider == null) { + clientProvider = getClientProvider(); + } + return clientProvider; + } + + @Override + public ClientScopeProvider clientScopes() { + if (clientScopeProvider == null) { + clientScopeProvider = getClientScopeProvider(); + } + return clientScopeProvider; + } + + @Override + public GroupProvider groups() { + if (groupProvider == null) { + groupProvider = getGroupProvider(); + } + return groupProvider; + } + + @Override + public RealmProvider realms() { + if (realmProvider == null) { + realmProvider = getRealmProvider(); + } + return realmProvider; + } + + @Override + public RoleProvider roles() { + if (roleProvider == null) { + roleProvider = getRoleProvider(); + } + return roleProvider; + } +} diff --git a/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java b/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java new file mode 100644 index 0000000000..da06a220bf --- /dev/null +++ b/model/legacy/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java @@ -0,0 +1,48 @@ +package org.keycloak.storage.datastore; + +import org.keycloak.Config; +import org.keycloak.Config.Scope; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.DatastoreProvider; +import org.keycloak.storage.DatastoreProviderFactory; + +public class LegacyDatastoreProviderFactory implements DatastoreProviderFactory { + + private static final String PROVIDER_ID = "legacy"; + private long clientStorageProviderTimeout; + private long roleStorageProviderTimeout; + + @Override + public DatastoreProvider create(KeycloakSession session) { + return new LegacyDatastoreProvider(this, session); + } + + @Override + public void init(Scope config) { + clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L); + roleStorageProviderTimeout = Config.scope("role").getLong("storageProviderTimeout", 3000L); + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + public long getClientStorageProviderTimeout() { + return clientStorageProviderTimeout; + } + + public long getRoleStorageProviderTimeout() { + return roleStorageProviderTimeout; + } + +} diff --git a/model/legacy/src/main/java/org/keycloak/utils/ServicesUtils.java b/model/legacy/src/main/java/org/keycloak/utils/ServicesUtils.java new file mode 100644 index 0000000000..a30cb0af5d --- /dev/null +++ b/model/legacy/src/main/java/org/keycloak/utils/ServicesUtils.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 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.utils; + +import org.jboss.logging.Logger; +import org.keycloak.executors.ExecutorsProvider; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; + +import static org.keycloak.common.util.StackUtil.getShortStackTrace; + +/** + * Utility class for general helper methods used across the keycloak-services. + */ +public class ServicesUtils { + + private static final Logger logger = Logger.getLogger(ServicesUtils.class); + + public static Function> timeBound(KeycloakSession session, + long timeout, + Function> func) { + ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads"); + return p -> { + // We are running another thread here, which serves as a time checking thread. When timeout is hit, the time + // checking thread will send interrupted flag to main thread, which can cause interruption of func execution. + // To support interruption func implementation should react to interrupt flag. + // If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time + // than the threshold given by timeout variable + Future timeCheckingThread = executor.submit(timeWarningRunnable(timeout, Thread.currentThread())); + try { + // We cannot run func in different than main thread, because main thread have, for example, EntityManager + // transaction context. If we run any operation on EntityManager in a different thread, it will fail + // with a transaction doesn't exist error + return func.apply(p); + } finally { + timeCheckingThread.cancel(true); + + if (Thread.interrupted()) { + logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace()); + } + } + }; + } + + public static Function timeBoundOne(KeycloakSession session, + long timeout, + Function func) { + ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads"); + return p -> { + // We are running another thread here, which serves as a time checking thread. When timeout is hit, the time + // checking thread will send interrupted flag to main thread, which can cause interruption of func execution. + // To support interruption func implementation should react to interrupt flag. + // If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time + // than the threshold given by timeout variable + Future warningThreadFuture = executor.submit(timeWarningRunnable(timeout, Thread.currentThread())); + try { + // We cannot run func in different than main thread, because main thread have, for example, EntityManager + // transaction context. If we run any operation on EntityManager in a different thread, it will fail + // with a transaction doesn't exist error + return func.apply(p); + } finally { + warningThreadFuture.cancel(true); + + if (Thread.interrupted()) { + logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace()); + } + } + }; + } + + public static Consumer consumeWithTimeBound(KeycloakSession session, + long timeout, + Consumer func) { + ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("storage-provider-threads"); + return p -> { + // We are running another thread here, which serves as a time checking thread. When timeout is hit, the time + // checking thread will send interrupted flag to main thread, which can cause interruption of func execution. + // To support interruption func implementation should react to interrupt flag. + // If func doesn't check the interrupted flag, the execution won't be interrupted and can take more time + // than the threshold given by timeout variable + Future warningThreadFuture = executor.submit(timeWarningRunnable(timeout, Thread.currentThread())); + try { + // We cannot run func in different than main thread, because main thread have, for example, EntityManager + // transaction context. If we run any operation on EntityManager in a different thread, it will fail + // with a transaction doesn't exist error + func.accept(p); + } finally { + warningThreadFuture.cancel(true); + + if (Thread.interrupted()) { + logger.warnf("Execution with object [%s] exceeded specified time limit %d. %s", p, timeout, getShortStackTrace()); + } + } + }; + } + + private static Runnable timeWarningRunnable(long timeout, Thread mainThread) { + return new Runnable() { + @Override + public void run() { + try { + Thread.sleep(timeout); + } catch (InterruptedException exception) { + return; // Do not interrupt if warning thread was interrupted (== main thread finished execution in time) + } + + mainThread.interrupt(); + } + }; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 8c699e39bc..6b791257c7 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -124,6 +124,10 @@ public class ModelToRepresentation { } + public static GroupRepresentation groupToBriefRepresentation(GroupModel g) { + return toRepresentation(g, false); + } + public static GroupRepresentation toRepresentation(GroupModel group, boolean full) { GroupRepresentation rep = new GroupRepresentation(); rep.setId(group.getId()); diff --git a/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProvider.java b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProvider.java new file mode 100644 index 0000000000..edd21e4913 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProvider.java @@ -0,0 +1,22 @@ +package org.keycloak.storage; + +import org.keycloak.models.ClientProvider; +import org.keycloak.models.ClientScopeProvider; +import org.keycloak.models.GroupProvider; +import org.keycloak.models.RealmProvider; +import org.keycloak.models.RoleProvider; +import org.keycloak.provider.Provider; + +public interface DatastoreProvider extends Provider { + + public ClientScopeProvider clientScopes(); + + public ClientProvider clients(); + + public GroupProvider groups(); + + public RealmProvider realms(); + + public RoleProvider roles(); + +} diff --git a/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProviderFactory.java new file mode 100644 index 0000000000..484eae25bd --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreProviderFactory.java @@ -0,0 +1,7 @@ +package org.keycloak.storage; + +import org.keycloak.provider.ProviderFactory; + +public interface DatastoreProviderFactory extends ProviderFactory { + +} diff --git a/server-spi-private/src/main/java/org/keycloak/storage/DatastoreSpi.java b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreSpi.java new file mode 100644 index 0000000000..a84d61f65b --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/storage/DatastoreSpi.java @@ -0,0 +1,27 @@ +package org.keycloak.storage; + +import org.keycloak.provider.Spi; + +public class DatastoreSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "datastore"; + } + + @Override + public Class getProviderClass() { + return DatastoreProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return DatastoreProviderFactory.class; + } + +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 597c19c74c..8174bc578d 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -80,6 +80,7 @@ org.keycloak.credential.hash.PasswordHashSpi org.keycloak.credential.CredentialSpi org.keycloak.keys.PublicKeyStorageSpi org.keycloak.keys.KeySpi +org.keycloak.storage.DatastoreSpi org.keycloak.storage.client.ClientStorageProviderSpi org.keycloak.storage.clientscope.ClientScopeStorageProviderSpi org.keycloak.storage.role.RoleStorageProviderSpi diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index bb2a838755..53fe40dfea 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -225,6 +225,7 @@ public interface KeycloakSession { /** * @return ClientScopeStorageManager instance + * @deprecated Use {@link #clientScopes()} instead */ ClientScopeProvider clientScopeStorageManager(); @@ -271,6 +272,7 @@ public interface KeycloakSession { /** * Keycloak specific local storage for client scopes. No cache in front, this api talks directly to database configured for Keycloak * + * @deprecated Use {@link #clientScopes()} instead * @return */ ClientScopeProvider clientScopeLocalStorage(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java index 2c7f53be77..01d6cc4c0d 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java @@ -80,7 +80,7 @@ public class RedirectUtils { @Deprecated private static Set getValidateRedirectUris(KeycloakSession session) { RealmModel realm = session.getContext().getRealm(); - return session.clientStorageManager().getAllRedirectUrisOfEnabledClients(realm).entrySet().stream() + return session.clients().getAllRedirectUrisOfEnabledClients(realm).entrySet().stream() .filter(me -> me.getKey().isEnabled() && OIDCLoginProtocol.LOGIN_PROTOCOL.equals(me.getKey().getProtocol()) && !me.getKey().isBearerOnly() && (me.getKey().isStandardFlowEnabled() || me.getKey().isImplicitFlowEnabled())) .map(me -> resolveValidRedirects(session, me.getKey().getRootUrl(), me.getValue())) .flatMap(Collection::stream) diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index cd423c8df1..af467879c7 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -24,33 +24,29 @@ import org.keycloak.keys.DefaultKeyManager; import org.keycloak.models.ClientProvider; import org.keycloak.models.ClientScopeProvider; import org.keycloak.models.GroupProvider; -import org.keycloak.models.UserLoginFailureProvider; -import org.keycloak.models.TokenManager; +import org.keycloak.models.KeyManager; import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakTransactionManager; -import org.keycloak.models.KeyManager; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleProvider; import org.keycloak.models.ThemeManager; +import org.keycloak.models.TokenManager; import org.keycloak.models.UserCredentialManager; +import org.keycloak.models.UserLoginFailureProvider; import org.keycloak.models.UserProvider; import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.UserCache; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.provider.InvalidationHandler.InvalidableObjectType; -import org.keycloak.provider.InvalidationHandler.ObjectType; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.InvalidationHandler.InvalidableObjectType; +import org.keycloak.provider.InvalidationHandler.ObjectType; import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.sessions.AuthenticationSessionProvider; -import org.keycloak.storage.ClientStorageManager; -import org.keycloak.storage.ClientScopeStorageManager; -import org.keycloak.storage.GroupStorageManager; -import org.keycloak.storage.RoleStorageManager; +import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.UserStorageManager; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.vault.DefaultVaultTranscriber; @@ -80,16 +76,8 @@ public class DefaultKeycloakSession implements KeycloakSession { private final DefaultKeycloakTransactionManager transactionManager; private final Map attributes = new HashMap<>(); private final Map> invalidationMap = new HashMap<>(); - private RealmProvider model; - private ClientProvider clientProvider; - private ClientScopeProvider clientScopeProvider; - private GroupProvider groupProvider; - private RoleProvider roleProvider; + private DatastoreProvider datastoreProvider; private UserStorageManager userStorageManager; - private ClientStorageManager clientStorageManager; - private ClientScopeStorageManager clientScopeStorageManager; - private RoleStorageManager roleStorageManager; - private GroupStorageManager groupStorageManager; private UserCredentialStoreManager userCredentialStorageManager; private UserSessionProvider sessionProvider; private UserLoginFailureProvider userLoginFailureProvider; @@ -113,53 +101,11 @@ public class DefaultKeycloakSession implements KeycloakSession { return context; } - private RealmProvider getRealmProvider() { - CacheRealmProvider cache = getProvider(CacheRealmProvider.class); - if (cache != null) { - return cache; - } else { - return getProvider(RealmProvider.class); - } - } - - private ClientProvider getClientProvider() { - // TODO: Extract ClientProvider from CacheRealmProvider and use that instead - ClientProvider cache = getProvider(CacheRealmProvider.class); - if (cache != null) { - return cache; - } else { - return clientStorageManager(); - } - } - - private ClientScopeProvider getClientScopeProvider() { - // TODO: Extract ClientScopeProvider from CacheRealmProvider and use that instead - ClientScopeProvider cache = getProvider(CacheRealmProvider.class); - if (cache != null) { - return cache; - } else { - return clientScopeStorageManager(); - } - } - - private GroupProvider getGroupProvider() { - // TODO: Extract GroupProvider from CacheRealmProvider and use that instead - GroupProvider cache = getProvider(CacheRealmProvider.class); - if (cache != null) { - return cache; - } else { - return groupStorageManager(); - } - } - - private RoleProvider getRoleProvider() { - // TODO: Extract RoleProvider from CacheRealmProvider and use that instead - RoleProvider cache = getProvider(CacheRealmProvider.class); - if (cache != null) { - return cache; - } else { - return roleStorageManager(); + private DatastoreProvider getDatastoreProvider() { + if (this.datastoreProvider == null) { + this.datastoreProvider = getProvider(DatastoreProvider.class); } + return this.datastoreProvider; } @Override @@ -228,59 +174,47 @@ public class DefaultKeycloakSession implements KeycloakSession { @Override public RealmProvider realmLocalStorage() { - return getProvider(RealmProvider.class); + return realms(); } @Override public ClientProvider clientLocalStorage() { - return getProvider(ClientProvider.class); + return clients(); } @Override public ClientScopeProvider clientScopeLocalStorage() { - return getProvider(ClientScopeProvider.class); + return clientScopes(); } @Override public GroupProvider groupLocalStorage() { - return getProvider(GroupProvider.class); + return groups(); } @Override public ClientProvider clientStorageManager() { - if (clientStorageManager == null) { - clientStorageManager = new ClientStorageManager(this, factory.getClientStorageProviderTimeout()); - } - return clientStorageManager; + return clients(); } @Override public ClientScopeProvider clientScopeStorageManager() { - if (clientScopeStorageManager == null) { - clientScopeStorageManager = new ClientScopeStorageManager(this); - } - return clientScopeStorageManager; + return clientScopes(); } @Override public RoleProvider roleLocalStorage() { - return getProvider(RoleProvider.class); + return roles(); } @Override public RoleProvider roleStorageManager() { - if (roleStorageManager == null) { - roleStorageManager = new RoleStorageManager(this, factory.getRoleStorageProviderTimeout()); - } - return roleStorageManager; + return roles(); } @Override public GroupProvider groupStorageManager() { - if (groupStorageManager == null) { - groupStorageManager = new GroupStorageManager(this); - } - return groupStorageManager; + return groups(); } @@ -413,42 +347,27 @@ public class DefaultKeycloakSession implements KeycloakSession { @Override public RealmProvider realms() { - if (model == null) { - model = getRealmProvider(); - } - return model; + return getDatastoreProvider().realms(); } @Override public ClientProvider clients() { - if (clientProvider == null) { - clientProvider = getClientProvider(); - } - return clientProvider; + return getDatastoreProvider().clients(); } @Override public ClientScopeProvider clientScopes() { - if (clientScopeProvider == null) { - clientScopeProvider = getClientScopeProvider(); - } - return clientScopeProvider; + return getDatastoreProvider().clientScopes(); } @Override public GroupProvider groups() { - if (groupProvider == null) { - groupProvider = getGroupProvider(); - } - return groupProvider; + return getDatastoreProvider().groups(); } @Override public RoleProvider roles() { - if (roleProvider == null) { - roleProvider = getRoleProvider(); - } - return roleProvider; + return getDatastoreProvider().roles(); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 2564d0fda6..5559092f86 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -49,6 +49,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import com.fasterxml.jackson.core.type.TypeReference; + import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.ResteasyProviderFactory; @@ -99,6 +101,7 @@ import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.LDAPCapabilityRepresentation; import org.keycloak.representations.idm.ManagementPermissionReference; import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.RealmEventsConfigRepresentation; @@ -113,13 +116,9 @@ import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; -import org.keycloak.representations.idm.LDAPCapabilityRepresentation; import org.keycloak.utils.ProfileHelper; import org.keycloak.utils.ReservedCharValidator; -import com.fasterxml.jackson.core.type.TypeReference; -import org.keycloak.utils.ServicesUtils; - /** * Base resource class for the admin REST api of one realm * @@ -1037,7 +1036,7 @@ public class RealmAdminResource { public Stream getDefaultGroups() { auth.realm().requireViewRealm(); - return realm.getDefaultGroupsStream().map(ServicesUtils::groupToBriefRepresentation); + return realm.getDefaultGroupsStream().map(ModelToRepresentation::groupToBriefRepresentation); } @PUT @NoCache diff --git a/services/src/main/java/org/keycloak/utils/ServicesUtils.java b/services/src/main/java/org/keycloak/utils/ServicesUtils.java index e994139cb5..fe2ba7eefc 100644 --- a/services/src/main/java/org/keycloak/utils/ServicesUtils.java +++ b/services/src/main/java/org/keycloak/utils/ServicesUtils.java @@ -34,6 +34,7 @@ import static org.keycloak.common.util.StackUtil.getShortStackTrace; /** * Utility class for general helper methods used across the keycloak-services. + * @deprecated - DELETE once only used from within legacy datastore module */ public class ServicesUtils { @@ -131,8 +132,4 @@ public class ServicesUtils { } }; } - - public static GroupRepresentation groupToBriefRepresentation(GroupModel g) { - return ModelToRepresentation.toRepresentation(g, false); - } } diff --git a/testsuite/model/pom.xml b/testsuite/model/pom.xml index 1683aaccc5..1a0aa2bf1a 100644 --- a/testsuite/model/pom.xml +++ b/testsuite/model/pom.xml @@ -193,102 +193,102 @@ - jpa + legacy-jpa - Jpa + LegacyJpa - jpa+infinispan + legacy-jpa+infinispan - Infinispan,Jpa + Infinispan,LegacyJpa - jpa+infinispan+client-storage + legacy-jpa+infinispan+client-storage - Jpa,Infinispan,HardcodedClientStorage + LegacyJpa,Infinispan,HardcodedClientStorage - jpa+cross-dc-infinispan + legacy-jpa+cross-dc-infinispan - CrossDCInfinispan,Jpa + CrossDCInfinispan,LegacyJpa - jpa+cross-dc-infinispan-offline-sessions-preloading + legacy-jpa+cross-dc-infinispan-offline-sessions-preloading - CrossDCInfinispan,Jpa + CrossDCInfinispan,LegacyJpa true - jpa+infinispan-offline-sessions-preloading + legacy-jpa+infinispan-offline-sessions-preloading - Infinispan,Jpa + Infinispan,LegacyJpa true - jpa-federation+infinispan + legacy-jpa-federation+infinispan - Infinispan,JpaFederation,TestsuiteUserMapStorage + Infinispan,LegacyJpaFederation,TestsuiteUserMapStorage - jpa-federation-backward+infinispan + legacy-jpa-federation-backward+infinispan - Infinispan,JpaFederation,BackwardsCompatibilityUserStorage + Infinispan,LegacyJpaFederation,BackwardsCompatibilityUserStorage - jpa-federation + legacy-jpa-federation - JpaFederation,TestsuiteUserMapStorage + LegacyJpaFederation,TestsuiteUserMapStorage - jpa-federation-backward + legacy-jpa-federation-backward - JpaFederation,BackwardsCompatibilityUserStorage + LegacyJpaFederation,BackwardsCompatibilityUserStorage - jpa-federation-file-storage + legacy-jpa-federation-file-storage - JpaFederation,TestsuiteUserFileStorage + LegacyJpaFederation,TestsuiteUserFileStorage - jpa-federation-file-storage+infinispan + legacy-jpa-federation-file-storage+infinispan - JpaFederation,TestsuiteUserFileStorage,Infinispan + LegacyJpaFederation,TestsuiteUserFileStorage,Infinispan - jpa-federation+ldap + legacy-jpa-federation+ldap - JpaFederation,LdapUserStorage + LegacyJpaFederation,LdapUserStorage - jpa-federation+ldap+infinispan + legacy-jpa-federation+ldap+infinispan - JpaFederation,LdapUserStorage,Infinispan + LegacyJpaFederation,LdapUserStorage,Infinispan @@ -296,7 +296,7 @@ map enabled - Jpa,Map,ConcurrentHashMapStorage + LegacyJpa,Map,ConcurrentHashMapStorage @@ -304,7 +304,7 @@ hot-rod enabled - Jpa,Map,HotRodMapStorage + LegacyJpa,Map,HotRodMapStorage diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Jpa.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpa.java similarity index 93% rename from testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Jpa.java rename to testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpa.java index 8981710bed..add3ee2680 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Jpa.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpa.java @@ -38,6 +38,8 @@ import org.keycloak.models.jpa.JpaRoleProviderFactory; import org.keycloak.models.jpa.JpaUserProviderFactory; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; +import org.keycloak.storage.DatastoreSpi; +import org.keycloak.storage.datastore.LegacyDatastoreProviderFactory; import org.keycloak.testsuite.model.Config; import com.google.common.collect.ImmutableSet; import java.util.Set; @@ -48,7 +50,7 @@ import org.keycloak.protocol.LoginProtocolSpi; * * @author hmlnarik */ -public class Jpa extends KeycloakModelParameters { +public class LegacyJpa extends KeycloakModelParameters { static final Set> ALLOWED_SPIS = ImmutableSet.>builder() // jpa-specific @@ -57,6 +59,8 @@ public class Jpa extends KeycloakModelParameters { .add(LiquibaseConnectionSpi.class) .add(UserSessionPersisterSpi.class) + .add(DatastoreSpi.class) + //required for migrateModel .add(MigrationSpi.class) .add(LoginProtocolSpi.class) @@ -65,6 +69,8 @@ public class Jpa extends KeycloakModelParameters { static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() // jpa-specific + .add(LegacyDatastoreProviderFactory.class) + .add(DefaultJpaConnectionProviderFactory.class) .add(JPAAuthorizationStoreFactory.class) .add(JpaClientProviderFactory.class) @@ -85,7 +91,7 @@ public class Jpa extends KeycloakModelParameters { .build(); - public Jpa() { + public LegacyJpa() { super(ALLOWED_SPIS, ALLOWED_FACTORIES); } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaFederation.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpaFederation.java similarity index 93% rename from testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaFederation.java rename to testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpaFederation.java index 29ba4088af..8cfb500bbd 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaFederation.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LegacyJpaFederation.java @@ -37,12 +37,12 @@ import org.keycloak.testsuite.model.Config; * * @author hmlnarik */ -public class JpaFederation extends KeycloakModelParameters { +public class LegacyJpaFederation extends KeycloakModelParameters { private final AtomicInteger counter = new AtomicInteger(); static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .addAll(Jpa.ALLOWED_SPIS) + .addAll(LegacyJpa.ALLOWED_SPIS) .add(UserStorageProviderSpi.class) .add(UserFederatedStorageProviderSpi.class) .add(ClientScopeStorageProviderSpi.class) @@ -50,12 +50,12 @@ public class JpaFederation extends KeycloakModelParameters { .build(); static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .addAll(Jpa.ALLOWED_FACTORIES) + .addAll(LegacyJpa.ALLOWED_FACTORIES) .add(JpaUserFederatedStorageProviderFactory.class) .add(ClientScopeStorageProviderFactory.class) .build(); - public JpaFederation() { + public LegacyJpaFederation() { super(ALLOWED_SPIS, ALLOWED_FACTORIES); } @@ -74,6 +74,6 @@ public class JpaFederation extends KeycloakModelParameters { @Override public void updateConfig(Config cf) { - Jpa.updateConfigForJpa(cf); + LegacyJpa.updateConfigForJpa(cf); } }