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) {
|
public static Scope scope(String... scope) {
|
||||||
return configProvider.scope(scope);
|
return configProvider.scope(scope);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +60,8 @@ public class Config {
|
||||||
|
|
||||||
String getProvider(String spi);
|
String getProvider(String spi);
|
||||||
|
|
||||||
|
String getDefaultProvider(String spi);
|
||||||
|
|
||||||
Scope scope(String... scope);
|
Scope scope(String... scope);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -62,6 +73,11 @@ public class Config {
|
||||||
return System.getProperties().getProperty("keycloak." + spi + ".provider");
|
return System.getProperties().getProperty("keycloak." + spi + ".provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultProvider(String spi) {
|
||||||
|
return System.getProperties().getProperty("keycloak." + spi + ".provider.default");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Scope scope(String... scope) {
|
public Scope scope(String... scope) {
|
||||||
StringBuilder sb = new StringBuilder();
|
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
|
.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"/>
|
<@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.
|
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"/>
|
<@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
|
== 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.ScriptProviderDescriptor;
|
||||||
import org.keycloak.representations.provider.ScriptProviderMetadata;
|
import org.keycloak.representations.provider.ScriptProviderMetadata;
|
||||||
import org.keycloak.representations.userprofile.config.UPConfig;
|
import org.keycloak.representations.userprofile.config.UPConfig;
|
||||||
|
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.resources.JsResource;
|
import org.keycloak.services.resources.JsResource;
|
||||||
import org.keycloak.services.resources.LoadBalancerResource;
|
import org.keycloak.services.resources.LoadBalancerResource;
|
||||||
|
@ -877,38 +878,19 @@ class KeycloakProcessor {
|
||||||
private void checkProviders(Spi spi,
|
private void checkProviders(Spi spi,
|
||||||
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap,
|
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap,
|
||||||
Map<Class<? extends Provider>, String> defaultProviders) {
|
Map<Class<? extends Provider>, String> defaultProviders) {
|
||||||
String defaultProvider = Config.getProvider(spi.getName());
|
String provider = Config.getProvider(spi.getName());
|
||||||
|
if (provider != null) {
|
||||||
if (defaultProvider != null) {
|
|
||||||
Map<String, ProviderFactory> map = factoriesMap.get(spi.getProviderClass());
|
Map<String, ProviderFactory> map = factoriesMap.get(spi.getProviderClass());
|
||||||
if (map == null || map.get(defaultProvider) == null) {
|
if (map == null || map.get(provider) == null) {
|
||||||
throw new RuntimeException("Failed to find provider " + defaultProvider + " for " + spi.getName());
|
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
|
||||||
}
|
}
|
||||||
|
defaultProviders.put(spi.getProviderClass(), provider);
|
||||||
} else {
|
} else {
|
||||||
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
||||||
if (factories != null && factories.size() == 1) {
|
String defaultProvider = DefaultKeycloakSessionFactory.resolveDefaultProvider(factories, spi);
|
||||||
defaultProvider = factories.values().iterator().next().getId();
|
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");
|
return scope(spi).get("provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultProvider(String spi) {
|
||||||
|
return scope(spi).get("provider.default");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Config.Scope scope(String... scope) {
|
public Config.Scope scope(String... scope) {
|
||||||
return new MicroProfileScope(scope);
|
return new MicroProfileScope(scope);
|
||||||
|
|
|
@ -227,35 +227,49 @@ public abstract class DefaultKeycloakSessionFactory implements KeycloakSessionFa
|
||||||
provider.clear();
|
provider.clear();
|
||||||
|
|
||||||
for (Spi spi : spis) {
|
for (Spi spi : spis) {
|
||||||
String defaultProvider = Config.getProvider(spi.getName());
|
String provider = Config.getProvider(spi.getName());
|
||||||
if (defaultProvider != null) {
|
if (provider != null) {
|
||||||
if (getProviderFactory(spi.getProviderClass(), defaultProvider) == null) {
|
if (getProviderFactory(spi.getProviderClass(), provider) == null) {
|
||||||
throw new RuntimeException("Failed to find provider " + defaultProvider + " for " + spi.getName());
|
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
|
||||||
}
|
}
|
||||||
|
this.provider.put(spi.getProviderClass(), provider);
|
||||||
} else {
|
} else {
|
||||||
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
|
||||||
if (factories != null && factories.size() == 1) {
|
String defaultProvider = resolveDefaultProvider(factories, spi);
|
||||||
defaultProvider = factories.values().iterator().next().getId();
|
if (defaultProvider != null) {
|
||||||
}
|
this.provider.put(spi.getProviderClass(), defaultProvider);
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultProvider != null) {
|
public static String resolveDefaultProvider(Map<String, ProviderFactory> factories, Spi spi) {
|
||||||
this.provider.put(spi.getProviderClass(), defaultProvider);
|
if (factories == null) {
|
||||||
logger.debugv("Set default provider for {0} to {1}", spi.getName(), defaultProvider);
|
return null;
|
||||||
} else {
|
}
|
||||||
logger.debugv("No default provider for {0}", spi.getName());
|
|
||||||
|
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;
|
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
|
@Override
|
||||||
public Config.Scope scope(String... path) {
|
public Config.Scope scope(String... path) {
|
||||||
return new JsonScope(getNode(config, path));
|
return new JsonScope(getNode(config, path));
|
||||||
|
|
|
@ -146,6 +146,11 @@ public class SoapTest {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultProvider(String spi) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Config.Scope scope(String... scope) {
|
public Config.Scope scope(String... scope) {
|
||||||
if (scope.length == 2 && "connectionsHttpClient".equals(scope[0]) && "default".equals(scope[1])) {
|
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");
|
return getConfig().get(spiName + ".provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultProvider(String spiName) {
|
||||||
|
return getConfig().get(spiName + ".provider.default");
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getConfig() {
|
public Map<String, String> getConfig() {
|
||||||
return useGlobalConfigurationFunc.getAsBoolean() ? defaultProperties : properties.get();
|
return useGlobalConfigurationFunc.getAsBoolean() ? defaultProperties : properties.get();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue