mirror of
https://github.com/keycloak/keycloak.git
synced 2026-06-09 00:52:07 -04:00
Create CacheRemoteConfigProvider (#38570)
Closes #38496 Signed-off-by: Pedro Ruivo <pruivo@redhat.com>
This commit is contained in:
parent
60fb7a5fa7
commit
636fffe0bc
16 changed files with 847 additions and 274 deletions
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -46,17 +46,24 @@ import org.keycloak.cluster.ManagedCacheManagerProvider;
|
|||
import org.keycloak.connections.infinispan.remote.RemoteInfinispanConnectionProvider;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.marshalling.KeycloakIndexSchemaUtil;
|
||||
import org.keycloak.marshalling.KeycloakModelSchema;
|
||||
import org.keycloak.marshalling.Marshalling;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RealmRemovedEvent;
|
||||
import org.keycloak.models.cache.infinispan.events.RealmUpdatedEvent;
|
||||
import org.keycloak.models.sessions.infinispan.query.ClientSessionQueries;
|
||||
import org.keycloak.models.sessions.infinispan.query.UserSessionQueries;
|
||||
import org.keycloak.models.sessions.infinispan.remote.RemoteInfinispanAuthenticationSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.remote.RemoteUserLoginFailureProviderFactory;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.PostMigrationEvent;
|
||||
import org.keycloak.provider.InvalidationHandler.ObjectType;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProvider;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.ACTION_TOKEN_CACHE;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME;
|
||||
|
|
@ -64,7 +71,6 @@ import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.A
|
|||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_DEFAULT_MAX;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.JGROUPS_BIND_ADDR;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR;
|
||||
|
|
@ -83,7 +89,6 @@ import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.U
|
|||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.WORK_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.skipSessionsCacheIfRequired;
|
||||
import static org.keycloak.connections.infinispan.InfinispanUtil.configureTransport;
|
||||
import static org.keycloak.connections.infinispan.InfinispanUtil.createCacheConfigurationBuilder;
|
||||
import static org.keycloak.connections.infinispan.InfinispanUtil.getActionTokenCacheConfig;
|
||||
|
|
@ -153,6 +158,10 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
cacheManager.stop();
|
||||
}
|
||||
});
|
||||
if (remoteCacheManager != null) {
|
||||
remoteCacheManager.close();
|
||||
remoteCacheManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,67 +184,68 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
}
|
||||
|
||||
protected void lazyInit(KeycloakSession keycloakSession) {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
EmbeddedCacheManager managedCacheManager = null;
|
||||
RemoteCacheManager rcm = null;
|
||||
Iterator<ManagedCacheManagerProvider> providers = ServiceLoader.load(ManagedCacheManagerProvider.class, DefaultInfinispanConnectionProvider.class.getClassLoader())
|
||||
.iterator();
|
||||
|
||||
if (providers.hasNext()) {
|
||||
ManagedCacheManagerProvider provider = providers.next();
|
||||
|
||||
if (providers.hasNext()) {
|
||||
throw new RuntimeException("Multiple " + org.keycloak.cluster.ManagedCacheManagerProvider.class + " providers found.");
|
||||
}
|
||||
|
||||
managedCacheManager = provider.getEmbeddedCacheManager(keycloakSession, config);
|
||||
if (InfinispanUtils.isRemoteInfinispan()) {
|
||||
rcm = provider.getRemoteCacheManager(config);
|
||||
}
|
||||
}
|
||||
|
||||
// store it in a locale variable first, so it is not visible to the outside, yet
|
||||
EmbeddedCacheManager localCacheManager;
|
||||
if (managedCacheManager == null) {
|
||||
if (!config.getBoolean("embedded", false)) {
|
||||
throw new RuntimeException("No " + ManagedCacheManagerProvider.class.getName() + " found. If running in embedded mode set the [embedded] property to this provider.");
|
||||
}
|
||||
localCacheManager = initEmbedded();
|
||||
if (InfinispanUtils.isRemoteInfinispan()) {
|
||||
rcm = initRemote();
|
||||
}
|
||||
} else {
|
||||
localCacheManager = initContainerManaged(managedCacheManager);
|
||||
}
|
||||
|
||||
logger.infof(topologyInfo.toString());
|
||||
|
||||
// only set the cache manager attribute at the very end to avoid passing a half-initialized entry callers
|
||||
cacheManager = localCacheManager;
|
||||
remoteCacheManager = rcm;
|
||||
}
|
||||
if (cacheManager != null) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (cacheManager != null) {
|
||||
return;
|
||||
}
|
||||
EmbeddedCacheManager managedCacheManager = null;
|
||||
Iterator<ManagedCacheManagerProvider> providers = ServiceLoader.load(ManagedCacheManagerProvider.class, DefaultInfinispanConnectionProvider.class.getClassLoader())
|
||||
.iterator();
|
||||
|
||||
if (providers.hasNext()) {
|
||||
ManagedCacheManagerProvider provider = providers.next();
|
||||
|
||||
if (providers.hasNext()) {
|
||||
throw new RuntimeException("Multiple " + org.keycloak.cluster.ManagedCacheManagerProvider.class + " providers found.");
|
||||
}
|
||||
|
||||
managedCacheManager = provider.getEmbeddedCacheManager(keycloakSession, config);
|
||||
}
|
||||
|
||||
// store it in a locale variable first, so it is not visible to the outside, yet
|
||||
EmbeddedCacheManager localCacheManager;
|
||||
if (managedCacheManager == null) {
|
||||
if (!config.getBoolean("embedded", false)) {
|
||||
throw new RuntimeException("No " + ManagedCacheManagerProvider.class.getName() + " found. If running in embedded mode set the [embedded] property to this provider.");
|
||||
}
|
||||
localCacheManager = initEmbedded();
|
||||
} else {
|
||||
localCacheManager = initContainerManaged(managedCacheManager);
|
||||
}
|
||||
|
||||
logger.infof(topologyInfo.toString());
|
||||
|
||||
|
||||
// only set the cache manager attribute at the very end to avoid passing a half-initialized entry callers
|
||||
cacheManager = localCacheManager;
|
||||
remoteCacheManager = createRemoteCacheManager(keycloakSession);
|
||||
}
|
||||
}
|
||||
|
||||
private RemoteCacheManager initRemote() {
|
||||
var host = config.get("remoteStoreHost", "127.0.0.1");
|
||||
var port = config.getInt("remoteStorePort", 11222);
|
||||
|
||||
org.infinispan.client.hotrod.configuration.ConfigurationBuilder builder = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
|
||||
builder.addServer().host(host).port(port);
|
||||
builder.connectionPool().maxActive(16).exhaustedAction(org.infinispan.client.hotrod.configuration.ExhaustedAction.CREATE_NEW);
|
||||
|
||||
Marshalling.configure(builder);
|
||||
|
||||
RemoteCacheManager remoteCacheManager = new RemoteCacheManager(builder.build());
|
||||
|
||||
// establish connection to all caches
|
||||
skipSessionsCacheIfRequired(Arrays.stream(CLUSTERED_CACHE_NAMES)).forEach(remoteCacheManager::getCache);
|
||||
return remoteCacheManager;
|
||||
protected RemoteCacheManager createRemoteCacheManager(KeycloakSession session) {
|
||||
var remoteConfig = session.getProvider(CacheRemoteConfigProvider.class).configuration();
|
||||
if (remoteConfig.isEmpty()) {
|
||||
logger.debug("Remote Cache feature is disabled");
|
||||
return null;
|
||||
}
|
||||
logger.debug("Remote Cache feature is enabled");
|
||||
var rcm = new RemoteCacheManager(remoteConfig.get());
|
||||
|
||||
// upload the schema before trying to access the caches
|
||||
// not caching the list; it is only used during startup
|
||||
var entities = List.of(
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(RemoteUserLoginFailureProviderFactory.PROTO_ENTITY, LOGIN_FAILURE_CACHE_NAME),
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(RemoteInfinispanAuthenticationSessionProviderFactory.PROTO_ENTITY, AUTHENTICATION_SESSIONS_CACHE_NAME),
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(ClientSessionQueries.CLIENT_SESSION, CLIENT_SESSION_CACHE_NAME),
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(ClientSessionQueries.CLIENT_SESSION, OFFLINE_CLIENT_SESSION_CACHE_NAME),
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(UserSessionQueries.USER_SESSION, USER_SESSION_CACHE_NAME),
|
||||
new KeycloakIndexSchemaUtil.IndexedEntity(UserSessionQueries.USER_SESSION, OFFLINE_USER_SESSION_CACHE_NAME)
|
||||
);
|
||||
KeycloakIndexSchemaUtil.uploadAndReindexCaches(rcm, KeycloakModelSchema.INSTANCE, entities);
|
||||
return rcm;
|
||||
}
|
||||
|
||||
protected EmbeddedCacheManager initContainerManaged(EmbeddedCacheManager cacheManager) {
|
||||
|
|
@ -438,6 +448,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||
|
||||
@Override
|
||||
public Set<Class<? extends Provider>> dependsOn() {
|
||||
return Set.of(JpaConnectionProvider.class);
|
||||
return Set.of(JpaConnectionProvider.class, CacheRemoteConfigProvider.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.marshalling;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
|
@ -24,14 +25,24 @@ import java.util.stream.Collectors;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import org.infinispan.api.annotations.indexing.model.Values;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.client.hotrod.RemoteCacheManager;
|
||||
import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
|
||||
import org.infinispan.commons.internal.InternalCacheNames;
|
||||
import org.infinispan.protostream.GeneratedSchema;
|
||||
import org.infinispan.protostream.config.Configuration;
|
||||
import org.infinispan.protostream.descriptors.AnnotationElement;
|
||||
import org.infinispan.protostream.descriptors.Descriptor;
|
||||
import org.infinispan.protostream.descriptors.FieldDescriptor;
|
||||
import org.infinispan.protostream.descriptors.FileDescriptor;
|
||||
import org.infinispan.protostream.impl.AnnotatedDescriptorImpl;
|
||||
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
public class KeycloakIndexSchemaUtil {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
// Basic annotation data
|
||||
private static final String BASIC_ANNOTATION = "Basic";
|
||||
private static final String NAME_ATTRIBUTE = "name";
|
||||
|
|
@ -44,6 +55,87 @@ public class KeycloakIndexSchemaUtil {
|
|||
// we only use Basic annotation, we may need to add others in the future.
|
||||
private static final List<String> INDEX_ANNOTATION = List.of(BASIC_ANNOTATION);
|
||||
|
||||
/**
|
||||
* Uploads the {@link GeneratedSchema} to the Infinispan cluster.
|
||||
* <p>
|
||||
* If indexing is enabled for one or more entities present in the {@link GeneratedSchema}, users may add a list of
|
||||
* entities, and the caches where they live. This method will update the indexing schema and perform the reindexing
|
||||
* for the new schema. Note that reindexing may be an expensive operation, depending on the amount of data.
|
||||
*
|
||||
* @param remoteCacheManager The {@link RemoteCacheManager} connected to the Infinispan server.
|
||||
* @param schema The {@link GeneratedSchema} instance to upload.
|
||||
* @param indexedEntities The {@link List} of indexed entities and their caches. Duplicates are allowed if the
|
||||
* same entity is stored in multiple caches.
|
||||
* @throws NullPointerException if {@code remoteCacheManager} or {@code schema} is null.
|
||||
*/
|
||||
public static void uploadAndReindexCaches(RemoteCacheManager remoteCacheManager, GeneratedSchema schema, List<IndexedEntity> indexedEntities) {
|
||||
var key = schema.getProtoFileName();
|
||||
var current = schema.getProtoFile();
|
||||
|
||||
var protostreamMetadataCache = remoteCacheManager.<String, String>getCache(InternalCacheNames.PROTOBUF_METADATA_CACHE_NAME);
|
||||
var stored = protostreamMetadataCache.getWithMetadata(key);
|
||||
if (stored == null) {
|
||||
if (protostreamMetadataCache.putIfAbsent(key, current) == null) {
|
||||
logger.info("Infinispan ProtoStream schema uploaded for the first time.");
|
||||
} else {
|
||||
logger.info("Failed to update Infinispan ProtoStream schema. Assumed it was updated by other Keycloak server.");
|
||||
}
|
||||
checkForProtoSchemaErrors(protostreamMetadataCache);
|
||||
return;
|
||||
}
|
||||
if (Objects.equals(stored.getValue(), current)) {
|
||||
logger.info("Infinispan ProtoStream schema is up to date!");
|
||||
return;
|
||||
}
|
||||
if (protostreamMetadataCache.replaceWithVersion(key, current, stored.getVersion())) {
|
||||
logger.info("Infinispan ProtoStream schema successful updated.");
|
||||
reindexCaches(remoteCacheManager, stored.getValue(), current, indexedEntities);
|
||||
} else {
|
||||
logger.info("Failed to update Infinispan ProtoStream schema. Assumed it was updated by other Keycloak server.");
|
||||
}
|
||||
checkForProtoSchemaErrors(protostreamMetadataCache);
|
||||
}
|
||||
|
||||
private static void checkForProtoSchemaErrors(RemoteCache<String, String> protostreamMetadataCache) {
|
||||
var errors = protostreamMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX);
|
||||
if (errors == null) {
|
||||
return;
|
||||
}
|
||||
for (String errorFile : errors.split("\n")) {
|
||||
logger.errorf("%nThere was an error in proto file: %s%nError message: %s%nCurrent proto schema: %s%n",
|
||||
errorFile,
|
||||
protostreamMetadataCache.get(errorFile + ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX),
|
||||
protostreamMetadataCache.get(errorFile));
|
||||
}
|
||||
}
|
||||
|
||||
private static void reindexCaches(RemoteCacheManager remoteCacheManager, String oldSchema, String newSchema, List<IndexedEntity> indexedEntities) {
|
||||
if (indexedEntities == null || indexedEntities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
var oldPS = KeycloakModelSchema.parseProtoSchema(oldSchema);
|
||||
var newPS = KeycloakModelSchema.parseProtoSchema(newSchema);
|
||||
var admin = remoteCacheManager.administration();
|
||||
|
||||
indexedEntities.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(indexedEntity -> isEntityChanged(oldPS, newPS, indexedEntity.entity()))
|
||||
.map(IndexedEntity::cache)
|
||||
.distinct()
|
||||
.forEach(cacheName -> updateSchemaAndReIndexCache(admin, cacheName));
|
||||
}
|
||||
|
||||
private static boolean isEntityChanged(FileDescriptor oldSchema, FileDescriptor newSchema, String entity) {
|
||||
var v1 = KeycloakModelSchema.findEntity(oldSchema, entity);
|
||||
var v2 = KeycloakModelSchema.findEntity(newSchema, entity);
|
||||
return v1.isPresent() && v2.isPresent() && KeycloakIndexSchemaUtil.isIndexSchemaChanged(v1.get(), v2.get());
|
||||
}
|
||||
|
||||
private static void updateSchemaAndReIndexCache(RemoteCacheManagerAdmin admin, String cacheName) {
|
||||
admin.updateIndexSchema(cacheName);
|
||||
admin.reindexCache(cacheName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the annotations to the ProtoStream parser.
|
||||
*/
|
||||
|
|
@ -72,7 +164,8 @@ public class KeycloakIndexSchemaUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Compares two entities and returns {@code true} if any indexing related annotation were changed, added or removed.
|
||||
* Compares two entities and returns {@code true} if any indexing related annotation were changed, added or
|
||||
* removed.
|
||||
*/
|
||||
public static boolean isIndexSchemaChanged(Descriptor oldDescriptor, Descriptor newDescriptor) {
|
||||
var allFields = Stream.concat(
|
||||
|
|
@ -157,4 +250,10 @@ public class KeycloakIndexSchemaUtil {
|
|||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getValue().getValue()));
|
||||
}
|
||||
|
||||
public record IndexedEntity(String entity, String cache) {
|
||||
public IndexedEntity {
|
||||
Objects.requireNonNull(entity);
|
||||
Objects.requireNonNull(cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.infinispan.client.hotrod.configuration.Configuration;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* A provider to create a configuration to the Hot Rod client.
|
||||
*/
|
||||
public interface CacheRemoteConfigProvider extends Provider {
|
||||
|
||||
/**
|
||||
* Creates the {@link Configuration} for the Hot Rod client.
|
||||
* <p>
|
||||
* The optional signal if a Hot Rod client should be instantiated and started. If present, it assumes an external
|
||||
* Infinispan cluster is ready and online, otherwise Keycloak fails to start.
|
||||
*
|
||||
* @return The {@link Configuration} for the Hot Rod client.
|
||||
*/
|
||||
Optional<Configuration> configuration();
|
||||
|
||||
@Override
|
||||
default void close() {
|
||||
//no-op
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* A factory for {@link CacheRemoteConfigProvider}
|
||||
*
|
||||
* @see CacheRemoteConfigProvider
|
||||
*/
|
||||
public interface CacheRemoteConfigProviderFactory extends ProviderFactory<CacheRemoteConfigProvider> {
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan;
|
||||
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* An SPI to generate the configuration for the Hot Rod client.
|
||||
*/
|
||||
public class CacheRemoteConfigProviderSpi implements Spi {
|
||||
|
||||
public static final String SPI_NAME = "cacheRemote";
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return SPI_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<CacheRemoteConfigProvider> getProviderClass() {
|
||||
return CacheRemoteConfigProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<CacheRemoteConfigProviderFactory> getProviderFactoryClass() {
|
||||
return CacheRemoteConfigProviderFactory.class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan.impl;
|
||||
|
||||
import org.keycloak.config.Option;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
|
||||
/**
|
||||
* Utility method for this package and subpackages
|
||||
*/
|
||||
public final class Util {
|
||||
|
||||
private Util() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the {@link Option} information into the {@link ProviderConfigurationBuilder}.
|
||||
*
|
||||
* @param builder The property to set/configure.
|
||||
* @param name The desired property name.
|
||||
* @param label The label of the property's argument.
|
||||
* @param type The type of the property's value.
|
||||
* @param option The source {@link Option} to gather the information.
|
||||
* @param isSecret {@code true} if the property is a secret.
|
||||
*/
|
||||
public static void copyFromOption(ProviderConfigurationBuilder builder, String name, String label, String type, Option<?> option, boolean isSecret) {
|
||||
var property = builder.property()
|
||||
.name(name)
|
||||
.helpText(option.getDescription())
|
||||
.label(label)
|
||||
.type(type)
|
||||
.secret(isSecret);
|
||||
option.getDefaultValue().ifPresent(property::defaultValue);
|
||||
property.options(option.getExpectedValues());
|
||||
property.add();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan.impl.remote;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.infinispan.client.hotrod.configuration.AuthenticationConfigurationBuilder;
|
||||
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
|
||||
import org.infinispan.client.hotrod.configuration.Configuration;
|
||||
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
|
||||
import org.infinispan.client.hotrod.configuration.ExhaustedAction;
|
||||
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
|
||||
import org.infinispan.commons.dataconversion.MediaType;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.config.CachingOptions;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.marshalling.Marshalling;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProvider;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.skipSessionsCacheIfRequired;
|
||||
import static org.keycloak.spi.infinispan.impl.Util.copyFromOption;
|
||||
import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRAM_SHA_512;
|
||||
|
||||
/**
|
||||
* The default implementation for {@link CacheRemoteConfigProviderFactory} and {@link CacheRemoteConfigProvider}.
|
||||
* <p>
|
||||
* It is used when an external Infinispan cluster is enabled.
|
||||
*/
|
||||
public class DefaultCacheRemoteConfigProviderFactory implements CacheRemoteConfigProviderFactory, CacheRemoteConfigProvider, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final String PROVIDER_ID = "default";
|
||||
private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
// configuration
|
||||
private static final String PROPERTIES_FILE = "propertiesFile";
|
||||
private static final String CLIENT_INTELLIGENCE = "clientIntelligence";
|
||||
private static final String HOSTNAME = "hostname";
|
||||
private static final String PORT = "port";
|
||||
private static final String TLS_ENABLED = "tlsEnabled";
|
||||
private static final String TLS_SNI_HOSTNAME = "tlsSniHostname";
|
||||
private static final String USERNAME = "username";
|
||||
private static final String PASSWORD = "password";
|
||||
private static final String CONNECTION_POOL_MAX_ACTIVE = "connectionPoolMaxActive";
|
||||
private static final String CONNECTION_POOL_EXHAUSTED_ACTION = "connectionPoolExhaustedAction";
|
||||
private static final String AUTH_REALM = "authRealm";
|
||||
private static final String SASL_MECHANISM = "saslMechanism";
|
||||
|
||||
// configuration defaults
|
||||
private static final String CLIENT_INTELLIGENCE_DEFAULT = ClientIntelligence.getDefault().name();
|
||||
private static final int CONNECTION_POOL_MAX_ACTIVE_DEFAULT = 16;
|
||||
private static final String CONNECTION_POOL_EXHAUSTED_ACTION_DEFAULT = ExhaustedAction.CREATE_NEW.name();
|
||||
private static final String SASL_MECHANISM_DEFAULT = SCRAM_SHA_512;
|
||||
|
||||
private volatile Configuration remoteConfiguration;
|
||||
private volatile Config.Scope keycloakConfiguration;
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Config.Scope config) {
|
||||
return InfinispanUtils.isRemoteInfinispan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRemoteConfigProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.keycloakConfiguration = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
lazyInit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Configuration> configuration() {
|
||||
assert remoteConfiguration != null;
|
||||
return Optional.of(remoteConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigMetadata() {
|
||||
var builder = ProviderConfigurationBuilder.create();
|
||||
addHostNameAndPortConfig(builder);
|
||||
addClientIntelligenceConfig(builder);
|
||||
addPropertiesFileConfig(builder);
|
||||
addConnectionPoolConfig(builder);
|
||||
addTlsConfig(builder);
|
||||
addAuthenticationConfig(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link ConfigurationBuilder}.
|
||||
* <p>
|
||||
* This class is protected if power users need to extend this class for more advanced configuration. Using a
|
||||
* properties file is the recommended way to configure the client in more detail. Check
|
||||
* {@link ConfigurationProperties} for property keys.
|
||||
*
|
||||
* @return The {@link ConfigurationBuilder}. This instance can be modified.
|
||||
* @throws IOException if an error occurred when reading from the properties file (if configured).
|
||||
* @see ConfigurationProperties
|
||||
*/
|
||||
protected ConfigurationBuilder createConfigurationBuilder() throws IOException {
|
||||
logger.info("Starting Infinispan remote cache manager (Hot Rod Client)");
|
||||
|
||||
var builder = new ConfigurationBuilder();
|
||||
loadProperties(builder);
|
||||
builder.clientIntelligence(ClientIntelligence.valueOf(keycloakConfiguration.get(CLIENT_INTELLIGENCE, CLIENT_INTELLIGENCE_DEFAULT)));
|
||||
configureHostname(builder);
|
||||
configureConnectionPool(builder);
|
||||
configureTls(builder);
|
||||
configureAuthentication(builder);
|
||||
Marshalling.configure(builder);
|
||||
configureRemoteCaches(builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (remoteConfiguration != null) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (remoteConfiguration != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
remoteConfiguration = createConfigurationBuilder().build();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadProperties(ConfigurationBuilder builder) throws IOException {
|
||||
var path = keycloakConfiguration.get(PROPERTIES_FILE);
|
||||
if (path == null) {
|
||||
logger.debug("Hot Rod properties file not configured.");
|
||||
return;
|
||||
}
|
||||
var file = new File(path);
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("Hot Rod properties file not found: " + path);
|
||||
}
|
||||
try (var is = new FileInputStream(file)) {
|
||||
var properties = new Properties();
|
||||
properties.load(is);
|
||||
builder.withProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureHostname(ConfigurationBuilder builder) {
|
||||
var cacheRemoteHost = keycloakConfiguration.get(HOSTNAME);
|
||||
if (cacheRemoteHost == null) {
|
||||
logger.debug("Hot Rod hostname not configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
builder.addServer()
|
||||
.host(cacheRemoteHost)
|
||||
.port(keycloakConfiguration.getInt(PORT, ConfigurationProperties.DEFAULT_HOTROD_PORT));
|
||||
}
|
||||
|
||||
private void configureConnectionPool(ConfigurationBuilder builder) {
|
||||
builder.connectionPool()
|
||||
.maxActive(keycloakConfiguration.getInt(CONNECTION_POOL_MAX_ACTIVE, CONNECTION_POOL_MAX_ACTIVE_DEFAULT))
|
||||
.exhaustedAction(ExhaustedAction.valueOf(keycloakConfiguration.get(CONNECTION_POOL_EXHAUSTED_ACTION, CONNECTION_POOL_EXHAUSTED_ACTION_DEFAULT)));
|
||||
}
|
||||
|
||||
private void configureTls(ConfigurationBuilder builder) {
|
||||
if (!keycloakConfiguration.getBoolean(TLS_ENABLED, Boolean.FALSE)) {
|
||||
logger.debug("Hot Rod TLS not enabled.");
|
||||
return;
|
||||
}
|
||||
var sniHostName = keycloakConfiguration.get(TLS_SNI_HOSTNAME);
|
||||
if (sniHostName == null) {
|
||||
sniHostName = keycloakConfiguration.get(HOSTNAME);
|
||||
}
|
||||
builder.security().ssl()
|
||||
.enable()
|
||||
.sslContext(createSSLContext())
|
||||
.sniHostName(sniHostName);
|
||||
}
|
||||
|
||||
private void configureAuthentication(ConfigurationBuilder builder) {
|
||||
var username = keycloakConfiguration.get(USERNAME);
|
||||
var password = keycloakConfiguration.get(PASSWORD);
|
||||
if (username == null && password == null) {
|
||||
logger.debug("Hot Rod authentication not enabled.");
|
||||
return;
|
||||
}
|
||||
builder.security().authentication()
|
||||
.enable()
|
||||
.username(username)
|
||||
.password(password)
|
||||
.realm(keycloakConfiguration.get(AUTH_REALM, AuthenticationConfigurationBuilder.DEFAULT_REALM))
|
||||
.saslMechanism(keycloakConfiguration.get(SASL_MECHANISM, SASL_MECHANISM_DEFAULT));
|
||||
}
|
||||
|
||||
private static SSLContext createSSLContext() {
|
||||
try {
|
||||
// uses the default Java Runtime TrustStore, or the one generated by Keycloak (see org.keycloak.truststore.TruststoreBuilder)
|
||||
var sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, null, null);
|
||||
return sslContext;
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldCreateRemoteCaches() {
|
||||
// TODO convert to SPI option when we want to support this feature
|
||||
// http://github.com/keycloak/keycloak/issues/32129
|
||||
return Boolean.getBoolean("kc.cache-remote-create-caches");
|
||||
}
|
||||
|
||||
private static void configureRemoteCaches(ConfigurationBuilder builder) {
|
||||
if (!shouldCreateRemoteCaches()) {
|
||||
return;
|
||||
}
|
||||
// fall back for distributed caches if not defined
|
||||
logger.warn("Creating remote cache in external Infinispan server. It should not be used in production!");
|
||||
var baseConfig = defaultRemoteCacheBuilder();
|
||||
|
||||
skipSessionsCacheIfRequired(Arrays.stream(CLUSTERED_CACHE_NAMES))
|
||||
.forEach(name -> builder.remoteCache(name).configuration(baseConfig.toStringConfiguration(name)));
|
||||
}
|
||||
|
||||
private static org.infinispan.configuration.cache.Configuration defaultRemoteCacheBuilder() {
|
||||
var builder = new org.infinispan.configuration.cache.ConfigurationBuilder();
|
||||
builder.clustering().cacheMode(CacheMode.DIST_SYNC);
|
||||
builder.encoding().mediaType(MediaType.APPLICATION_PROTOSTREAM);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// configuration option below
|
||||
|
||||
private static void addHostNameAndPortConfig(ProviderConfigurationBuilder builder) {
|
||||
copyFromOption(builder, HOSTNAME, "hostname", ProviderConfigProperty.STRING_TYPE, CachingOptions.CACHE_REMOTE_HOST, false);
|
||||
copyFromOption(builder, PORT, "port", ProviderConfigProperty.INTEGER_TYPE, CachingOptions.CACHE_REMOTE_PORT, false);
|
||||
}
|
||||
|
||||
private static void addClientIntelligenceConfig(ProviderConfigurationBuilder builder) {
|
||||
builder.property()
|
||||
.name(CLIENT_INTELLIGENCE)
|
||||
.helpText("Specifies the level of intelligence the Hot Rod client should have.")
|
||||
.label("intelligence")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(CLIENT_INTELLIGENCE_DEFAULT)
|
||||
.options(Arrays.stream(ClientIntelligence.values()).map(Enum::name).toList())
|
||||
.add();
|
||||
}
|
||||
|
||||
private static void addPropertiesFileConfig(ProviderConfigurationBuilder builder) {
|
||||
builder.property()
|
||||
.name(PROPERTIES_FILE)
|
||||
.helpText("Path to the properties file with the Hot Rod client configuration.")
|
||||
.label("file")
|
||||
.type(ProviderConfigProperty.FILE_TYPE)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static void addConnectionPoolConfig(ProviderConfigurationBuilder builder) {
|
||||
builder.property()
|
||||
.name(CONNECTION_POOL_MAX_ACTIVE)
|
||||
.helpText("Sets the maximum number of connections per Infinispan server instance.")
|
||||
.label("maxActive")
|
||||
.type(ProviderConfigProperty.INTEGER_TYPE)
|
||||
.defaultValue(CONNECTION_POOL_MAX_ACTIVE_DEFAULT)
|
||||
.add();
|
||||
builder.property()
|
||||
.name(CONNECTION_POOL_EXHAUSTED_ACTION)
|
||||
.helpText("Specifies what happens when asking for a connection from a server's pool, and that pool is exhausted.")
|
||||
.label("action")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(CONNECTION_POOL_EXHAUSTED_ACTION_DEFAULT)
|
||||
.options(Arrays.stream(ExhaustedAction.values()).map(Enum::name).toList())
|
||||
.add();
|
||||
}
|
||||
|
||||
private static void addAuthenticationConfig(ProviderConfigurationBuilder builder) {
|
||||
copyFromOption(builder, USERNAME, "username", ProviderConfigProperty.STRING_TYPE, CachingOptions.CACHE_REMOTE_USERNAME, false);
|
||||
copyFromOption(builder, PASSWORD, "password", ProviderConfigProperty.STRING_TYPE, CachingOptions.CACHE_REMOTE_PASSWORD, true);
|
||||
builder.property()
|
||||
.name(AUTH_REALM)
|
||||
.helpText("Specifies the Infinispan server realm to be used for authentication.")
|
||||
.label("realm")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(AuthenticationConfigurationBuilder.DEFAULT_REALM)
|
||||
.add();
|
||||
builder.property()
|
||||
.name(SASL_MECHANISM)
|
||||
.helpText("Selects the SASL mechanism to use for the connection to the Infinispan server.")
|
||||
.label("mechanism")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.defaultValue(SASL_MECHANISM_DEFAULT)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static void addTlsConfig(ProviderConfigurationBuilder builder) {
|
||||
copyFromOption(builder, TLS_ENABLED, "enabled", ProviderConfigProperty.BOOLEAN_TYPE, CachingOptions.CACHE_REMOTE_TLS_ENABLED, false);
|
||||
builder.property()
|
||||
.name(TLS_SNI_HOSTNAME)
|
||||
.helpText("Specifies the TLS SNI hostname for the connection to the Infinispan server.")
|
||||
.label("hostname")
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.add();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2025 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.spi.infinispan.impl.remote;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.infinispan.client.hotrod.configuration.Configuration;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProvider;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderFactory;
|
||||
|
||||
/**
|
||||
* Implementation used when an external Infinispan cluster is not configured.
|
||||
*/
|
||||
public class DisabledCacheRemoteConfigProviderFactory implements CacheRemoteConfigProviderFactory, CacheRemoteConfigProvider, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final String PROVIDER_ID = "disabled";
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Config.Scope config) {
|
||||
return !InfinispanUtils.isRemoteInfinispan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRemoteConfigProvider create(KeycloakSession session) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
//no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Configuration> configuration() {
|
||||
// no configuration since it is disabled
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,4 +15,5 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.connections.infinispan.InfinispanConnectionSpi
|
||||
org.keycloak.connections.infinispan.InfinispanConnectionSpi
|
||||
org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#
|
||||
# Copyright 2025 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.spi.infinispan.impl.remote.DisabledCacheRemoteConfigProviderFactory
|
||||
org.keycloak.spi.infinispan.impl.remote.DefaultCacheRemoteConfigProviderFactory
|
||||
|
|
@ -92,23 +92,28 @@ final class CachingPropertyMappers {
|
|||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_HOST)
|
||||
.paramLabel("hostname")
|
||||
.to("kc.spi-cache-remote-default-hostname")
|
||||
.addValidateEnabled(CachingPropertyMappers::isRemoteCacheHostEnabled, MULTI_SITE_OR_EMBEDDED_REMOTE_FEATURE_SET)
|
||||
.isRequired(InfinispanUtils::isRemoteInfinispan, MULTI_SITE_FEATURE_SET)
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_PORT)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-port")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_TLS_ENABLED)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-tls-enabled")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_USERNAME)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-username")
|
||||
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_USERNAME, CachingOptions.CACHE_REMOTE_PASSWORD))
|
||||
.paramLabel("username")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_PASSWORD)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-password")
|
||||
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_PASSWORD, CachingOptions.CACHE_REMOTE_USERNAME))
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import java.security.KeyManagementException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
|
@ -32,14 +31,7 @@ import java.util.function.Supplier;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.client.hotrod.RemoteCacheManager;
|
||||
import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
|
||||
import org.infinispan.client.hotrod.impl.ConfigurationProperties;
|
||||
import org.infinispan.commons.dataconversion.MediaType;
|
||||
import org.infinispan.commons.internal.InternalCacheNames;
|
||||
import org.infinispan.commons.util.concurrent.CompletableFutures;
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.cache.HashConfiguration;
|
||||
import org.infinispan.configuration.global.ShutdownHookBehavior;
|
||||
|
|
@ -49,8 +41,6 @@ import org.infinispan.manager.DefaultCacheManager;
|
|||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.infinispan.metrics.config.MicrometerMeterRegisterConfigurationBuilder;
|
||||
import org.infinispan.persistence.remote.configuration.RemoteStoreConfigurationBuilder;
|
||||
import org.infinispan.protostream.descriptors.FileDescriptor;
|
||||
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.MultiSiteUtils;
|
||||
|
|
@ -60,14 +50,8 @@ import org.keycloak.config.Option;
|
|||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.marshalling.KeycloakIndexSchemaUtil;
|
||||
import org.keycloak.marshalling.KeycloakModelSchema;
|
||||
import org.keycloak.marshalling.Marshalling;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.sessions.infinispan.query.ClientSessionQueries;
|
||||
import org.keycloak.models.sessions.infinispan.query.UserSessionQueries;
|
||||
import org.keycloak.models.sessions.infinispan.remote.RemoteInfinispanAuthenticationSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.remote.RemoteUserLoginFailureProviderFactory;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.storage.infinispan.jgroups.JGroupsConfigurator;
|
||||
|
||||
|
|
@ -77,18 +61,15 @@ import static org.keycloak.config.CachingOptions.CACHE_REMOTE_HOST_PROPERTY;
|
|||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PASSWORD_PROPERTY;
|
||||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_PORT_PROPERTY;
|
||||
import static org.keycloak.config.CachingOptions.CACHE_REMOTE_USERNAME_PROPERTY;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_CACHE_NAMES;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_AND_CLIENT_SESSION_CACHES;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.WORK_CACHE_NAME;
|
||||
import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.skipSessionsCacheIfRequired;
|
||||
import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRAM_SHA_512;
|
||||
|
||||
public class CacheManagerFactory {
|
||||
|
|
@ -101,7 +82,6 @@ public class CacheManagerFactory {
|
|||
private static final Supplier<ConfigurationBuilder> TO_NULL = () -> null;
|
||||
|
||||
private volatile CompletableFuture<EmbeddedCacheManager> cacheManagerFuture;
|
||||
private final CompletableFuture<RemoteCacheManager> remoteCacheManagerFuture;
|
||||
private final JGroupsConfigurator jGroupsConfigurator;
|
||||
|
||||
public CacheManagerFactory(String config) {
|
||||
|
|
@ -113,14 +93,6 @@ public class CacheManagerFactory {
|
|||
} else {
|
||||
cacheManagerFuture = CompletableFuture.supplyAsync(() -> startEmbeddedCacheManager(null));
|
||||
}
|
||||
|
||||
if (InfinispanUtils.isRemoteInfinispan()) {
|
||||
logger.debug("Remote Cache feature is enabled");
|
||||
this.remoteCacheManagerFuture = CompletableFuture.supplyAsync(this::startRemoteCacheManager);
|
||||
} else {
|
||||
logger.debug("Remote Cache feature is disabled");
|
||||
this.remoteCacheManagerFuture = CompletableFutures.completedNull();
|
||||
}
|
||||
}
|
||||
|
||||
public EmbeddedCacheManager getOrCreateEmbeddedCacheManager(KeycloakSession keycloakSession) {
|
||||
|
|
@ -137,10 +109,6 @@ public class CacheManagerFactory {
|
|||
return join(cacheManagerFuture);
|
||||
}
|
||||
|
||||
public RemoteCacheManager getOrCreateRemoteCacheManager() {
|
||||
return join(remoteCacheManagerFuture);
|
||||
}
|
||||
|
||||
private static <T> T join(Future<T> future) {
|
||||
try {
|
||||
return future.get(getStartTimeout(), TimeUnit.SECONDS);
|
||||
|
|
@ -152,142 +120,6 @@ public class CacheManagerFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private RemoteCacheManager startRemoteCacheManager() {
|
||||
logger.info("Starting Infinispan remote cache manager (Hot Rod Client)");
|
||||
String cacheRemoteHost = requiredStringProperty(CACHE_REMOTE_HOST_PROPERTY);
|
||||
Integer cacheRemotePort = Configuration.getOptionalKcValue(CACHE_REMOTE_PORT_PROPERTY)
|
||||
.map(Integer::parseInt)
|
||||
.orElse(ConfigurationProperties.DEFAULT_HOTROD_PORT);
|
||||
|
||||
org.infinispan.client.hotrod.configuration.ConfigurationBuilder builder = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder();
|
||||
builder.addServer().host(cacheRemoteHost).port(cacheRemotePort);
|
||||
builder.connectionPool().maxActive(16).exhaustedAction(org.infinispan.client.hotrod.configuration.ExhaustedAction.CREATE_NEW);
|
||||
|
||||
if (isRemoteTLSEnabled()) {
|
||||
builder.security().ssl()
|
||||
.enable()
|
||||
.sslContext(createSSLContext())
|
||||
.sniHostName(cacheRemoteHost);
|
||||
}
|
||||
|
||||
if (isRemoteAuthenticationEnabled()) {
|
||||
builder.security().authentication()
|
||||
.enable()
|
||||
.username(requiredStringProperty(CACHE_REMOTE_USERNAME_PROPERTY))
|
||||
.password(requiredStringProperty(CACHE_REMOTE_PASSWORD_PROPERTY))
|
||||
.realm("default")
|
||||
.saslMechanism(SCRAM_SHA_512);
|
||||
}
|
||||
|
||||
Marshalling.configure(builder);
|
||||
|
||||
if (shouldCreateRemoteCaches()) {
|
||||
createRemoteCaches(builder);
|
||||
}
|
||||
|
||||
var remoteCacheManager = new RemoteCacheManager(builder.build());
|
||||
|
||||
// update the schema before trying to access the caches
|
||||
updateProtoSchema(remoteCacheManager);
|
||||
|
||||
// establish connection to all caches
|
||||
if (isStartEagerly()) {
|
||||
skipSessionsCacheIfRequired(Arrays.stream(CLUSTERED_CACHE_NAMES)).forEach(remoteCacheManager::getCache);
|
||||
}
|
||||
return remoteCacheManager;
|
||||
}
|
||||
|
||||
private static void createRemoteCaches(org.infinispan.client.hotrod.configuration.ConfigurationBuilder builder) {
|
||||
// fall back for distributed caches if not defined
|
||||
logger.warn("Creating remote cache in external Infinispan server. It should not be used in production!");
|
||||
var baseConfig = defaultRemoteCacheBuilder().build();
|
||||
|
||||
skipSessionsCacheIfRequired(Arrays.stream(CLUSTERED_CACHE_NAMES))
|
||||
.forEach(name -> builder.remoteCache(name).configuration(baseConfig.toStringConfiguration(name)));
|
||||
}
|
||||
|
||||
private static ConfigurationBuilder defaultRemoteCacheBuilder() {
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.clustering().cacheMode(CacheMode.DIST_SYNC);
|
||||
builder.encoding().mediaType(MediaType.APPLICATION_PROTOSTREAM);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private void updateProtoSchema(RemoteCacheManager remoteCacheManager) {
|
||||
var key = KeycloakModelSchema.INSTANCE.getProtoFileName();
|
||||
var current = KeycloakModelSchema.INSTANCE.getProtoFile();
|
||||
|
||||
RemoteCache<String, String> protostreamMetadataCache = remoteCacheManager.getCache(InternalCacheNames.PROTOBUF_METADATA_CACHE_NAME);
|
||||
var stored = protostreamMetadataCache.getWithMetadata(key);
|
||||
if (stored == null) {
|
||||
if (protostreamMetadataCache.putIfAbsent(key, current) == null) {
|
||||
logger.info("Infinispan ProtoStream schema uploaded for the first time.");
|
||||
} else {
|
||||
logger.info("Failed to update Infinispan ProtoStream schema. Assumed it was updated by other Keycloak server.");
|
||||
}
|
||||
checkForProtoSchemaErrors(protostreamMetadataCache);
|
||||
return;
|
||||
}
|
||||
if (Objects.equals(stored.getValue(), current)) {
|
||||
logger.info("Infinispan ProtoStream schema is up to date!");
|
||||
return;
|
||||
}
|
||||
if (protostreamMetadataCache.replaceWithVersion(key, current, stored.getVersion())) {
|
||||
logger.info("Infinispan ProtoStream schema successful updated.");
|
||||
reindexCaches(remoteCacheManager, stored.getValue(), current);
|
||||
} else {
|
||||
logger.info("Failed to update Infinispan ProtoStream schema. Assumed it was updated by other Keycloak server.");
|
||||
}
|
||||
checkForProtoSchemaErrors(protostreamMetadataCache);
|
||||
}
|
||||
|
||||
private void checkForProtoSchemaErrors(RemoteCache<String, String> protostreamMetadataCache) {
|
||||
String errors = protostreamMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX);
|
||||
if (errors != null) {
|
||||
for (String errorFile : errors.split("\n")) {
|
||||
logger.errorf("%nThere was an error in proto file: %s%nError message: %s%nCurrent proto schema: %s%n",
|
||||
errorFile,
|
||||
protostreamMetadataCache.get(errorFile + ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX),
|
||||
protostreamMetadataCache.get(errorFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void reindexCaches(RemoteCacheManager remoteCacheManager, String oldSchema, String newSchema) {
|
||||
var oldPS = KeycloakModelSchema.parseProtoSchema(oldSchema);
|
||||
var newPS = KeycloakModelSchema.parseProtoSchema(newSchema);
|
||||
var admin = remoteCacheManager.administration();
|
||||
|
||||
if (isEntityChanged(oldPS, newPS, RemoteUserLoginFailureProviderFactory.PROTO_ENTITY)) {
|
||||
updateSchemaAndReIndexCache(admin, LOGIN_FAILURE_CACHE_NAME);
|
||||
}
|
||||
|
||||
if (isEntityChanged(oldPS, newPS, RemoteInfinispanAuthenticationSessionProviderFactory.PROTO_ENTITY)) {
|
||||
updateSchemaAndReIndexCache(admin, AUTHENTICATION_SESSIONS_CACHE_NAME);
|
||||
}
|
||||
|
||||
if (isEntityChanged(oldPS, newPS, ClientSessionQueries.CLIENT_SESSION)) {
|
||||
updateSchemaAndReIndexCache(admin, CLIENT_SESSION_CACHE_NAME);
|
||||
updateSchemaAndReIndexCache(admin, OFFLINE_CLIENT_SESSION_CACHE_NAME);
|
||||
}
|
||||
|
||||
if (isEntityChanged(oldPS, newPS, UserSessionQueries.USER_SESSION)) {
|
||||
updateSchemaAndReIndexCache(admin, USER_SESSION_CACHE_NAME);
|
||||
updateSchemaAndReIndexCache(admin, OFFLINE_USER_SESSION_CACHE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEntityChanged(FileDescriptor oldSchema, FileDescriptor newSchema, String entity) {
|
||||
var v1 = KeycloakModelSchema.findEntity(oldSchema, entity);
|
||||
var v2 = KeycloakModelSchema.findEntity(newSchema, entity);
|
||||
return v1.isPresent() && v2.isPresent() && KeycloakIndexSchemaUtil.isIndexSchemaChanged(v1.get(), v2.get());
|
||||
}
|
||||
|
||||
private static void updateSchemaAndReIndexCache(RemoteCacheManagerAdmin admin, String cacheName) {
|
||||
admin.updateIndexSchema(cacheName);
|
||||
admin.reindexCache(cacheName);
|
||||
}
|
||||
|
||||
private EmbeddedCacheManager startEmbeddedCacheManager(KeycloakSession session) {
|
||||
logger.info("Starting Infinispan embedded cache manager");
|
||||
var builder = jGroupsConfigurator.holder();
|
||||
|
|
@ -359,10 +191,6 @@ public class CacheManagerFactory {
|
|||
Configuration.getOptionalKcValue(CACHE_REMOTE_PASSWORD_PROPERTY).isPresent();
|
||||
}
|
||||
|
||||
private static boolean shouldCreateRemoteCaches() {
|
||||
return Boolean.getBoolean("kc.cache-remote-create-caches");
|
||||
}
|
||||
|
||||
private static SSLContext createSSLContext() {
|
||||
try {
|
||||
// uses the default Java Runtime TrustStore, or the one generated by Keycloak (see org.keycloak.truststore.TruststoreBuilder)
|
||||
|
|
|
|||
|
|
@ -32,9 +32,4 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro
|
|||
public <C> C getEmbeddedCacheManager(KeycloakSession keycloakSession, Config.Scope config) {
|
||||
return (C) Arc.container().instance(CacheManagerFactory.class).get().getOrCreateEmbeddedCacheManager(keycloakSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C> C getRemoteCacheManager(Config.Scope config) {
|
||||
return (C) Arc.container().instance(CacheManagerFactory.class).get().getOrCreateRemoteCacheManager();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ public interface ManagedCacheManagerProvider {
|
|||
|
||||
/**
|
||||
* @return A RemoteCacheManager if the features {@link org.keycloak.common.Profile.Feature#CLUSTERLESS} or {@link org.keycloak.common.Profile.Feature#MULTI_SITE} is enabled, {@code null} otherwise.
|
||||
* @deprecated The RemoteCacheManager is created and managed by keycloak. Use InfinispanConnectionProvider to retrieve it and implement CacheRemoteConfigProvider to overwrite the configuration.
|
||||
*/
|
||||
<C> C getRemoteCacheManager(Config.Scope config);
|
||||
@Deprecated(since = "26.3", forRemoval = true)
|
||||
default <C> C getRemoteCacheManager(Config.Scope config) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,6 +189,13 @@
|
|||
}
|
||||
},
|
||||
|
||||
"cacheRemote": {
|
||||
"default": {
|
||||
"hostname": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
|
||||
"port": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
|
||||
}
|
||||
},
|
||||
|
||||
"truststore": {
|
||||
"file": {
|
||||
"file": "${keycloak.truststore.file:target/dependency/keystore/keycloak.truststore}",
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ import org.keycloak.provider.Spi;
|
|||
import org.keycloak.services.DefaultComponentFactoryProviderFactory;
|
||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||
import org.keycloak.services.resteasy.ResteasyKeycloakSessionFactory;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderFactory;
|
||||
import org.keycloak.spi.infinispan.CacheRemoteConfigProviderSpi;
|
||||
import org.keycloak.storage.DatastoreProviderFactory;
|
||||
import org.keycloak.storage.DatastoreSpi;
|
||||
import org.keycloak.timer.TimerSpi;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import java.lang.management.LockInfo;
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
|
@ -175,9 +176,9 @@ public abstract class KeycloakModelTest {
|
|||
@Override
|
||||
public Statement apply(Statement base, Description description) {
|
||||
Stream<RequireProvider> st = Optional.ofNullable(description.getAnnotation(RequireProviders.class))
|
||||
.map(RequireProviders::value)
|
||||
.map(Stream::of)
|
||||
.orElseGet(Stream::empty);
|
||||
.map(RequireProviders::value)
|
||||
.stream()
|
||||
.flatMap(Stream::of);
|
||||
|
||||
RequireProvider rp = description.getAnnotation(RequireProvider.class);
|
||||
if (rp != null) {
|
||||
|
|
@ -232,37 +233,37 @@ public abstract class KeycloakModelTest {
|
|||
}
|
||||
};
|
||||
|
||||
private static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(AuthorizationSpi.class)
|
||||
.add(PolicySpi.class)
|
||||
.add(ClientScopeSpi.class)
|
||||
.add(ClientSpi.class)
|
||||
.add(ComponentFactorySpi.class)
|
||||
.add(ClusterSpi.class)
|
||||
.add(EventStoreSpi.class)
|
||||
.add(ExecutorsSpi.class)
|
||||
.add(GroupSpi.class)
|
||||
.add(RealmSpi.class)
|
||||
.add(RoleSpi.class)
|
||||
.add(DeploymentStateSpi.class)
|
||||
.add(StoreFactorySpi.class)
|
||||
.add(TimerSpi.class)
|
||||
.add(TracingSpi.class)
|
||||
.add(UserLoginFailureSpi.class)
|
||||
.add(UserSessionSpi.class)
|
||||
.add(UserSpi.class)
|
||||
.add(DatastoreSpi.class)
|
||||
.build();
|
||||
private static final Set<Class<? extends Spi>> ALLOWED_SPIS = Set.of(
|
||||
AuthorizationSpi.class,
|
||||
PolicySpi.class,
|
||||
ClientScopeSpi.class,
|
||||
ClientSpi.class,
|
||||
ComponentFactorySpi.class,
|
||||
ClusterSpi.class,
|
||||
EventStoreSpi.class,
|
||||
ExecutorsSpi.class,
|
||||
GroupSpi.class,
|
||||
RealmSpi.class,
|
||||
RoleSpi.class,
|
||||
DeploymentStateSpi.class,
|
||||
StoreFactorySpi.class,
|
||||
TimerSpi.class,
|
||||
TracingSpi.class,
|
||||
UserLoginFailureSpi.class,
|
||||
UserSessionSpi.class,
|
||||
UserSpi.class,
|
||||
DatastoreSpi.class,
|
||||
CacheRemoteConfigProviderSpi.class);
|
||||
|
||||
private static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
|
||||
.add(ComponentFactoryProviderFactory.class)
|
||||
.add(DefaultAuthorizationProviderFactory.class)
|
||||
.add(PolicyProviderFactory.class)
|
||||
.add(DefaultExecutorsProviderFactory.class)
|
||||
.add(DeploymentStateProviderFactory.class)
|
||||
.add(DatastoreProviderFactory.class)
|
||||
.add(TracingProviderFactory.class)
|
||||
.build();
|
||||
private static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = Set.of(
|
||||
ComponentFactoryProviderFactory.class,
|
||||
DefaultAuthorizationProviderFactory.class,
|
||||
PolicyProviderFactory.class,
|
||||
DefaultExecutorsProviderFactory.class,
|
||||
DeploymentStateProviderFactory.class,
|
||||
DatastoreProviderFactory.class,
|
||||
TracingProviderFactory.class,
|
||||
CacheRemoteConfigProviderFactory.class);
|
||||
|
||||
protected static final List<KeycloakModelParameters> MODEL_PARAMETERS;
|
||||
protected static final Config CONFIG = new Config(KeycloakModelTest::useDefaultFactory);
|
||||
|
|
|
|||
Loading…
Reference in a new issue