[KEYCLOAK-11679] - Server startup on Quarkus

This commit is contained in:
Pedro Igor 2020-05-21 14:04:31 -03:00
parent 7deb89caab
commit f15821fe69
13 changed files with 886 additions and 408 deletions

View file

@ -1,12 +1,27 @@
package org.keycloak.quarkus.deployment;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.spi.PersistenceUnitTransactionType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.keycloak.Config;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.DelegatingDialect;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import org.keycloak.runtime.KeycloakRecorder;
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
@ -15,7 +30,6 @@ import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import org.keycloak.runtime.KeycloakRecorder;
class KeycloakProcessor {
@ -33,6 +47,7 @@ class KeycloakProcessor {
ParsedPersistenceXmlDescriptor unit = descriptors.get(0).getDescriptor();
unit.setTransactionType(PersistenceUnitTransactionType.JTA);
unit.getProperties().setProperty(AvailableSettings.DIALECT, DelegatingDialect.class.getName());
unit.getProperties().setProperty(AvailableSettings.QUERY_STARTUP_CHECKING, Boolean.FALSE.toString());
}
@Record(ExecutionTime.STATIC_INIT)
@ -40,4 +55,47 @@ class KeycloakProcessor {
void configureDataSource(KeycloakRecorder recorder, BuildProducer<BeanContainerListenerBuildItem> container) {
container.produce(new BeanContainerListenerBuildItem(recorder.configureDataSource()));
}
/**
* <p>Load the built-in provider factories during build time so we don't spend time looking up them at runtime.
*
* <p>User-defined providers are going to be loaded at startup</p>
*/
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void configureBuiltInProviders(KeycloakRecorder recorder, BuildProducer<BeanContainerListenerBuildItem> container) {
container.produce(new BeanContainerListenerBuildItem(recorder.configSessionFactory(loadBuiltInFactories())));
}
private Map<Spi, Set<Class<? extends ProviderFactory>>> loadBuiltInFactories() {
ProviderManager pm = new ProviderManager(
KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
Map<Spi, Set<Class<? extends ProviderFactory>>> result = new HashMap<>();
for (Spi spi : pm.loadSpis()) {
List<ProviderFactory> loaded = pm.load(spi);
if (loaded.isEmpty()) {
continue;
}
Set<Class<? extends ProviderFactory>> factories = new HashSet<>();
for (ProviderFactory factory : loaded) {
if (Arrays.asList(
JBossJtaTransactionManagerLookup.class,
DefaultJpaConnectionProviderFactory.class,
DefaultLiquibaseConnectionProvider.class,
LiquibaseJpaUpdaterProviderFactory.class).contains(factory.getClass())) {
continue;
}
factories.add(factory.getClass());
}
result.put(spi, factories);
}
return result;
}
}

View file

@ -9,7 +9,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
public class TestStartup {
public class StartupTest {
@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()

View file

@ -31,7 +31,7 @@
<packaging>pom</packaging>
<properties>
<quarkus.version>999-SNAPSHOT</quarkus.version>
<quarkus.version>1.5.0.CR1</quarkus.version>
<resteasy.version>4.5.3.Final</resteasy.version>
<jackson.version>2.10.2</jackson.version>
<jackson.databind.version>${jackson.version}</jackson.databind.version>

View file

@ -1,402 +1,57 @@
package org.keycloak;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;
import javax.ws.rs.ApplicationPath;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.core.type.TypeReference;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Resteasy;
import org.keycloak.config.ConfigProviderFactory;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
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.KeycloakModelUtils;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.platform.Platform;
import org.keycloak.platform.PlatformProvider;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.error.KeycloakErrorHandler;
import org.keycloak.services.filters.KeycloakSecurityHeadersFilter;
import org.keycloak.services.filters.KeycloakTransactionCommitter;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.services.resources.JsResource;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.RobotsResource;
import org.keycloak.services.resources.ThemeResource;
import org.keycloak.services.resources.QuarkusWelcomeResource;
import org.keycloak.services.resources.WelcomeResource;
import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.scheduled.ClearExpiredClientInitialAccessTokens;
import org.keycloak.services.scheduled.ClearExpiredEvents;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
import org.keycloak.services.scheduled.ScheduledTaskRunner;
import org.keycloak.services.util.ObjectMapperResolver;
import org.keycloak.timer.TimerProvider;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.util.JsonSerialization;
@ApplicationPath("/")
public class QuarkusKeycloakApplication extends KeycloakApplication {
private static final Logger logger = Logger.getLogger(KeycloakApplication.class);
protected final PlatformProvider platform = Platform.getPlatform();
protected Set<Object> singletons = new HashSet<Object>();
protected Set<Class<?>> classes = new HashSet<Class<?>>();
protected KeycloakSessionFactory sessionFactory;
public QuarkusKeycloakApplication() {
try {
logger.debugv("PlatformProvider: {0}", platform.getClass().getName());
logger.debugv("RestEasy provider: {0}", Resteasy.getProvider().getClass().getName());
loadConfig();
Resteasy.pushDefaultContextObject(KeycloakApplication.class, this);
Resteasy.pushContext(KeycloakApplication.class, this); // for injection
singletons.add(new RobotsResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
classes.add(ThemeResource.class);
classes.add(JsResource.class);
classes.add(KeycloakSecurityHeadersFilter.class);
classes.add(KeycloakTransactionCommitter.class);
classes.add(KeycloakErrorHandler.class);
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
singletons.add(new WelcomeResource());
platform.onStartup(this::startup);
platform.onShutdown(this::shutdown);
} catch (Throwable t) {
platform.exit(t);
}
}
protected void startup() {
this.sessionFactory = createSessionFactory();
ExportImportManager[] exportImportManager = new ExportImportManager[1];
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession lockSession) {
DBLockManager dbLockManager = new DBLockManager(lockSession);
dbLockManager.checkForcedUnlock();
DBLockProvider dbLock = dbLockManager.getDBLock();
dbLock.waitForLock(DBLockProvider.Namespace.KEYCLOAK_BOOT);
try {
exportImportManager[0] = migrateAndBootstrap();
} finally {
dbLock.releaseLock();
}
}
});
if (exportImportManager[0].isRunExport()) {
exportImportManager[0].runExport();
}
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
boolean shouldBootstrapAdmin = new ApplianceBootstrap(session).isNoMasterUser();
BOOTSTRAP_ADMIN_USER.set(shouldBootstrapAdmin);
}
});
sessionFactory.publish(new PostMigrationEvent());
setupScheduledTasks(sessionFactory);
}
protected void shutdown() {
if (sessionFactory != null)
sessionFactory.close();
}
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
protected ExportImportManager migrateAndBootstrap() {
ExportImportManager exportImportManager;
logger.debug("Calling migrateModel");
migrateModel();
logger.debug("bootstrap");
KeycloakSession session = sessionFactory.create();
try {
session.getTransactionManager().begin();
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
if (lookup != null) {
if (lookup.getTransactionManager() != null) {
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);
exportImportManager = new ExportImportManager(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();
} else {
importRealms();
}
importAddUser();
return exportImportManager;
}
protected void migrateModel() {
KeycloakSession session = sessionFactory.create();
try {
session.getTransactionManager().begin();
MigrationModelManager.migrate(session);
session.getTransactionManager().commit();
} catch (Exception e) {
session.getTransactionManager().rollback();
throw e;
} finally {
session.close();
}
}
protected void loadConfig() {
ServiceLoader<ConfigProviderFactory> loader = ServiceLoader.load(ConfigProviderFactory.class, KeycloakApplication.class.getClassLoader());
try {
ConfigProviderFactory factory = loader.iterator().next();
logger.debugv("ConfigProvider: {0}", factory.getClass().getName());
Config.init(factory.create().orElseThrow(() -> new RuntimeException("Failed to load Keycloak configuration")));
} catch (NoSuchElementException e) {
throw new RuntimeException("No valid ConfigProvider found");
}
}
public static KeycloakSessionFactory createSessionFactory() {
DefaultKeycloakSessionFactory factory = new DefaultKeycloakSessionFactory();
factory.init();
return factory;
}
public static void setupScheduledTasks(final KeycloakSessionFactory sessionFactory) {
long interval = Config.scope("scheduled").getLong("interval", 900L) * 1000;
KeycloakSession session = sessionFactory.create();
try {
TimerProvider timer = session.getProvider(TimerProvider.class);
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, ClearExpiredUserSessions.TASK_NAME);
new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
} finally {
session.close();
}
}
public KeycloakSessionFactory getSessionFactory() {
return sessionFactory;
}
@Inject
Instance<EntityManagerFactory> entityManagerFactory;
@Override
public Set<Class<?>> getClasses() {
return classes;
protected void startup() {
forceEntityManagerInitialization();
initializeKeycloakSessionFactory();
setupScheduledTasks(sessionFactory);
}
@Override
public Set<Object> getSingletons() {
HashSet<Object> singletons = new HashSet<>(super.getSingletons().stream().filter(new Predicate<Object>() {
@Override
public boolean test(Object o) {
return !WelcomeResource.class.isInstance(o);
}
}).collect(Collectors.toSet()));
singletons.add(new QuarkusWelcomeResource());
return singletons;
}
public 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 {
rep = loadJson(new FileInputStream(file), RealmRepresentation.class);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
importRealm(rep, "file " + file);
}
}
private void initializeKeycloakSessionFactory() {
QuarkusKeycloakSessionFactory instance = QuarkusKeycloakSessionFactory.getInstance();
sessionFactory = instance;
instance.init();
sessionFactory.publish(new PostMigrationEvent());
}
public void importRealm(RealmRepresentation rep, String from) {
KeycloakSession session = sessionFactory.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 (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 t) {
session.getTransactionManager().rollback();
if (!exists) {
ServicesLogger.LOGGER.unableToImportRealm(t, rep.getRealm(), from);
}
}
} finally {
session.close();
}
}
public 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 = sessionFactory.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(userRep.getUsername(), realm) != 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 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);
}
private void forceEntityManagerInitialization() {
// also forces an initialization of the entity manager so that providers don't need to wait for any initialization logic
// when first creating an entity manager
entityManagerFactory.get().createEntityManager().close();
}
}

View file

@ -0,0 +1,163 @@
package org.keycloak;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import org.jboss.logging.Logger;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderLoader;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionFactory {
private static final Logger logger = Logger.getLogger(QuarkusKeycloakSessionFactory.class);
public static QuarkusKeycloakSessionFactory getInstance() {
if (INSTANCE == null) {
INSTANCE = new QuarkusKeycloakSessionFactory();
}
return INSTANCE;
}
public static void setInstance(QuarkusKeycloakSessionFactory instance) {
INSTANCE = instance;
}
private static QuarkusKeycloakSessionFactory INSTANCE;
private Map<Spi, Set<Class<? extends ProviderFactory>>> factories;
public QuarkusKeycloakSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories) {
this.factories = factories;
}
private QuarkusKeycloakSessionFactory() {
}
@Override
public void init() {
spis = factories.keySet();
serverStartupTimestamp = System.currentTimeMillis();
ProviderLoader userProviderLoader = createUserProviderLoader();
for (Spi spi : factories.keySet()) {
loadUserProviders(spi, userProviderLoader);
for (Class<? extends ProviderFactory> factoryClazz : factories.get(spi)) {
ProviderFactory factory = lookupProviderFactory(factoryClazz);
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
if (isEnabled(factory, scope)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
ServicesLogger.LOGGER.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName());
}
factoriesMap.computeIfAbsent(spi.getProviderClass(), aClass -> new HashMap<>()).put(factory.getId(),
factory);
} else {
logger.debugv("SPI {0} provider {1} disabled", spi.getName(), factory.getId());
}
}
checkProviders(spi);
}
for (Map<String, ProviderFactory> f : factoriesMap.values()) {
for (ProviderFactory factory : f.values()) {
factory.postInit(this);
}
}
AdminPermissions.registerListener(this);
}
@Override
public void deploy(ProviderManager pm) {
throw new RuntimeException("Not supported");
}
@Override
public void undeploy(ProviderManager pm) {
throw new RuntimeException("Not supported");
}
private ProviderLoader createUserProviderLoader() {
return UserProviderLoader
.create(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader());
}
private ProviderFactory lookupProviderFactory(Class<? extends ProviderFactory> factoryClazz) {
ProviderFactory factory;
try {
factory = factoryClazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
return factory;
}
private void checkProviders(Spi spi) {
String defaultProvider = Config.getProvider(spi.getName());
if (defaultProvider != null) {
if (getProviderFactory(spi.getProviderClass(), defaultProvider) == null) {
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
}
} else {
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
if (factories != null && factories.size() == 1) {
defaultProvider = factories.values().iterator().next().getId();
}
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) {
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());
}
}
private void loadUserProviders(Spi spi, ProviderLoader loader) {
//TODO: support loading providers from CDI. We should probably consider writing providers using CDI for Quarkus, much easier
// to develop and integrate with
List<ProviderFactory> load = loader.load(spi);
for (ProviderFactory factory : load) {
factories.computeIfAbsent(spi, new Function<Spi, Set<Class<? extends ProviderFactory>>>() {
@Override
public Set<Class<? extends ProviderFactory>> apply(Spi spi) {
return new HashSet<>();
}
}).add(factory.getClass());
}
}
}

View file

@ -0,0 +1,82 @@
package org.keycloak;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.provider.DefaultProviderLoader;
import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderLoader;
class UserProviderLoader {
private static final Logger logger = Logger.getLogger(UserProviderLoader.class);
static ProviderLoader create(KeycloakDeploymentInfo info, ClassLoader parentClassLoader) {
return new DefaultProviderLoader(info, createClassLoader(parentClassLoader));
}
private static ClassLoader createClassLoader(ClassLoader parent) {
String homeDir = System.getProperty("keycloak.home.dir");
if (homeDir == null) {
return parent;
}
try {
List<URL> urls = new LinkedList<URL>();
File dir = new File(homeDir + File.separator + "providers");
if (dir.isDirectory()) {
for (File file : dir.listFiles(new JarFilter())) {
urls.add(file.toURI().toURL());
}
}
logger.debug("Loading providers from " + urls.toString());
return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent) {
@Override
public InputStream getResourceAsStream(String name) {
return super.getResourceAsStream(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL> resources = findResources(name);
List<URL> result = new ArrayList<>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
if (url.toString().contains(dir.getAbsolutePath())) {
result.add(url);
}
}
return Collections.enumeration(result);
}
};
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static class JarFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".jar");
}
}
}

View file

@ -20,20 +20,27 @@ package org.keycloak.connections.jpa;
import static org.keycloak.connections.liquibase.QuarkusJpaUpdaterProvider.VERIFY_AND_RUN_MASTER_CHANGELOG;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.SynchronizationType;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import com.fasterxml.jackson.core.type.TypeReference;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.internal.SessionImpl;
import org.jboss.logging.Logger;
@ -41,14 +48,28 @@ import org.keycloak.Config;
import org.keycloak.ServerStartupError;
import org.keycloak.common.Version;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
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.KeycloakSessionTask;
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.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
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.util.JsonSerialization;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -173,7 +194,8 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
session.setAttribute(VERIFY_AND_RUN_MASTER_CHANGELOG, version == null || !version.equals(new ModelVersion(Version.VERSION_KEYCLOAK).toString()));
boolean requiresMigration = version == null || !version.equals(new ModelVersion(Version.VERSION_KEYCLOAK).toString());
session.setAttribute(VERIFY_AND_RUN_MASTER_CHANGELOG, requiresMigration);
JpaUpdaterProvider.Status status = updater.validate(connection, schema);
@ -206,6 +228,32 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
throw new ServerStartupError("Database not up-to-date, please enable database migration", false);
}
}
ExportImportManager exportImportManager = new ExportImportManager(session);
if (requiresMigration) {
KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
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();
}
}
protected void update(Connection connection, String schema, KeycloakSession session, JpaUpdaterProvider updater) {
@ -300,4 +348,178 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide
public int order() {
return 100;
}
protected ExportImportManager createMasterRealm(ExportImportManager exportImportManager) {
logger.debug("bootstrap");
KeycloakSession session = factory.create();
try {
session.getTransactionManager().begin();
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) factory
.getProviderFactory(JtaTransactionManagerLookup.class);
if (lookup != null) {
if (lookup.getTransactionManager() != null) {
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();
} else {
importRealms();
}
importAddUser();
return exportImportManager;
}
protected void migrateModel(KeycloakSession session) {
try {
MigrationModelManager.migrate(session);
} catch (Exception e) {
throw e;
}
}
public 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 {
rep = JsonSerialization.readValue(new FileInputStream(file), RealmRepresentation.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
importRealm(rep, "file " + file);
}
}
}
public 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 (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 t) {
session.getTransactionManager().rollback();
if (!exists) {
ServicesLogger.LOGGER.unableToImportRealm(t, rep.getRealm(), from);
}
}
} finally {
session.close();
}
}
public 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(userRep.getUsername(), realm) != 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());
}
}
}
}
}

View file

@ -75,6 +75,7 @@ public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionPr
protected void baseLiquibaseInitialization(KeycloakSession session) {
resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
FastServiceLocator locator = (FastServiceLocator) ServiceLocator.getInstance();
JpaConnectionProviderFactory jpaConnectionProvider = (JpaConnectionProviderFactory) session
.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);

View file

@ -49,7 +49,7 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro
configureTransportStack(config, builder);
}
return (C) new DefaultCacheManager(builder, true);
return (C) new DefaultCacheManager(builder, false);
} catch (Exception e) {
throw new RuntimeException(e);
}

View file

@ -2,14 +2,18 @@ package org.keycloak.runtime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.QuarkusKeycloakSessionFactory;
import org.keycloak.connections.liquibase.FastServiceLocator;
import org.keycloak.connections.liquibase.KeycloakLogger;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import io.quarkus.agroal.runtime.DataSourceSupport;
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.arc.runtime.BeanContainerListener;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import org.keycloak.connections.liquibase.FastServiceLocator;
import org.keycloak.connections.liquibase.KeycloakLogger;
import io.quarkus.runtime.annotations.Recorder;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigProviderResolver;
@ -57,4 +61,13 @@ public class KeycloakRecorder {
}
};
}
public BeanContainerListener configSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories) {
return new BeanContainerListener() {
@Override
public void created(BeanContainer container) {
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories));
}
};
}
}

View file

@ -0,0 +1,275 @@
/*
* Copyright 2016 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.services.resources;
import org.jboss.logging.Logger;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Version;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MimeTypeUtil;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@Path("/")
public class QuarkusWelcomeResource {
protected static final Logger logger = Logger.getLogger(WelcomeResource.class);
private static final String KEYCLOAK_STATE_CHECKER = "WELCOME_STATE_CHECKER";
private AtomicBoolean shouldBootstrap;
@Context
protected HttpHeaders headers;
@Context
private KeycloakSession session;
/**
* Welcome page of Keycloak
*
* @return
* @throws URISyntaxException
*/
@GET
@Produces(MediaType.TEXT_HTML_UTF_8)
public Response getWelcomePage() throws URISyntaxException {
String requestUri = session.getContext().getUri().getRequestUri().toString();
if (!requestUri.endsWith("/")) {
return Response.seeOther(new URI(requestUri + "/")).build();
} else {
return createWelcomePage(null, null);
}
}
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_HTML_UTF_8)
public Response createUser(final MultivaluedMap<String, String> formData) {
if (!shouldBootstrap()) {
return createWelcomePage(null, null);
} else {
if (!isLocal()) {
ServicesLogger.LOGGER.rejectedNonLocalAttemptToCreateInitialUser(session.getContext().getConnection().getRemoteAddr());
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
csrfCheck(formData);
String username = formData.getFirst("username");
String password = formData.getFirst("password");
String passwordConfirmation = formData.getFirst("passwordConfirmation");
if (username != null) {
username = username.trim();
}
if (username == null || username.length() == 0) {
return createWelcomePage(null, "Username is missing");
}
if (password == null || password.length() == 0) {
return createWelcomePage(null, "Password is missing");
}
if (!password.equals(passwordConfirmation)) {
return createWelcomePage(null, "Password and confirmation doesn't match");
}
expireCsrfCookie();
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
applianceBootstrap.createMasterRealmUser(username, password);
shouldBootstrap.set(false);
ServicesLogger.LOGGER.createdInitialAdminUser(username);
return createWelcomePage("User created", null);
}
}
/**
* Resources for welcome page
*
* @param path
* @return
*/
@GET
@Path("/welcome-content/{path}")
@Produces(MediaType.TEXT_HTML_UTF_8)
public Response getResource(@PathParam("path") String path) {
try {
InputStream resource = getTheme().getResourceAsStream(path);
if (resource != null) {
String contentType = MimeTypeUtil.getContentType(path);
Response.ResponseBuilder builder = Response.ok(resource).type(contentType).cacheControl(CacheControlUtil.getDefaultCacheControl());
return builder.build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
} catch (IOException e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
private Response createWelcomePage(String successMessage, String errorMessage) {
try {
Theme theme = getTheme();
Map<String, Object> map = new HashMap<>();
map.put("productName", Version.NAME);
map.put("productNameFull", Version.NAME_FULL);
map.put("properties", theme.getProperties());
map.put("adminUrl", session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path("/admin/").build());
map.put("resourcesPath", "resources/" + Version.RESOURCES_VERSION + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName());
map.put("resourcesCommonPath", "resources/" + Version.RESOURCES_VERSION + "/common/keycloak");
boolean bootstrap = shouldBootstrap();
map.put("bootstrap", bootstrap);
if (bootstrap) {
boolean isLocal = isLocal();
map.put("localUser", isLocal);
if (isLocal) {
String stateChecker = setCsrfCookie();
map.put("stateChecker", stateChecker);
}
}
if (successMessage != null) {
map.put("successMessage", successMessage);
}
if (errorMessage != null) {
map.put("errorMessage", errorMessage);
}
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
ResponseBuilder rb = Response.status(errorMessage == null ? Status.OK : Status.BAD_REQUEST)
.entity(result)
.cacheControl(CacheControlUtil.noCache());
return rb.build();
} catch (Exception e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
private Theme getTheme() {
try {
return session.theme().getTheme(Theme.Type.WELCOME);
} catch (IOException e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
private boolean shouldBootstrap() {
if (shouldBootstrap == null) {
synchronized (this) {
if (shouldBootstrap == null) {
shouldBootstrap = new AtomicBoolean(new ApplianceBootstrap(session).isNoMasterUser());
}
}
}
return shouldBootstrap.get();
}
private boolean isLocal() {
try {
ClientConnection clientConnection = session.getContext().getConnection();
InetAddress remoteInetAddress = InetAddress.getByName(clientConnection.getRemoteAddr());
InetAddress localInetAddress = InetAddress.getByName(clientConnection.getLocalAddr());
String xForwardedFor = headers.getHeaderString("X-Forwarded-For");
logger.debugf("Checking WelcomePage. Remote address: %s, Local address: %s, X-Forwarded-For header: %s", remoteInetAddress.toString(), localInetAddress.toString(), xForwardedFor);
// Access through AJP protocol (loadbalancer) may cause that remoteAddress is "127.0.0.1".
// So consider that welcome page accessed locally just if it was accessed really through "localhost" URL and without loadbalancer (x-forwarded-for header is empty).
return isLocalAddress(remoteInetAddress) && isLocalAddress(localInetAddress) && xForwardedFor == null;
} catch (UnknownHostException e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
private boolean isLocalAddress(InetAddress inetAddress) {
return inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress();
}
private String setCsrfCookie() {
String stateChecker = Base64Url.encode(KeycloakModelUtils.generateSecret());
String cookiePath = session.getContext().getUri().getPath();
boolean secureOnly = session.getContext().getUri().getRequestUri().getScheme().equalsIgnoreCase("https");
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, stateChecker, cookiePath, null, null, 300, secureOnly, true);
return stateChecker;
}
private void expireCsrfCookie() {
String cookiePath = session.getContext().getUri().getPath();
boolean secureOnly = session.getContext().getUri().getRequestUri().getScheme().equalsIgnoreCase("https");
CookieHelper.addCookie(KEYCLOAK_STATE_CHECKER, "", cookiePath, null, null, 0, secureOnly, true);
}
private void csrfCheck(final MultivaluedMap<String, String> formData) {
String formStateChecker = formData.getFirst("stateChecker");
Cookie cookie = headers.getCookies().get(KEYCLOAK_STATE_CHECKER);
if (cookie == null) {
throw new ForbiddenException();
}
String cookieStateChecker = cookie.getValue();
if (cookieStateChecker == null || !cookieStateChecker.equals(formStateChecker)) {
throw new ForbiddenException();
}
}
}

View file

@ -18,30 +18,36 @@
package org.keycloak.transaction;
import javax.enterprise.inject.spi.CDI;
import javax.transaction.TransactionManager;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSessionFactory;
import javax.transaction.TransactionManager;
public class QuarkusJtaTransactionManagerLookup implements JtaTransactionManagerLookup {
private static final Logger logger = Logger.getLogger(QuarkusJtaTransactionManagerLookup.class);
private TransactionManager tm;
private volatile TransactionManager tm;
@Override
public TransactionManager getTransactionManager() {
if (tm == null) {
synchronized (this) {
if (tm == null) {
tm = CDI.current().select(TransactionManager.class).get();
logger.tracev("TransactionManager = {0}", tm);
if (tm == null) {
logger.debug("Could not locate JTA TransactionManager. JTA transactions not supported.");
}
}
}
}
return tm;
}
@Override
public void init(Config.Scope config) {
tm = CDI.current().select(TransactionManager.class).get();
logger.tracev("TransactionManager = {0}", tm);
if (tm == null) {
logger.debug("Could not locate TransactionManager");
}
}
@Override

View file

@ -34,6 +34,7 @@ import org.keycloak.provider.Spi;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.theme.DefaultThemeManagerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
@ -48,12 +49,12 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
private static final Logger logger = Logger.getLogger(DefaultKeycloakSessionFactory.class);
private Set<Spi> spis = new HashSet<>();
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
protected Set<Spi> spis = new HashSet<>();
protected Map<Class<? extends Provider>, String> provider = new HashMap<>();
protected volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
private DefaultThemeManagerFactory themeManagerFactory;
private final DefaultThemeManagerFactory themeManagerFactory = new DefaultThemeManagerFactory();
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
protected long serverStartupTimestamp;
@ -105,8 +106,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
AdminPermissions.registerListener(this);
themeManagerFactory = new DefaultThemeManagerFactory();
}
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
@ -265,7 +264,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
return factoryMap;
}
private boolean isEnabled(ProviderFactory factory, Config.Scope scope) {
protected boolean isEnabled(ProviderFactory factory, Config.Scope scope) {
if (!scope.getBoolean("enabled", true)) {
return false;
}
@ -309,8 +308,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
@Override
public List<ProviderFactory> getProviderFactories(Class<? extends Provider> clazz) {
if (factoriesMap == null) return Collections.emptyList();
List<ProviderFactory> list = new LinkedList<ProviderFactory>();
if (factoriesMap == null) return list;
Map<String, ProviderFactory> providerFactoryMap = factoriesMap.get(clazz);
if (providerFactoryMap == null) return list;
list.addAll(providerFactoryMap.values());
@ -318,8 +317,12 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
<T extends Provider> Set<String> getAllProviderIds(Class<T> clazz) {
Set<String> ids = new HashSet<String>();
for (ProviderFactory f : factoriesMap.get(clazz).values()) {
Map<String, ProviderFactory> factoryMap = factoriesMap.get(clazz);
if (factoryMap == null) {
return Collections.emptySet();
}
Set<String> ids = new HashSet<>();
for (ProviderFactory f : factoryMap.values()) {
ids.add(f.getId());
}
return ids;
@ -343,7 +346,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
}
private boolean isInternal(ProviderFactory<?> factory) {
protected boolean isInternal(ProviderFactory<?> factory) {
String packageName = factory.getClass().getPackage().getName();
return packageName.startsWith("org.keycloak") && !packageName.startsWith("org.keycloak.examples");
}