diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index bd171a04bf..f632e2acc3 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -23,6 +23,7 @@ import org.keycloak.credential.CredentialInput; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.LegacySessionSupportProvider; import org.keycloak.models.cache.infinispan.events.InvalidationEvent; import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.component.ComponentModel; @@ -372,7 +373,7 @@ public class UserCacheSession implements UserCache.Streams, OnCreateComponent, O private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) { ((OnUserCache)getDelegate()).onCache(realm, adapter, delegate); - ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate); + ((OnUserCache) session.getProvider(LegacySessionSupportProvider.class).userCredentialManager()).onCache(realm, adapter, delegate); } @Override diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/model/legacy-services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java similarity index 86% rename from services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java rename to model/legacy-services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java index 45e2cb992b..d6086a10bd 100644 --- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java +++ b/model/legacy-services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java @@ -16,6 +16,7 @@ */ package org.keycloak.credential; +import org.jboss.logging.Logger; import org.keycloak.common.util.reflections.Types; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.KeycloakSession; @@ -33,8 +34,10 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserCredentialStoreManager - implements UserCredentialManager.Streams, OnUserCache { +@Deprecated +public class UserCredentialStoreManager implements UserCredentialManager.Streams, OnUserCache { + + private final static Logger log = Logger.getLogger(UserCredentialStoreManager.class); private final KeycloakSession session; @@ -45,90 +48,91 @@ public class UserCredentialStoreManager @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); user.getUserCredentialManager().updateStoredCredential(cred); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().createStoredCredential(cred); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().removeStoredCredentialById(id); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getStoredCredentialById(id); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public Stream getStoredCredentialsStream(RealmModel realm, UserModel user) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getStoredCredentialsStream(); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public Stream getStoredCredentialsByTypeStream(RealmModel realm, UserModel user, String type) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getStoredCredentialsByTypeStream(type); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) { - // TODO: no longer used in non-legacy code, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getStoredCredentialByNameAndType(name, type); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean moveCredentialTo(RealmModel realm, UserModel user, String id, String newPreviousCredentialId){ - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().moveStoredCredentialTo(id, newPreviousCredentialId); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean isValid(RealmModel realm, UserModel user, CredentialInput... inputs) { - // TODO: no longer used, can be removed + warnAboutUsage(); return isValid(realm, user, Arrays.asList(inputs)); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public CredentialModel createCredentialThroughProvider(RealmModel realm, UserModel user, CredentialModel model){ - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().createCredentialThroughProvider(model); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public void updateCredentialLabel(RealmModel realm, UserModel user, String credentialId, String userLabel){ - // TODO: no longer used, can be removed + warnAboutUsage(); user.getUserCredentialManager().updateCredentialLabel(credentialId, userLabel); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean isValid(RealmModel realm, UserModel user, List inputs) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().isValid(inputs); } @Deprecated // Keep this up to and including Keycloak 19, then inline public static Stream getCredentialProviders(KeycloakSession session, Class type) { - // TODO: no longer used, can be removed + // called via #onCache() + // warnAboutUsage(); return session.getKeycloakSessionFactory().getProviderFactoriesStream(CredentialProvider.class) .filter(f -> Types.supports(type, f, CredentialProviderFactory.class)) .map(f -> (T) session.getProvider(CredentialProvider.class, f.getId())); @@ -137,56 +141,57 @@ public class UserCredentialStoreManager @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().updateCredential(input); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { - // TODO: no longer used, can be removed + warnAboutUsage(); user.getUserCredentialManager().disableCredentialType(credentialType); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public Stream getDisableableCredentialTypesStream(RealmModel realm, UserModel user) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getDisableableCredentialTypesStream(); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().isConfiguredFor(type); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public boolean isConfiguredLocally(RealmModel realm, UserModel user, String type) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().isConfiguredLocally(type); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public CredentialValidationOutput authenticate(KeycloakSession session, RealmModel realm, CredentialInput input) { - // TODO: no longer used, can be removed + warnAboutUsage(); return session.users().getUserByCredential(realm, input); } @Override @Deprecated // Keep this up to and including Keycloak 19, then remove it together with the OnUserCache class public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) { - // TODO: only called from legacy code (infinispan), can be removed + // called by UserCacheSession#onCache, therefore don't warn here + // warnAboutUsage(); getCredentialProviders(session, OnUserCache.class).forEach(validator -> validator.onCache(realm, user, delegate)); } @Override @Deprecated // Keep this up to and including Keycloak 19, the use methods on user.getUserCredentialManager() instead public Stream getConfiguredUserStorageCredentialTypesStream(RealmModel realm, UserModel user) { - // TODO: no longer used, can be removed + warnAboutUsage(); return user.getUserCredentialManager().getConfiguredUserStorageCredentialTypesStream(); } @@ -195,4 +200,11 @@ public class UserCredentialStoreManager } + private static void warnAboutUsage() { + if (log.isEnabled(Logger.Level.WARN)) { + // check if warning is enabled first before constructing the exception that is expensive to construct + log.warn("Calls to session.userCredentialManager() now deprecated. Use user.getUserCredentialManager() instead!", new RuntimeException()); + } + } + } diff --git a/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactoryImpl.java b/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactoryImpl.java new file mode 100644 index 0000000000..10d3af571f --- /dev/null +++ b/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactoryImpl.java @@ -0,0 +1,37 @@ +package org.keycloak.services.legacysessionsupport; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.LegacySessionSupportProvider; + +/** + * @author Alexander Schwartz + */ +public class LegacySessionSupportProviderFactoryImpl implements LegacySessionSupportProviderFactory { + + private static final String PROVIDER_ID = "default"; + + @Override + public LegacySessionSupportProvider create(KeycloakSession session) { + return new LegacySessionSupportProviderImpl(session); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } + +} diff --git a/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderImpl.java b/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderImpl.java new file mode 100644 index 0000000000..b1db22d3df --- /dev/null +++ b/model/legacy-services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderImpl.java @@ -0,0 +1,31 @@ +package org.keycloak.services.legacysessionsupport; + +import org.keycloak.credential.UserCredentialStoreManager; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.UserCredentialManager; +import org.keycloak.models.LegacySessionSupportProvider; + +/** + * @author Alexander Schwartz + */ +public class LegacySessionSupportProviderImpl implements LegacySessionSupportProvider { + + private final KeycloakSession session; + + public LegacySessionSupportProviderImpl(KeycloakSession session) { + this.session = session; + } + + @Override + public void close() { + + } + + @Override + @Deprecated + public UserCredentialManager userCredentialManager() { + // UserCacheSession calls session.userCredentialManager().onCache(), therefore can't trigger a warning here at the moment. + return new UserCredentialStoreManager(session); + } + +} diff --git a/model/legacy-services/src/main/resources/META-INF/services/org.keycloak.services.legacysessionsupport.LegacySessionSupportProviderFactory b/model/legacy-services/src/main/resources/META-INF/services/org.keycloak.services.legacysessionsupport.LegacySessionSupportProviderFactory new file mode 100644 index 0000000000..182d5fb380 --- /dev/null +++ b/model/legacy-services/src/main/resources/META-INF/services/org.keycloak.services.legacysessionsupport.LegacySessionSupportProviderFactory @@ -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.services.legacysessionsupport.LegacySessionSupportProviderFactoryImpl \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/models/LegacySessionSupportProvider.java b/server-spi-private/src/main/java/org/keycloak/models/LegacySessionSupportProvider.java new file mode 100644 index 0000000000..f853de1b95 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/models/LegacySessionSupportProvider.java @@ -0,0 +1,15 @@ +package org.keycloak.models; + +import org.keycloak.provider.Provider; + +/** + * Support for elements in Keycloak's session that are deprecated. + * This allows the deprecated implementations to be moved to the legacy module. + * + * @author Alexander Schwartz + */ +public interface LegacySessionSupportProvider extends Provider { + + @Deprecated + UserCredentialManager userCredentialManager(); +} diff --git a/server-spi/src/main/java/org/keycloak/models/SingleUserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/SingleUserCredentialManager.java index 7e2ec039f3..1e452b2246 100644 --- a/server-spi/src/main/java/org/keycloak/models/SingleUserCredentialManager.java +++ b/server-spi/src/main/java/org/keycloak/models/SingleUserCredentialManager.java @@ -68,7 +68,9 @@ public interface SingleUserCredentialManager { @Deprecated boolean isConfiguredLocally(String type); + // TODO: not needed for new store? -> no, will be removed without replacement Stream getConfiguredUserStorageCredentialTypesStream(); + // TODO: not needed for new store? -> no, will be removed without replacement CredentialModel createCredentialThroughProvider(CredentialModel model); } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 3f9c0260d8..ea84261e97 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -18,7 +18,6 @@ package org.keycloak.services; import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentModel; -import org.keycloak.credential.UserCredentialStoreManager; import org.keycloak.jose.jws.DefaultTokenManager; import org.keycloak.keys.DefaultKeyManager; import org.keycloak.models.ClientProvider; @@ -45,6 +44,7 @@ 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.models.LegacySessionSupportProvider; import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.federated.UserFederatedStorageProvider; @@ -76,12 +76,13 @@ public class DefaultKeycloakSession implements KeycloakSession { private final Map attributes = new HashMap<>(); private final Map> invalidationMap = new HashMap<>(); private DatastoreProvider datastoreProvider; - private UserCredentialStoreManager userCredentialStorageManager; + @Deprecated + private UserCredentialManager userCredentialStorageManager; private UserSessionProvider sessionProvider; private UserLoginFailureProvider userLoginFailureProvider; private AuthenticationSessionProvider authenticationSessionProvider; private UserFederatedStorageProvider userFederatedStorageProvider; - private KeycloakContext context; + private final KeycloakContext context; private KeyManager keyManager; private ThemeManager themeManager; private TokenManager tokenManager; @@ -229,8 +230,15 @@ public class DefaultKeycloakSession implements KeycloakSession { } @Override + @Deprecated public UserCredentialManager userCredentialManager() { - if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this); + if (userCredentialStorageManager == null) { + LegacySessionSupportProvider provider = this.getProvider(LegacySessionSupportProvider.class); + if (provider == null) { + throw new IllegalStateException("legacy support for a UserCredentialManager is not enabled"); + } + userCredentialStorageManager = provider.userCredentialManager(); + } return userCredentialStorageManager; } diff --git a/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactory.java b/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactory.java new file mode 100644 index 0000000000..5495784c8c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportProviderFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.services.legacysessionsupport; + +import org.keycloak.models.LegacySessionSupportProvider; +import org.keycloak.provider.ProviderFactory; + +/** + * @author Alexander Schwartz + */ +public interface LegacySessionSupportProviderFactory extends ProviderFactory { +} diff --git a/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportSpi.java b/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportSpi.java new file mode 100644 index 0000000000..7f26000c63 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/legacysessionsupport/LegacySessionSupportSpi.java @@ -0,0 +1,32 @@ +package org.keycloak.services.legacysessionsupport; + +import org.keycloak.models.LegacySessionSupportProvider; +import org.keycloak.provider.Provider; +import org.keycloak.provider.Spi; + +/** + * @author Alexander Schwartz + */ +public class LegacySessionSupportSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "legacy-session-support"; + } + + @Override + public Class getProviderClass() { + return LegacySessionSupportProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return LegacySessionSupportProviderFactory.class; + } + +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 997589cf63..baf42d0345 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -28,4 +28,4 @@ org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelSpi org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolverSpi org.keycloak.protocol.oidc.rar.AuthorizationRequestParserSpi org.keycloak.services.resources.admin.ext.AdminRealmResourceSpi -org.keycloak.credential.SingleUserCredentialManagerSpi +org.keycloak.services.legacysessionsupport.LegacySessionSupportSpi diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java index efb73e0379..18dce7dbca 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java @@ -27,6 +27,8 @@ import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionPr import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; +import org.keycloak.services.legacysessionsupport.LegacySessionSupportProviderFactory; +import org.keycloak.services.legacysessionsupport.LegacySessionSupportSpi; import org.keycloak.sessions.AuthenticationSessionSpi; import org.keycloak.sessions.StickySessionEncoderProviderFactory; import org.keycloak.sessions.StickySessionEncoderSpi; @@ -62,6 +64,8 @@ public class Infinispan extends KeycloakModelParameters { .add(ActionTokenStoreSpi.class) .add(SingleUseObjectSpi.class) + .add(LegacySessionSupportSpi.class) // necessary as it will call session.userCredentialManager().onCache() + .build(); static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() @@ -76,6 +80,7 @@ public class Infinispan extends KeycloakModelParameters { .add(InfinispanSingleUseObjectProviderFactory.class) .add(StickySessionEncoderProviderFactory.class) .add(TimerProviderFactory.class) + .add(LegacySessionSupportProviderFactory.class) .build(); @Override