Ability to set the default provider for an SPI (#28135)
Closes #28134 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
cae92cbe8c
commit
3f9cebca39
9 changed files with 256 additions and 54 deletions
|
@ -43,6 +43,15 @@ public class Config {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getDefaultProvider(String spi) {
|
||||
String provider = configProvider.getDefaultProvider(spi);
|
||||
if (provider == null || provider.trim().equals("")) {
|
||||
return null;
|
||||
} else {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
public static Scope scope(String... scope) {
|
||||
return configProvider.scope(scope);
|
||||
}
|
||||
|
@ -51,6 +60,8 @@ public class Config {
|
|||
|
||||
String getProvider(String spi);
|
||||
|
||||
String getDefaultProvider(String spi);
|
||||
|
||||
Scope scope(String... scope);
|
||||
|
||||
}
|
||||
|
@ -62,6 +73,11 @@ public class Config {
|
|||
return System.getProperties().getProperty("keycloak." + spi + ".provider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultProvider(String spi) {
|
||||
return System.getProperties().getProperty("keycloak." + spi + ".provider.default");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope scope(String... scope) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
|
|
@ -45,17 +45,33 @@ Provider configuration options are provided when starting the server. See all su
|
|||
.Setting the `connection-pool-size` for the `default` provider of the `connections-http-client` SPI
|
||||
<@kc.start parameters="--spi-connections-http-client-default-connection-pool-size=10"/>
|
||||
|
||||
== Configuring a default provider
|
||||
== Configuring a single provider for an SPI
|
||||
|
||||
Depending on the SPI, multiple provider implementations can co-exist but only one of them is going to be used at runtime.
|
||||
For these SPIs, a default provider is the primary implementation that is going to be active and used at runtime.
|
||||
For these SPIs, a specific provider is the primary implementation that is going to be active and used at runtime.
|
||||
|
||||
To configure a provider as the default you should run the `build` command as follows:
|
||||
To configure a provider as the single provider you should run the `build` command as follows:
|
||||
|
||||
.Marking the `mycustomprovider` provider as the default provider for the `email-template` SPI
|
||||
.Marking the `mycustomprovider` provider as the single provider for the `email-template` SPI
|
||||
<@kc.build parameters="--spi-email-template-provider=mycustomprovider"/>
|
||||
|
||||
In the example above, we are using the `provider` property to set the id of the provider we want to mark as the default.
|
||||
== Configuring a default provider for an SPI
|
||||
|
||||
Depending on the SPI, multiple provider implementations can co-exist and one is used by default.
|
||||
For these SPIs, a specific provider is the default implementation that is going to selected unless a specific provider
|
||||
is requested.
|
||||
|
||||
The following logic is used to determine the default provider:
|
||||
|
||||
1. The explicitly configured default provider
|
||||
2. The provider with the highest order (providers with order <= 0 are ignored)
|
||||
3. The provider with the id set to `default`
|
||||
|
||||
To configure a provider as the default provider you should run the `build` command as follows:
|
||||
|
||||
.Marking the `mycustomhash` provider as the default provider for the `password-hashing` SPI
|
||||
<@kc.build parameters="--spi-password-hashing-provider-default=mycustomprovider"/>
|
||||
|
||||
|
||||
== Enabling and disabling a provider
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ import org.keycloak.quarkus.runtime.themes.FlatClasspathThemeResourceProviderFac
|
|||
import org.keycloak.representations.provider.ScriptProviderDescriptor;
|
||||
import org.keycloak.representations.provider.ScriptProviderMetadata;
|
||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.resources.JsResource;
|
||||
import org.keycloak.services.resources.LoadBalancerResource;
|
||||
|
@ -877,38 +878,19 @@ class KeycloakProcessor {
|
|||
private void checkProviders(Spi spi,
|
||||
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap,
|
||||
Map<Class<? extends Provider>, String> defaultProviders) {
|
||||
String defaultProvider = Config.getProvider(spi.getName());
|
||||
|
||||
if (defaultProvider != null) {
|
||||
String provider = Config.getProvider(spi.getName());
|
||||
if (provider != null) {
|
||||
Map<String, ProviderFactory> map = factoriesMap.get(spi.getProviderClass());
|
||||
if (map == null || map.get(defaultProvider) == null) {
|
||||
throw new RuntimeException("Failed to find provider " + defaultProvider + " for " + spi.getName());
|
||||
if (map == null || map.get(provider) == null) {
|
||||
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
|
||||
}
|
||||
defaultProviders.put(spi.getProviderClass(), provider);
|
||||
} else {
|
||||
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
||||
if (factories != null && factories.size() == 1) {
|
||||
defaultProvider = factories.values().iterator().next().getId();
|
||||
String defaultProvider = DefaultKeycloakSessionFactory.resolveDefaultProvider(factories, spi);
|
||||
if (defaultProvider != null) {
|
||||
defaultProviders.put(spi.getProviderClass(), defaultProvider);
|
||||
}
|
||||
|
||||
if (factories != null) {
|
||||
if (defaultProvider == null) {
|
||||
Optional<ProviderFactory> highestPriority = factories.values().stream()
|
||||
.max(Comparator.comparing(ProviderFactory::order));
|
||||
if (highestPriority.isPresent() && highestPriority.get().order() > 0) {
|
||||
defaultProvider = highestPriority.get().getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProvider == null && (factories == null || factories.containsKey("default"))) {
|
||||
defaultProvider = "default";
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProvider != null) {
|
||||
defaultProviders.put(spi.getProviderClass(), defaultProvider);
|
||||
} else {
|
||||
logger.debugv("No default provider for {0}", spi.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,11 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
|
|||
return scope(spi).get("provider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultProvider(String spi) {
|
||||
return scope(spi).get("provider.default");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... scope) {
|
||||
return new MicroProfileScope(scope);
|
||||
|
|
|
@ -227,35 +227,49 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
|
|||
provider.clear();
|
||||
|
||||
for (Spi spi : spis) {
|
||||
String defaultProvider = Config.getProvider(spi.getName());
|
||||
if (defaultProvider != null) {
|
||||
if (getProviderFactory(spi.getProviderClass(), defaultProvider) == null) {
|
||||
throw new RuntimeException("Failed to find provider " + defaultProvider + " for " + spi.getName());
|
||||
String provider = Config.getProvider(spi.getName());
|
||||
if (provider != null) {
|
||||
if (getProviderFactory(spi.getProviderClass(), provider) == null) {
|
||||
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
|
||||
}
|
||||
this.provider.put(spi.getProviderClass(), provider);
|
||||
} else {
|
||||
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
||||
if (factories != null && factories.size() == 1) {
|
||||
defaultProvider = factories.values().iterator().next().getId();
|
||||
}
|
||||
|
||||
if (defaultProvider == null) {
|
||||
Optional<ProviderFactory> highestPriority = factories.values().stream().max(Comparator.comparing(ProviderFactory::order));
|
||||
if (highestPriority.isPresent() && highestPriority.get().order() > 0) {
|
||||
defaultProvider = highestPriority.get().getId();
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProvider == null && factories.containsKey("default")) {
|
||||
defaultProvider = "default";
|
||||
String defaultProvider = resolveDefaultProvider(factories, spi);
|
||||
if (defaultProvider != null) {
|
||||
this.provider.put(spi.getProviderClass(), defaultProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProvider != null) {
|
||||
this.provider.put(spi.getProviderClass(), defaultProvider);
|
||||
logger.debugv("Set default provider for {0} to {1}", spi.getName(), defaultProvider);
|
||||
} else {
|
||||
logger.debugv("No default provider for {0}", spi.getName());
|
||||
public static String resolveDefaultProvider(Map<String, ProviderFactory> factories, Spi spi) {
|
||||
if (factories == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String defaultProvider = Config.getDefaultProvider(spi.getName());
|
||||
if (defaultProvider != null) {
|
||||
if (!factories.containsKey(defaultProvider)) {
|
||||
throw new RuntimeException("Failed to find provider " + defaultProvider + " for " + spi.getName());
|
||||
}
|
||||
} else if (factories.size() == 1) {
|
||||
defaultProvider = factories.values().iterator().next().getId();
|
||||
} else {
|
||||
Optional<ProviderFactory> highestPriority = factories.values().stream().filter(p -> p.order() > 0).max(Comparator.comparing(ProviderFactory::order));
|
||||
if (highestPriority.isPresent()) {
|
||||
defaultProvider = highestPriority.get().getId();
|
||||
} else if (factories.containsKey("default")) {
|
||||
defaultProvider = "default";
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProvider != null) {
|
||||
logger.debugv("Set default provider for {0} to {1}", spi.getName(), defaultProvider);
|
||||
return defaultProvider;
|
||||
} else {
|
||||
logger.debugv("No default provider for {0}", spi.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ public class JsonConfigProvider implements Config.ConfigProvider {
|
|||
return n != null ? replaceProperties(n.textValue()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultProvider(String spi) {
|
||||
JsonNode n = getNode(config, spi, "provider-default");
|
||||
return n != null ? replaceProperties(n.textValue()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... path) {
|
||||
return new JsonScope(getNode(config, path));
|
||||
|
|
|
@ -146,6 +146,11 @@ public class SoapTest {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultProvider(String spi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... scope) {
|
||||
if (scope.length == 2 && "connectionsHttpClient".equals(scope[0]) && "default".equals(scope[1])) {
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package org.keycloak.services;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultKeycloakSessionFactoryTest {
|
||||
|
||||
private DummyConfigurationProvider config;
|
||||
private DummySpi spi;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
config = new DummyConfigurationProvider();
|
||||
Config.init(config);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
Config.init(new Config.SystemPropertiesConfigProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultProviderFromConfigTest() {
|
||||
Map<String, ProviderFactory> map = new HashMap<>(Map.of(
|
||||
"two", new DummyProviderFactory("two", 2),
|
||||
"one", new DummyProviderFactory("one", 0),
|
||||
"three", new DummyProviderFactory("three", 3)));
|
||||
spi = new DummySpi();
|
||||
|
||||
// Default provider configured
|
||||
config.defaultProvider = "one";
|
||||
Assert.assertEquals("one", DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi));
|
||||
|
||||
// Highest priority selected
|
||||
config.defaultProvider = null;
|
||||
Assert.assertEquals("three", DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi));
|
||||
|
||||
// No default, with order=0
|
||||
map.values().stream().forEach(p -> ((DummyProviderFactory) p).order = 0);
|
||||
Assert.assertNull(DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi));
|
||||
|
||||
// Provider with id=default selected
|
||||
map.put("default", new DummyProviderFactory("default", 0));
|
||||
Assert.assertEquals("default", DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi));
|
||||
|
||||
// Default set if single provider exists
|
||||
map.remove("default");
|
||||
map.remove("two");
|
||||
map.remove("three");
|
||||
Assert.assertEquals("one", DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi));
|
||||
|
||||
// Throw error if default configured not found
|
||||
config.defaultProvider = "nosuch";
|
||||
try {
|
||||
DefaultKeycloakSessionFactory.resolveDefaultProvider(map, spi);
|
||||
Assert.fail("Expected exception");
|
||||
} catch (RuntimeException e) {
|
||||
Assert.assertEquals("Failed to find provider nosuch for dummy", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private class DummyConfigurationProvider implements Config.ConfigProvider {
|
||||
|
||||
String defaultProvider;
|
||||
|
||||
@Override
|
||||
public String getProvider(String spi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultProvider(String spi) {
|
||||
return defaultProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... scope) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class DummyProviderFactory implements ProviderFactory {
|
||||
|
||||
private String id;
|
||||
private int order;
|
||||
|
||||
public DummyProviderFactory(String id, int order) {
|
||||
this.id = id;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider create(KeycloakSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
private class DummySpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dummy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -148,6 +148,10 @@ public class Config implements ConfigProvider {
|
|||
return getConfig().get(spiName + ".provider");
|
||||
}
|
||||
|
||||
public String getDefaultProvider(String spiName) {
|
||||
return getConfig().get(spiName + ".provider.default");
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return useGlobalConfigurationFunc.getAsBoolean() ? defaultProperties : properties.get();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue