Enables the new store and the concurrenthashmap provider

Closes #12651
This commit is contained in:
Pedro Igor 2022-06-29 11:00:55 -03:00 committed by Bruno Oliveira da Silva
parent 3170efd3ad
commit 605b51890e
45 changed files with 686 additions and 443 deletions

View file

@ -103,7 +103,13 @@ public class Profile {
}
public static void init() {
CURRENT = new Profile(null);
PropertyResolver resolver = null;
if (CURRENT != null) {
resolver = CURRENT.propertyResolver;
}
CURRENT = new Profile(resolver);
}
public static String getName() {

View file

@ -4,13 +4,15 @@ import org.keycloak.common.Profile;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class FeatureOptions {
public static final Option FEATURES = new OptionBuilder("features", List.class, Profile.Feature.class)
public static final Option<List> FEATURES = new OptionBuilder("features", List.class, Profile.Feature.class)
.category(OptionCategory.FEATURE)
.description("Enables a set of one or more features.")
.expectedStringValues(getFeatureValues())
.defaultValue(Optional.empty())
.buildTime(true)
.build();

View file

@ -59,7 +59,7 @@ public class LoggingOptions {
return super.toString().toLowerCase(Locale.ROOT);
}
}
public static final Option LOG_CONSOLE_OUTPUT = new OptionBuilder<>("log-console-output", Output.class)
public static final Option<Output> LOG_CONSOLE_OUTPUT = new OptionBuilder<>("log-console-output", Output.class)
.category(OptionCategory.LOGGING)
.defaultValue(DEFAULT_CONSOLE_OUTPUT)
.description("Set the log output to JSON or default (plain) unstructured logging.")

View file

@ -14,7 +14,7 @@ public class ProxyOptions {
passthrough;
}
public static final Option<Mode> proxy = new OptionBuilder<>("proxy", Mode.class)
public static final Option<Mode> PROXY = new OptionBuilder<>("proxy", Mode.class)
.category(OptionCategory.PROXY)
.description("The proxy address forwarding mode if the server is behind a reverse proxy. " +
"Possible values are: " + String.join(",", Arrays.stream(Mode.values()).skip(1).map(m -> m.name()).collect(Collectors.joining(","))))
@ -22,7 +22,7 @@ public class ProxyOptions {
.expectedValues(Mode.values())
.build();
public static final Option<Boolean> proxyForwardedHost = new OptionBuilder<>("proxy-forwarded-host", Boolean.class)
public static final Option<Boolean> PROXY_FORWARDED_HOST = new OptionBuilder<>("proxy-forwarded-host", Boolean.class)
.category(OptionCategory.PROXY)
.defaultValue(Boolean.FALSE)
.build();
@ -30,7 +30,7 @@ public class ProxyOptions {
public static final List<Option<?>> ALL_OPTIONS = new ArrayList<>();
static {
ALL_OPTIONS.add(proxy);
ALL_OPTIONS.add(proxyForwardedHost);
ALL_OPTIONS.add(PROXY);
ALL_OPTIONS.add(PROXY_FORWARDED_HOST);
}
}

View file

@ -17,13 +17,145 @@
package org.keycloak.config;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StorageOptions {
public static final Option<Boolean> DEFAULT_PERSISTENCE_UNIT_ENABLED = new OptionBuilder<>("storage-default-persistence-unit-enabled", Boolean.class)
public enum StorageType {
legacy,
chm
}
public static final Option<Boolean> STORAGE_LEGACY_ENABLED = new OptionBuilder<>("storage-legacy-enabled", Boolean.class)
.category(OptionCategory.STORAGE)
.defaultValue(true)
.hidden()
.buildTime(true)
.build();
public static final Option<StorageType> STORAGE = new OptionBuilder<>("storage", StorageType.class)
.category(OptionCategory.STORAGE)
.description(String.format("Sets a storage mechanism. Possible values are: %s.",
String.join(",", String.join(", ", Arrays.stream(StorageType.values()).map(StorageType::name).collect(Collectors.toList())))))
.expectedValues(StorageType.values())
.defaultValue(StorageType.legacy)
.buildTime(true)
.build();
public static final Option<StorageType> STORAGE_PROVIDER = new OptionBuilder<>("storage-provider", StorageType.class)
.category(OptionCategory.STORAGE)
.buildTime(true)
.build();
public static final Option<String> STORAGE_REALM = new OptionBuilder<>("storage-realm", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_CLIENT = new OptionBuilder<>("storage-client", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_CLIENT_SCOPE = new OptionBuilder<>("storage-client-scope", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_GROUP = new OptionBuilder<>("storage-group", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_ROLE = new OptionBuilder<>("storage-role", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_USER = new OptionBuilder<>("storage-user", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_DEPLOYMENT_STATE = new OptionBuilder<>("storage-deployment-state", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_AUTH_SESSION = new OptionBuilder<>("storage-auth-session", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_USER_SESSION = new OptionBuilder<>("storage-user-session", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_LOGIN_FAILURE = new OptionBuilder<>("storage-login-failure", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_AUTHORIZATION_PERSISTER = new OptionBuilder<>("storage-authorization-persister", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_USER_SESSION_PERSISTER = new OptionBuilder<>("storage-user-session-persister", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_ACTION_TOKEN = new OptionBuilder<>("storage-action-token", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_DBLOCK = new OptionBuilder<>("storage-dblock", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_CACHE_REALM_ENABLED = new OptionBuilder<>("cache-realm-enabled", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_CACHE_USER_ENABLED = new OptionBuilder<>("cache-user-enabled", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_SINGLE_USE_OBJECT = new OptionBuilder<>("storage-single-use-object", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<String> STORAGE_CACHE_AUTHORIZATION_ENABLED = new OptionBuilder<>("cache-authorization-enabled", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final List<Option<?>> ALL_OPTIONS = List.of(STORAGE);
}

View file

@ -27,9 +27,8 @@ import java.nio.file.Paths;
import java.util.stream.Collectors;
import javax.enterprise.context.ApplicationScoped;
import org.infinispan.commons.util.FileLookupFactory;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.KeycloakRecorder;
import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory;
import org.keycloak.quarkus.runtime.storage.legacy.infinispan.CacheManagerFactory;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;

View file

@ -22,11 +22,11 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.getOption
import java.util.function.BooleanSupplier;
import org.keycloak.quarkus.runtime.Environment;
public class IsDefaultPersistenceUnitEnabled implements BooleanSupplier {
public class IsLegacyStoreEnabled implements BooleanSupplier {
@Override
public boolean getAsBoolean() {
return getOptionalBooleanValue("kc.storage-default-persistence-unit-enabled").get();
return getOptionalBooleanValue("kc.storage-legacy-enabled").get();
}
}

View file

@ -21,7 +21,7 @@ import static org.keycloak.quarkus.runtime.Providers.getProviderManager;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS;
import static org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource.QUARKUS_PROPERTY_ENABLED;
import static org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
import static org.keycloak.quarkus.runtime.storage.legacy.database.LegacyJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
@ -202,7 +202,7 @@ class KeycloakProcessor {
* @param config
* @param descriptors
*/
@BuildStep
@BuildStep(onlyIf = IsLegacyStoreEnabled.class)
@Record(ExecutionTime.RUNTIME_INIT)
void configurePersistenceUnits(HibernateOrmConfig config,
List<PersistenceXmlDescriptorBuildItem> descriptors,
@ -228,7 +228,7 @@ class KeycloakProcessor {
}
}
@BuildStep(onlyIf = IsDefaultPersistenceUnitEnabled.class)
@BuildStep(onlyIf = IsLegacyStoreEnabled.class)
void produceDefaultPersistenceUnit(BuildProducer<PersistenceXmlDescriptorBuildItem> producer) {
ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit(
Thread.currentThread().getContextClassLoader().getResource("default-persistence.xml"));
@ -522,7 +522,19 @@ class KeycloakProcessor {
for (Spi spi : pm.loadSpis()) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> providers = new HashMap<>();
List<ProviderFactory> loadedFactories = new ArrayList<>(pm.load(spi));
String provider = Config.getProvider(spi.getName());
List<ProviderFactory> loadedFactories = new ArrayList<>();
if (provider == null) {
loadedFactories.addAll(pm.load(spi));
} else {
ProviderFactory factory = pm.load(spi, provider);
if (factory != null) {
loadedFactories.add(factory);
}
}
Map<String, ProviderFactory> deployedScriptProviders = loadDeployedScriptProviders(classLoader, spi);
loadedFactories.addAll(deployedScriptProviders.values());

View file

@ -295,6 +295,16 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-map</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Keycloak Dependencies-->
<dependency>

View file

@ -20,6 +20,7 @@ package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.Environment.getKeycloakModeFromProfile;
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
import static org.keycloak.quarkus.runtime.Environment.isImportExportMode;
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
import static org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun;
import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllowed;
@ -35,10 +36,17 @@ import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.Quarkus;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.cli.Picocli;
import org.keycloak.common.Version;
import org.keycloak.quarkus.runtime.cli.command.Start;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
@ -50,6 +58,9 @@ import io.quarkus.runtime.annotations.QuarkusMain;
@ApplicationScoped
public class KeycloakMain implements QuarkusApplication {
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
public static void main(String[] args) {
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
List<String> cliArgs = Picocli.parseArgs(args);
@ -110,13 +121,17 @@ public class KeycloakMain implements QuarkusApplication {
*/
@Override
public int run(String... args) throws Exception {
if (!isImportExportMode()) {
createAdminUser();
}
if (isDevProfile()) {
Logger.getLogger(KeycloakMain.class).warnf("Running the server in development mode. DO NOT use this configuration in production.");
}
int exitCode = ApplicationLifecycleManager.getExitCode();
if (isTestLaunchMode()) {
if (isTestLaunchMode() || isImportExportMode()) {
// in test mode we exit immediately
// we should be managing this behavior more dynamically depending on the tests requirements (short/long lived)
Quarkus.asyncExit(exitCode);
@ -126,4 +141,35 @@ public class KeycloakMain implements QuarkusApplication {
return exitCode;
}
private void createAdminUser() {
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
if ((adminUserName == null || adminUserName.trim().length() == 0)
|| (adminPassword == null || adminPassword.trim().length() == 0)) {
return;
}
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakSession session = sessionFactory.create();
KeycloakTransactionManager transaction = session.getTransactionManager();
try {
transaction.begin();
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName, adminPassword);
ServicesLogger.LOGGER.addUserSuccess(adminUserName, Config.getAdminRealm());
transaction.commit();
} catch (IllegalStateException e) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailedUserExists(adminUserName, Config.getAdminRealm());
} catch (Throwable t) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
} finally {
session.close();
}
}
}

View file

@ -41,7 +41,7 @@ import org.keycloak.quarkus.runtime.storage.database.liquibase.FastServiceLocato
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory;
import org.keycloak.quarkus.runtime.storage.legacy.infinispan.CacheManagerFactory;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.ShutdownContext;

View file

@ -131,7 +131,7 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
}
private <T> T getValue(String key, Class<T> clazz, T defaultValue) {
return config.getOptionalValue(toDashCase(prefix.concat(OPTION_PART_SEPARATOR).concat(key)), clazz).orElse(defaultValue);
return config.getOptionalValue(toDashCase(prefix.concat(OPTION_PART_SEPARATOR).concat(key.replace('.', '-'))), clazz).orElse(defaultValue);
}
}

View file

@ -5,8 +5,11 @@ import org.keycloak.quarkus.runtime.Environment;
import io.smallrye.config.ConfigSourceInterceptorContext;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
final class ClusteringPropertyMappers {
private ClusteringPropertyMappers() {
@ -30,11 +33,11 @@ final class ClusteringPropertyMappers {
};
}
private static String resolveConfigFile(String value, ConfigSourceInterceptorContext context) {
if ("local".equals(value)) {
return "cache-local.xml";
} else if ("ispn".equals(value)) {
return "cache-ispn.xml";
private static Optional<String> resolveConfigFile(Optional<String> value, ConfigSourceInterceptorContext context) {
if ("local".equals(value.get())) {
return of("cache-local.xml");
} else if ("ispn".equals(value.get())) {
return of("cache-ispn.xml");
}
String pathPrefix;
@ -46,6 +49,6 @@ final class ClusteringPropertyMappers {
pathPrefix = homeDir + "/conf/";
}
return pathPrefix + value;
return of(pathPrefix + value.get());
}
}

View file

@ -7,8 +7,8 @@ import org.keycloak.config.DatabaseOptions;
import org.keycloak.config.database.Database;
import java.util.Optional;
import java.util.function.BiFunction;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.Messages.invalidDatabaseVendor;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
@ -27,7 +27,7 @@ final class DatabasePropertyMappers {
fromOption(DatabaseOptions.DB_DRIVER)
.mapFrom("db")
.to("quarkus.datasource.jdbc.driver")
.transformer(DatabasePropertyMappers.getXaOrNonXaDriver())
.transformer(DatabasePropertyMappers::getXaOrNonXaDriver)
.build(),
fromOption(DatabaseOptions.DB)
.to("quarkus.datasource.db-kind")
@ -37,7 +37,7 @@ final class DatabasePropertyMappers {
fromOption(DatabaseOptions.DB_URL)
.to("quarkus.datasource.jdbc.url")
.mapFrom("db")
.transformer(DatabasePropertyMappers.getDatabaseUrl())
.transformer(DatabasePropertyMappers::getDatabaseUrl)
.paramLabel("jdbc-url")
.build(),
fromOption(DatabaseOptions.DB_URL_HOST)
@ -88,46 +88,56 @@ final class DatabasePropertyMappers {
};
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> getDatabaseUrl() {
return (s, c) -> Database.getDefaultUrl(s).orElse(s);
private static Optional<String> getDatabaseUrl(Optional<String> value, ConfigSourceInterceptorContext c) {
Optional<String> url = Database.getDefaultUrl(value.get());
if (url.isPresent()) {
return url;
}
return value;
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> getXaOrNonXaDriver() {
return (String db, ConfigSourceInterceptorContext context) -> {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
private static Optional<String> getXaOrNonXaDriver(Optional<String> value, ConfigSourceInterceptorContext context) {
ConfigValue xaEnabledConfigValue = context.proceed("kc.transaction-xa-enabled");
boolean isXaEnabled = xaEnabledConfigValue == null || Boolean.parseBoolean(xaEnabledConfigValue.getValue());
boolean isXaEnabled = xaEnabledConfigValue == null || Boolean.parseBoolean(xaEnabledConfigValue.getValue());
return Database.getDriver(db, isXaEnabled).orElse(db);
};
Optional<String> driver = Database.getDriver(value.get(), isXaEnabled);
if (driver.isPresent()) {
return driver;
}
return value;
}
private static String toDatabaseKind(String db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db);
private static Optional<String> toDatabaseKind(Optional<String> db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db.get());
if (databaseKind.isPresent()) {
return databaseKind.get();
return databaseKind;
}
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
addInitializationException(invalidDatabaseVendor(db.get(), Database.getAliases()));
return "h2";
return of("h2");
}
private static String resolveUsername(String value, ConfigSourceInterceptorContext context) {
private static Optional<String> resolveUsername(Optional<String> value, ConfigSourceInterceptorContext context) {
if (isDevModeDatabase(context)) {
return "sa";
return of("sa");
}
return Database.getDatabaseKind(value).isEmpty() ? value : null;
return Database.getDatabaseKind(value.get()).isEmpty() ? value : null;
}
private static String resolvePassword(String value, ConfigSourceInterceptorContext context) {
private static Optional<String> resolvePassword(Optional<String> value, ConfigSourceInterceptorContext context) {
if (isDevModeDatabase(context)) {
return "password";
return of("password");
}
return Database.getDatabaseKind(value).isEmpty() ? value : null;
return Database.getDatabaseKind(value.get()).isEmpty() ? value : null;
}
private static boolean isDevModeDatabase(ConfigSourceInterceptorContext context) {
@ -135,13 +145,19 @@ final class DatabasePropertyMappers {
return Database.getDatabaseKind(db).get().equals(DatabaseKind.H2);
}
private static String transformDialect(String db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db);
private static Optional<String> transformDialect(Optional<String> db, ConfigSourceInterceptorContext context) {
Optional<String> databaseKind = Database.getDatabaseKind(db.get());
if (databaseKind.isEmpty()) {
return db;
}
return Database.getDialect(db).orElse(Database.getDialect("dev-file").get());
Optional<String> dialect = Database.getDialect(db.get());
if (dialect.isPresent()) {
return dialect;
}
return Database.getDialect("dev-file");
}
}

View file

@ -1,9 +1,19 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import org.keycloak.common.Profile;
import org.keycloak.config.FeatureOptions;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import io.smallrye.config.ConfigSourceInterceptorContext;
final class FeaturePropertyMappers {
private FeaturePropertyMappers() {
@ -13,6 +23,7 @@ final class FeaturePropertyMappers {
return new PropertyMapper[] {
fromOption(FeatureOptions.FEATURES)
.paramLabel("feature")
.transformer(FeaturePropertyMappers::transformFeatures)
.build(),
fromOption(FeatureOptions.FEATURES_DISABLED)
.paramLabel("feature")
@ -20,4 +31,15 @@ final class FeaturePropertyMappers {
};
}
private static Optional<String> transformFeatures(Optional<String> features, ConfigSourceInterceptorContext context) {
if (Boolean.parseBoolean(Configuration.getRawValue("kc.storage-legacy-enabled"))) {
return features;
}
Set<String> featureSet = new HashSet<>(List.of(features.orElse("").split(",")));
featureSet.add(Profile.Feature.MAP_STORAGE.name().replace('_', '-'));
return of(String.join(",", featureSet));
}
}

View file

@ -9,7 +9,9 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import java.io.File;
import java.nio.file.Paths;
import java.util.Optional;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.getMapper;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
@ -91,8 +93,8 @@ final class HttpPropertyMappers {
};
}
private static String getHttpEnabledTransformer(String value, ConfigSourceInterceptorContext context) {
boolean enabled = Boolean.parseBoolean(value);
private static Optional<String> getHttpEnabledTransformer(Optional<String> value, ConfigSourceInterceptorContext context) {
boolean enabled = Boolean.parseBoolean(value.get());
ConfigValue proxy = context.proceed(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + "proxy");
if (Environment.isDevMode() || Environment.isImportExportMode()
@ -112,7 +114,7 @@ final class HttpPropertyMappers {
}
}
return enabled ? "enabled" : "disabled";
return of(enabled ? "enabled" : "disabled");
}
private static String getDefaultKeystorePathValue() {

View file

@ -1,5 +1,6 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
@ -7,6 +8,7 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -29,12 +31,7 @@ public final class LoggingPropertyMappers {
fromOption(LoggingOptions.LOG_CONSOLE_OUTPUT)
.to("quarkus.log.console.json")
.paramLabel("default|json")
.transformer((value, context) -> {
if(value.equals(LoggingOptions.DEFAULT_CONSOLE_OUTPUT.name().toLowerCase(Locale.ROOT))) {
return Boolean.FALSE.toString();
}
return Boolean.TRUE.toString();
})
.transformer(LoggingPropertyMappers::resolveLogConsoleOutput)
.build(),
fromOption(LoggingOptions.LOG_CONSOLE_FORMAT)
.to("quarkus.log.console.format")
@ -71,38 +68,40 @@ public final class LoggingPropertyMappers {
};
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> resolveLogHandler(String handler) {
private static BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> resolveLogHandler(String handler) {
return (parentValue, context) -> {
//we want to fall back to console to not have nothing shown up when wrong values are set.
String consoleDependantErrorResult = handler.equals(LoggingOptions.DEFAULT_LOG_HANDLER.name()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
String handlers = parentValue.get();
if(parentValue.isBlank()) {
if(handlers.isBlank()) {
addInitializationException(Messages.emptyValueForKey("log"));
return consoleDependantErrorResult;
return of(consoleDependantErrorResult);
}
String[] logHandlerValues = parentValue.split(",");
String[] logHandlerValues = handlers.split(",");
List<String> availableLogHandlers = Arrays.stream(LoggingOptions.Handler.values()).map(h -> h.name()).collect(Collectors.toList());
if (!availableLogHandlers.containsAll(List.of(logHandlerValues))) {
addInitializationException(Messages.notRecognizedValueInList("log", parentValue, String.join(",", availableLogHandlers)));
return consoleDependantErrorResult;
addInitializationException(Messages.notRecognizedValueInList("log", handlers, String.join(",", availableLogHandlers)));
return of(consoleDependantErrorResult);
}
for (String handlerInput : logHandlerValues) {
if (handlerInput.equals(handler)) {
return Boolean.TRUE.toString();
return of(Boolean.TRUE.toString());
}
}
return Boolean.FALSE.toString();
return of(Boolean.FALSE.toString());
};
}
private static String resolveFileLogLocation(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
if (value.endsWith(File.separator)) {
return value + LoggingOptions.DEFAULT_LOG_FILENAME;
private static Optional<String> resolveFileLogLocation(Optional<String> value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
String location = value.get();
if (location.endsWith(File.separator)) {
return of(location + LoggingOptions.DEFAULT_LOG_FILENAME);
}
return value;
@ -116,10 +115,10 @@ public final class LoggingPropertyMappers {
LogContext.getLogContext().getLogger(category).setLevel(toLevel(level));
}
private static String resolveLogLevel(String value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
String rootLevel = LoggingOptions.DEFAULT_LOG_LEVEL.name();
private static Optional<String> resolveLogLevel(Optional<String> value, ConfigSourceInterceptorContext configSourceInterceptorContext) {
Optional<String> rootLevel = of(LoggingOptions.DEFAULT_LOG_LEVEL.name());
for (String level : value.split(",")) {
for (String level : value.get().split(",")) {
String[] parts = level.split(":");
String category = null;
String categoryLevel;
@ -144,7 +143,7 @@ public final class LoggingPropertyMappers {
}
if (category == null) {
rootLevel = levelType.getName();
rootLevel = of(levelType.getName());
} else {
setCategoryLevel(category, levelType.getName());
}
@ -152,4 +151,12 @@ public final class LoggingPropertyMappers {
return rootLevel;
}
private static Optional<String> resolveLogConsoleOutput(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.get().equals(LoggingOptions.DEFAULT_CONSOLE_OUTPUT.name().toLowerCase(Locale.ROOT))) {
return of(Boolean.FALSE.toString());
}
return of(Boolean.TRUE.toString());
}
}

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.ofNullable;
import static org.keycloak.quarkus.runtime.Environment.isRebuild;
import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PART_SEPARATOR;
import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PART_SEPARATOR_CHAR;
@ -23,6 +24,7 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.toCliForm
import static org.keycloak.quarkus.runtime.configuration.Configuration.toEnvVarFormat;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@ -52,14 +54,14 @@ public class PropertyMapper<T> {
private final Option<T> option;
private final String to;
private final BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private final BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
private final String mapFrom;
private final boolean mask;
private final String paramLabel;
private final String envVarFormat;
private String cliFormat;
PropertyMapper(Option<T> option, String to, BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
PropertyMapper(Option<T> option, String to, BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper,
String mapFrom, String paramLabel, boolean mask) {
this.option = option;
this.to = to == null ? getFrom() : to;
@ -71,7 +73,7 @@ public class PropertyMapper<T> {
this.envVarFormat = toEnvVarFormat(getFrom());
}
private static String defaultTransformer(String value, ConfigSourceInterceptorContext context) {
private static Optional<String> defaultTransformer(Optional<String> value, ConfigSourceInterceptorContext context) {
return value;
}
@ -111,19 +113,21 @@ public class PropertyMapper<T> {
}
if (parentValue != null) {
return transformValue(parentValue.getValue(), context);
return transformValue(ofNullable(parentValue.getValue()), context);
}
}
if (this.option.getDefaultValue().isPresent()) {
return transformValue(this.option.getDefaultValue().get().toString(), context);
ConfigValue defaultValue = transformValue(this.option.getDefaultValue().map(Objects::toString), context);
if (defaultValue != null) {
return defaultValue;
}
// now tries any defaults from quarkus
ConfigValue current = context.proceed(name);
if (current != null) {
return transformValue(current.getValue(), context);
return transformValue(ofNullable(current.getValue()), context);
}
return current;
@ -133,7 +137,7 @@ public class PropertyMapper<T> {
return config;
}
ConfigValue value = transformValue(config.getValue(), context);
ConfigValue value = transformValue(ofNullable(config.getValue()), context);
// we always fallback to the current value from the property we are mapping
if (value == null) {
@ -193,29 +197,29 @@ public class PropertyMapper<T> {
return mask;
}
private ConfigValue transformValue(String value, ConfigSourceInterceptorContext context) {
private ConfigValue transformValue(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value == null) {
return null;
}
if (mapper == null) {
return ConfigValue.builder().withName(to).withValue(value).build();
return ConfigValue.builder().withName(to).withValue(value.orElse(null)).build();
}
String mappedValue = mapper.apply(value, context);
Optional<String> mappedValue = mapper.apply(value, context);
if (mappedValue != null) {
return ConfigValue.builder().withName(to).withValue(mappedValue).build();
if (mappedValue == null || mappedValue.isEmpty()) {
return null;
}
return null;
return ConfigValue.builder().withName(to).withValue(mappedValue.get()).build();
}
public static class Builder<T> {
private final Option<T> option;
private String to;
private BiFunction<String, ConfigSourceInterceptorContext, String> mapper;
private BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper;
private String mapFrom = null;
private boolean isMasked = false;
private String paramLabel;
@ -229,7 +233,7 @@ public class PropertyMapper<T> {
return this;
}
public Builder<T> transformer(BiFunction<String, ConfigSourceInterceptorContext, String> mapper) {
public Builder<T> transformer(BiFunction<Optional<String>, ConfigSourceInterceptorContext, Optional<String>> mapper) {
this.mapper = mapper;
return this;
}

View file

@ -2,8 +2,9 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import java.util.function.BiFunction;
import java.util.Optional;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
@ -16,12 +17,12 @@ final class ProxyPropertyMappers {
public static PropertyMapper[] getProxyPropertyMappers() {
return new PropertyMapper[] {
fromOption(ProxyOptions.proxy)
fromOption(ProxyOptions.PROXY)
.to("quarkus.http.proxy.proxy-address-forwarding")
.transformer(ProxyPropertyMappers::getValidProxyModeValue)
.paramLabel("mode")
.build(),
fromOption(ProxyOptions.proxyForwardedHost)
fromOption(ProxyOptions.PROXY_FORWARDED_HOST)
.to("quarkus.http.proxy.enable-forwarded-host")
.mapFrom("proxy")
.transformer(ProxyPropertyMappers::getResolveEnableForwardedHost)
@ -29,22 +30,24 @@ final class ProxyPropertyMappers {
};
}
private static String getValidProxyModeValue(String mode, ConfigSourceInterceptorContext context) {
private static Optional<String> getValidProxyModeValue(Optional<String> value, ConfigSourceInterceptorContext context) {
String mode = value.get();
switch (mode) {
case "none":
return "false";
return of(Boolean.FALSE.toString());
case "edge":
case "reencrypt":
case "passthrough":
return "true";
return of(Boolean.TRUE.toString());
default:
addInitializationException(Messages.invalidProxyMode(mode));
return "false";
return of(Boolean.FALSE.toString());
}
}
private static String getResolveEnableForwardedHost(String proxy, ConfigSourceInterceptorContext context) {
return String.valueOf(!"none".equals(proxy));
private static Optional<String> getResolveEnableForwardedHost(Optional<String> proxy, ConfigSourceInterceptorContext context) {
return of(String.valueOf(!ProxyOptions.Mode.none.name().equals(proxy)));
}
}

View file

@ -17,10 +17,11 @@
package org.keycloak.quarkus.runtime.configuration.mappers;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
import org.keycloak.config.StorageOptions;
import org.keycloak.quarkus.runtime.configuration.Configuration;
import io.smallrye.config.ConfigSourceInterceptorContext;
@ -30,10 +31,161 @@ final class StoragePropertyMappers {
public static PropertyMapper[] getMappers() {
return new PropertyMapper[] {
fromOption(StorageOptions.DEFAULT_PERSISTENCE_UNIT_ENABLED)
.to("kc.spi-connections-jpa-quarkus-enabled")
fromOption(StorageOptions.STORAGE_LEGACY_ENABLED)
.to("kc.spi-connections-jpa-legacy-enabled")
.mapFrom("storage")
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build()
.transformer(StoragePropertyMappers::isDefaultPersistenceUnitEnabled)
.build(),
fromOption(StorageOptions.STORAGE)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_PROVIDER)
.mapFrom("storage")
.to("kc.spi-map-storage-provider")
.transformer(StoragePropertyMappers::resolveStorageProvider)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_REALM)
.to("kc.spi-realm-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_CLIENT)
.to("kc.spi-client-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_CLIENT_SCOPE)
.to("kc.spi-client-scope-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_GROUP)
.to("kc.spi-group-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_ROLE)
.to("kc.spi-role-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_USER)
.to("kc.spi-user-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_DEPLOYMENT_STATE)
.to("kc.spi-deployment-state-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_AUTH_SESSION)
.to("kc.spi-authentication-sessions-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_USER_SESSION)
.to("kc.spi-user-sessions-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_LOGIN_FAILURE)
.to("kc.spi-login-failure-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_USER_SESSION_PERSISTER)
.to("kc.spi-user-session-persister-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getUserSessionPersisterStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_AUTHORIZATION_PERSISTER)
.to("kc.spi-authorization-persister-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getAreaStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_ACTION_TOKEN)
.to("kc.spi-action-token-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_DBLOCK)
.to("kc.spi-dblock-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getDbLockProvider)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_CACHE_REALM_ENABLED)
.to("kc.spi-realm-cache-default-enabled")
.mapFrom("storage")
.transformer(StoragePropertyMappers::isCacheAreaEnabledForStorage)
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build(),
fromOption(StorageOptions.STORAGE_CACHE_AUTHORIZATION_ENABLED)
.to("kc.spi-authorization-cache-default-enabled")
.mapFrom("storage")
.transformer(StoragePropertyMappers::isCacheAreaEnabledForStorage)
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build(),
fromOption(StorageOptions.STORAGE_CACHE_USER_ENABLED)
.to("kc.spi-user-cache-default-enabled")
.mapFrom("storage")
.transformer(StoragePropertyMappers::isCacheAreaEnabledForStorage)
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
.build(),
fromOption(StorageOptions.STORAGE_SINGLE_USE_OBJECT)
.to("kc.spi-single-use-object-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
};
}
private static Optional<String> getAreaStorage(Optional<String> storage, ConfigSourceInterceptorContext context) {
return of("legacy".equals(storage.orElse(null)) ? "jpa" : "map");
}
private static Optional<String> getCacheStorage(Optional<String> storage, ConfigSourceInterceptorContext context) {
return of("legacy".equals(storage.orElse(null)) ? "infinispan" : "map");
}
private static Optional<String> getDbLockProvider(Optional<String> storage, ConfigSourceInterceptorContext context) {
return of("legacy".equals(storage.orElse(null)) ? "jpa" : "none");
}
private static Optional<String> getUserSessionPersisterStorage(Optional<String> storage, ConfigSourceInterceptorContext context) {
return of("legacy".equals(storage.orElse(null)) ? "jpa" : "disabled");
}
private static Optional<String> isDefaultPersistenceUnitEnabled(Optional<String> value, ConfigSourceInterceptorContext context) {
if (value.get().equals(StorageOptions.StorageType.legacy.name())) {
return of(Boolean.TRUE.toString());
}
return of(Boolean.valueOf(value.get()).toString());
}
private static Optional<String> resolveStorageProvider(Optional<String> value, ConfigSourceInterceptorContext context) {
return Optional.ofNullable("legacy".equals(value.orElse(null)) ? null : "concurrenthashmap");
}
private static Optional<String> isCacheAreaEnabledForStorage(Optional<String> storage, ConfigSourceInterceptorContext context) {
return of("legacy".equals(storage.orElse(null)) ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
}
}

View file

@ -3,8 +3,11 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.config.TransactionOptions;
import static java.util.Optional.of;
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
import java.util.Optional;
public class TransactionPropertyMappers {
private TransactionPropertyMappers(){}
@ -19,14 +22,14 @@ public class TransactionPropertyMappers {
};
}
private static String getQuarkusTransactionsValue(String txValue, ConfigSourceInterceptorContext context) {
boolean isXaEnabled = Boolean.parseBoolean(txValue);
private static Optional<String> getQuarkusTransactionsValue(Optional<String> txValue, ConfigSourceInterceptorContext context) {
boolean isXaEnabled = Boolean.parseBoolean(txValue.get());
if (isXaEnabled) {
return "xa";
return of("xa");
}
return "enabled";
return of("enabled");
}
}

View file

@ -21,15 +21,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.ApplicationPath;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.quarkus.runtime.services.resources.QuarkusWelcomeResource;
import org.keycloak.services.resources.WelcomeResource;
@ -37,17 +31,22 @@ import org.keycloak.services.resources.WelcomeResource;
@ApplicationPath("/")
public class QuarkusKeycloakApplication extends KeycloakApplication {
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
private static boolean filterSingletons(Object o) {
return !WelcomeResource.class.isInstance(o);
}
@Override
protected void startup() {
initializeKeycloakSessionFactory();
createAdminUser();
QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance();
sessionFactory = instance;
instance.init();
ExportImportManager exportImportManager = bootstrap();
if (exportImportManager.isRunExport()) {
exportImportManager.runExport();
}
sessionFactory.publish(new PostMigrationEvent(sessionFactory));
}
@Override
@ -60,42 +59,4 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
return singletons;
}
private void initializeKeycloakSessionFactory() {
QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance();
sessionFactory = instance;
instance.init();
sessionFactory.publish(new PostMigrationEvent(sessionFactory));
}
private void createAdminUser() {
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
if ((adminUserName == null || adminUserName.trim().length() == 0)
|| (adminPassword == null || adminPassword.trim().length() == 0)) {
return;
}
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakSession session = sessionFactory.create();
KeycloakTransactionManager transaction = session.getTransactionManager();
try {
transaction.begin();
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName, adminPassword);
ServicesLogger.LOGGER.addUserSuccess(adminUserName, Config.getAdminRealm());
transaction.commit();
} catch (IllegalStateException e) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailedUserExists(adminUserName, Config.getAdminRealm());
} catch (Throwable t) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
} finally {
session.close();
}
}
}

View file

@ -15,18 +15,13 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.database.jpa;
package org.keycloak.quarkus.runtime.storage.legacy.database;
import static org.keycloak.connections.jpa.util.JpaUtils.configureNamedQuery;
import static org.keycloak.quarkus.runtime.storage.database.liquibase.QuarkusJpaUpdaterProvider.VERIFY_AND_RUN_MASTER_CHANGELOG;
import static org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProvider.VERIFY_AND_RUN_MASTER_CHANGELOG;
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
@ -35,60 +30,40 @@ import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringTokenizer;
import javax.enterprise.inject.Instance;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import com.fasterxml.jackson.core.type.TypeReference;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.Quarkus;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.ServerStartupError;
import org.keycloak.common.Version;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.connections.jpa.DefaultJpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
import org.keycloak.models.dblock.DBLockManager;
import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.util.JsonSerialization;
import org.keycloak.quarkus.runtime.storage.database.jpa.AbstractJpaConnectionProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionProviderFactory implements ServerInfoAwareProviderFactory {
public class LegacyJpaConnectionProviderFactory extends AbstractJpaConnectionProviderFactory implements ServerInfoAwareProviderFactory {
public static final String QUERY_PROPERTY_PREFIX = "kc.query.";
private static final Logger logger = Logger.getLogger(QuarkusJpaConnectionProviderFactory.class);
private static final Logger logger = Logger.getLogger(LegacyJpaConnectionProviderFactory.class);
private static final String SQL_GET_LATEST_VERSION = "SELECT ID, VERSION FROM %sMIGRATION_MODEL ORDER BY UPDATE_TIME DESC";
enum MigrationStrategy {
@ -96,7 +71,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
}
private Map<String, String> operationalInfo;
private KeycloakSessionFactory factory;
@Override
public JpaConnectionProvider create(KeycloakSession session) {
@ -106,7 +80,7 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
@Override
public String getId() {
return "quarkus";
return "legacy";
}
private void addSpecificNamedQueries(KeycloakSession session) {
@ -128,7 +102,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
@Override
public void postInit(KeycloakSessionFactory factory) {
super.postInit(factory);
this.factory = factory;
KeycloakSession session = factory.create();
String id = null;
@ -160,8 +133,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
if (schemaChanged || Environment.isImportExportMode()) {
runJobInTransaction(factory, this::initSchema);
} else if (System.getProperty("keycloak.import") != null) {
importRealms();
} else {
Version.RESOURCES_VERSION = id;
}
@ -227,8 +198,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
}
private void initSchema(KeycloakSession session) {
ExportImportManager exportImportManager = new ExportImportManager(session);
/*
* Migrate model is executed just in case following providers are "jpa".
* In Map Storage, there is an assumption that migrateModel is not needed.
@ -240,75 +209,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
logger.debug("Calling migrateModel");
migrateModel(session);
}
DBLockManager dbLockManager = new DBLockManager(session);
dbLockManager.checkForcedUnlock();
DBLockProvider dbLock = dbLockManager.getDBLock();
dbLock.waitForLock(DBLockProvider.Namespace.KEYCLOAK_BOOT);
try {
createMasterRealm(exportImportManager);
} finally {
dbLock.releaseLock();
}
if (exportImportManager.isRunExport()) {
exportImportManager.runExport();
Quarkus.asyncExit();
}
}
private ExportImportManager createMasterRealm(ExportImportManager exportImportManager) {
logger.debug("bootstrap");
KeycloakSession session = factory.create();
try {
session.getTransactionManager().begin();
if (xaEnabled) {
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) factory
.getProviderFactory(JtaTransactionManagerLookup.class);
try {
Transaction transaction = lookup.getTransactionManager().getTransaction();
logger.debugv("bootstrap current transaction? {0}", transaction != null);
if (transaction != null) {
logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
}
} catch (SystemException e) {
throw new RuntimeException(e);
}
}
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
boolean createMasterRealm = applianceBootstrap.isNewInstall();
if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) {
createMasterRealm = false;
}
if (createMasterRealm) {
applianceBootstrap.createMasterRealm();
}
session.getTransactionManager().commit();
} catch (RuntimeException re) {
if (session.getTransactionManager().isActive()) {
session.getTransactionManager().rollback();
}
throw re;
} finally {
session.close();
}
if (exportImportManager.isRunImport()) {
exportImportManager.runImport();
Quarkus.asyncExit();
} else {
importRealms();
}
importAddUser();
return exportImportManager;
}
private void migrateModel(KeycloakSession session) {
@ -319,131 +219,6 @@ public class QuarkusJpaConnectionProviderFactory extends AbstractJpaConnectionPr
}
}
private void importRealms() {
String files = System.getProperty("keycloak.import");
if (files != null) {
StringTokenizer tokenizer = new StringTokenizer(files, ",");
while (tokenizer.hasMoreTokens()) {
String file = tokenizer.nextToken().trim();
RealmRepresentation rep;
try {
Path filePath = Paths.get(file);
if (!(Files.exists(filePath) && Files.isRegularFile(filePath) && filePath.toString().endsWith(".json"))) {
logger.debugf("Ignoring import file because it is not a valid file: %s", file);
continue;
}
rep = JsonSerialization.readValue(StringPropertyReplacer.replaceProperties(
Files.readString(filePath), new StringPropertyReplacer.PropertyResolver() {
@Override
public String resolve(String property) {
return Optional.ofNullable(System.getenv(property)).orElse(null);
}
}), RealmRepresentation.class);
} catch (Exception cause) {
throw new RuntimeException("Failed to parse realm configuration file: " + file, cause);
}
importRealm(rep, "file " + file);
}
}
}
private void importRealm(RealmRepresentation rep, String from) {
KeycloakSession session = factory.create();
boolean exists = false;
try {
session.getTransactionManager().begin();
try {
RealmManager manager = new RealmManager(session);
if (rep.getId() != null && manager.getRealm(rep.getId()) != null) {
ServicesLogger.LOGGER.realmExists(rep.getRealm(), from);
exists = true;
}
if (!exists && manager.getRealmByName(rep.getRealm()) != null) {
ServicesLogger.LOGGER.realmExists(rep.getRealm(), from);
exists = true;
}
if (!exists) {
RealmModel realm = manager.importRealm(rep);
ServicesLogger.LOGGER.importedRealm(realm.getName(), from);
}
session.getTransactionManager().commit();
} catch (Throwable cause) {
session.getTransactionManager().rollback();
if (!exists) {
throw new RuntimeException("Failed to import realm: " + rep.getRealm(), cause);
}
}
} finally {
session.close();
}
}
private void importAddUser() {
String configDir = System.getProperty("jboss.server.config.dir");
if (configDir != null) {
File addUserFile = new File(configDir + File.separator + "keycloak-add-user.json");
if (addUserFile.isFile()) {
ServicesLogger.LOGGER.imprtingUsersFrom(addUserFile);
List<RealmRepresentation> realms;
try {
realms = JsonSerialization
.readValue(new FileInputStream(addUserFile), new TypeReference<List<RealmRepresentation>>() {
});
} catch (IOException e) {
ServicesLogger.LOGGER.failedToLoadUsers(e);
return;
}
for (RealmRepresentation realmRep : realms) {
for (UserRepresentation userRep : realmRep.getUsers()) {
KeycloakSession session = factory.create();
try {
session.getTransactionManager().begin();
RealmModel realm = session.realms().getRealmByName(realmRep.getRealm());
if (realm == null) {
ServicesLogger.LOGGER.addUserFailedRealmNotFound(userRep.getUsername(), realmRep.getRealm());
}
UserProvider users = session.users();
if (users.getUserByUsername(realm, userRep.getUsername()) != null) {
ServicesLogger.LOGGER.notCreatingExistingUser(userRep.getUsername());
} else {
UserModel user = users.addUser(realm, userRep.getUsername());
user.setEnabled(userRep.isEnabled());
RepresentationToModel.createCredentials(userRep, session, realm, user, false);
RepresentationToModel.createRoleMappings(userRep, user, realm);
ServicesLogger.LOGGER.addUserSuccess(userRep.getUsername(), realmRep.getRealm());
}
session.getTransactionManager().commit();
} catch (ModelDuplicateException e) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailedUserExists(userRep.getUsername(), realmRep.getRealm());
} catch (Throwable t) {
session.getTransactionManager().rollback();
ServicesLogger.LOGGER.addUserFailed(t, userRep.getUsername(), realmRep.getRealm());
} finally {
session.close();
}
}
}
if (!addUserFile.delete()) {
ServicesLogger.LOGGER.failedToDeleteFile(addUserFile.getAbsolutePath());
}
}
}
}
private String getSchema(String schema) {
return schema == null ? "" : schema + ".";
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.infinispan;
package org.keycloak.quarkus.runtime.storage.legacy.infinispan;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.infinispan;
package org.keycloak.quarkus.runtime.storage.legacy.infinispan;
import org.keycloak.cluster.ManagedCacheManagerProvider;
import org.keycloak.Config;

View file

@ -1,23 +1,21 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* * Copyright 2021 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.
* 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.quarkus.runtime.storage.infinispan;
package org.keycloak.quarkus.runtime.storage.legacy.infinispan;
import org.infinispan.manager.EmbeddedCacheManager;
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.database.liquibase;
package org.keycloak.quarkus.runtime.storage.legacy.liquibase;
import java.io.File;
import java.io.FileWriter;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.database.liquibase;
package org.keycloak.quarkus.runtime.storage.legacy.liquibase;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.quarkus.runtime.storage.database.liquibase;
package org.keycloak.quarkus.runtime.storage.legacy.liquibase;
import java.lang.reflect.Method;
import java.sql.Connection;

View file

@ -24,4 +24,10 @@ metrics-enabled=false
#logging defaults
log-console-output=default
log-file=${kc.home.dir:default}data/log/keycloak.log
log-file=${kc.home.dir:default}data/log/keycloak.log
# Storage defaults
spi-map-storage-concurrenthashmap-dir=${kc.home.dir:default}data/chm
spi-map-storage-concurrenthashmap-key-type-single-use-objects=string
spi-map-storage-concurrenthashmap-key-type-realms=string
spi-map-storage-concurrenthashmap-key-type-authz-resource-servers=string

View file

@ -1 +1 @@
org.keycloak.quarkus.runtime.storage.infinispan.QuarkusCacheManagerProvider
org.keycloak.quarkus.runtime.storage.legacy.infinispan.QuarkusCacheManagerProvider

View file

@ -17,4 +17,4 @@
# */
#
org.keycloak.quarkus.runtime.storage.infinispan.QuarkusInfinispanConnectionFactory
org.keycloak.quarkus.runtime.storage.legacy.infinispan.QuarkusInfinispanConnectionFactory

View file

@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory
org.keycloak.quarkus.runtime.storage.legacy.database.LegacyJpaConnectionProviderFactory

View file

@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.quarkus.runtime.storage.database.liquibase.QuarkusJpaUpdaterProviderFactory
org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProviderFactory

View file

@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.quarkus.runtime.storage.database.liquibase.QuarkusLiquibaseConnectionProvider
org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusLiquibaseConnectionProvider

View file

@ -38,6 +38,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.Config;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
@ -128,6 +129,11 @@ public class ConfigurationTest {
assertEquals("debug", createConfig().getRawValue("kc.log-level"));
}
@Test
public void testSanitizeKey() {
assertEquals("string", initConfig("map-storage", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).get("keyType.realms"));
}
@Test
public void testEnvVarAvailableFromPropertyNames() {
putEnvVar("KC_VAULT_DIR", "/foo/bar");

View file

@ -88,14 +88,14 @@ public class CLITestExtension extends QuarkusMainTestExtension {
if (distConfig != null) {
onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class));
if (dist == null) {
dist = createDistribution(distConfig);
}
onBeforeStartDistribution(context.getRequiredTestClass().getAnnotation(BeforeStartDistribution.class));
onBeforeStartDistribution(context.getRequiredTestMethod().getAnnotation(BeforeStartDistribution.class));
if (launch != null) {
if (dist == null) {
dist = createDistribution(distConfig);
}
onBeforeStartDistribution(context.getRequiredTestClass().getAnnotation(BeforeStartDistribution.class));
onBeforeStartDistribution(context.getRequiredTestMethod().getAnnotation(BeforeStartDistribution.class));
result = dist.run(Arrays.asList(launch.value()));
}
} else {
@ -226,7 +226,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
Class<?> type = parameterContext.getParameter().getType();
return type == LaunchResult.class || type == RawDistRootPath.class || (dist != null && type == KeycloakDistribution.class);
return type == LaunchResult.class || type == RawDistRootPath.class || type == KeycloakDistribution.class;
}
private void configureProfile(ExtensionContext context) {

View file

@ -44,7 +44,7 @@ public class ImportAtStartupDistTest {
@Test
@BeforeStartDistribution(CreateRealmConfigurationFileAndDir.class)
@Launch({"start-dev", "--import-realm", "--log-level=org.keycloak.quarkus.runtime.storage.database.jpa:debug"})
@Launch({"start-dev", "--import-realm", "--log-level=org.keycloak.services.resources.KeycloakApplication:debug"})
void testImportAndIgnoreDirectory(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Imported realm quickstart-realm from file");
@ -53,7 +53,7 @@ public class ImportAtStartupDistTest {
@Test
@BeforeStartDistribution(CreateRealmConfigurationFileWithUnsupportedExtension.class)
@Launch({"start-dev", "--import-realm", "--log-level=org.keycloak.quarkus.runtime.storage.database.jpa:debug"})
@Launch({"start-dev", "--import-realm", "--log-level=org.keycloak.services.resources.KeycloakApplication:debug"})
void testIgnoreFileWithUnsupportedExtension(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
cliResult.assertMessage("Ignoring import file because it is not a valid file");

View file

@ -49,7 +49,7 @@ public class LoggingDistTest {
@Launch({ "start-dev", "--log-level=debug" })
void testSetRootLevel(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
assertTrue(cliResult.getOutput().contains("DEBUG [org.hibernate"));
assertTrue(cliResult.getOutput().contains("DEBUG [io.quarkus.resteasy.runtime]"));
cliResult.assertStartedDevMode();
}
@ -87,7 +87,7 @@ public class LoggingDistTest {
@Launch({ "start-dev", "--log-level=off,org.keycloak:warn,debug" })
void testSetLastRootLevelIfMultipleSet(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
assertTrue(cliResult.getOutput().contains("DEBUG [org.hibernate"));
assertTrue(cliResult.getOutput().contains("DEBUG [io.quarkus.resteasy.runtime]"));
assertFalse(cliResult.getOutput().contains("INFO [org.keycloak"));
cliResult.assertStartedDevMode();
}
@ -125,7 +125,7 @@ public class LoggingDistTest {
void testLogLevelSettingsAppliedWhenJsonEnabled(LaunchResult result) {
CLIResult cliResult = (CLIResult) result;
assertFalse(cliResult.getOutput().contains("\"loggerName\":\"io.quarkus\",\"level\":\"INFO\")"));
assertTrue(cliResult.getOutput().contains("\"loggerName\":\"org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory\",\"level\":\"DEBUG\""));
assertTrue(cliResult.getOutput().contains("\"loggerName\":\"org.keycloak.services.resources.KeycloakApplication\",\"level\":\"DEBUG\""));
assertTrue(cliResult.getOutput().contains("\"loggerName\":\"org.infinispan.CONTAINER\",\"level\":\"INFO\""));
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2021 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.it.storage.map;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.CLIResult;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.utils.RawDistRootPath;
import io.quarkus.test.junit.main.Launch;
import io.quarkus.test.junit.main.LaunchResult;
@RawDistOnly(reason = "Need to check dist path")
@DistributionTest(reInstall = DistributionTest.ReInstall.BEFORE_TEST)
public class ChmStorageDistTest {
@Test
@Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--storage=chm" })
void testStartUsingChmsStorage(LaunchResult result, RawDistRootPath distPath) {
CLIResult cliResult = (CLIResult) result;
assertExpectedMessages(cliResult, distPath);
cliResult.assertStarted();
}
@Test
@Launch({ "start-dev", "--storage=chm" })
void testStartDevUsingChmsStorage(LaunchResult result, RawDistRootPath distPath) {
CLIResult cliResult = (CLIResult) result;
assertExpectedMessages(cliResult, distPath);
cliResult.assertStartedDevMode();
}
private void assertExpectedMessages(CLIResult cliResult, RawDistRootPath distPath) {
cliResult.assertMessage("Experimental feature enabled: map_storage");
cliResult.assertMessage("Hibernate ORM is disabled because no JPA entities were found");
Assert.assertFalse(distPath.getDistRootPath().resolve("data").resolve("h2").toFile().exists());
}
}

View file

@ -35,6 +35,10 @@ Cluster:
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Storage:
--storage <type> Sets a storage mechanism. Possible values are: legacy, chm. Default: legacy.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,

View file

@ -35,6 +35,10 @@ Cluster:
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Storage:
--storage <type> Sets a storage mechanism. Possible values are: legacy, chm. Default: legacy.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,

View file

@ -27,6 +27,10 @@ Cluster:
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Storage:
--storage <type> Sets a storage mechanism. Possible values are: legacy, chm. Default: legacy.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,

View file

@ -27,6 +27,10 @@ Cluster:
Define the default stack to use for cluster communication and node discovery.
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
Storage:
--storage <type> Sets a storage mechanism. Possible values are: legacy, chm. Default: legacy.
Database:
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,

View file

@ -22,6 +22,7 @@ import org.keycloak.Config;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.Resteasy;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.config.ConfigProviderFactory;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.models.KeycloakSession;
@ -58,12 +59,14 @@ import javax.ws.rs.core.Application;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.StringTokenizer;
@ -137,7 +140,7 @@ public class KeycloakApplication extends Application {
}
}
});
if (exportImportManager[0].isRunExport()) {
exportImportManager[0].runExport();
}
@ -252,9 +255,22 @@ public class KeycloakApplication extends Application {
String file = tokenizer.nextToken().trim();
RealmRepresentation rep;
try {
rep = loadJson(new FileInputStream(file), RealmRepresentation.class);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
Path filePath = Paths.get(file);
if (!(Files.exists(filePath) && Files.isRegularFile(filePath) && filePath.toString().endsWith(".json"))) {
logger.debugf("Ignoring import file because it is not a valid file: %s", file);
continue;
}
rep = JsonSerialization.readValue(StringPropertyReplacer.replaceProperties(
new String(Files.readAllBytes(filePath), "UTF-8"), new StringPropertyReplacer.PropertyResolver() {
@Override
public String resolve(String property) {
return Optional.ofNullable(System.getenv(property)).orElse(null);
}
}), RealmRepresentation.class);
} catch (Exception cause) {
throw new RuntimeException("Failed to parse realm configuration file: " + file, cause);
}
importRealm(rep, "file " + file);
}
@ -354,13 +370,4 @@ public class KeycloakApplication extends Application {
}
}
}
private static <T> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
} catch (IOException e) {
throw new RuntimeException("Failed to parse json", e);
}
}
}