[KEYCLOAK-13141] - Supporting re-augmentation
This commit is contained in:
parent
48e4432e9d
commit
1db1deb066
35 changed files with 321 additions and 1243 deletions
6
.github/workflows/ci-x.yml
vendored
6
.github/workflows/ci-x.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 1.8
|
||||
- name: Cache Maven packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
@ -38,7 +38,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 1.8
|
||||
- name: Download Maven Repo
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
|
@ -61,7 +61,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 1.8
|
||||
- name: Download Maven Repo
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
<filter>
|
||||
<filter-name>Client Connection Filter</filter-name>
|
||||
<filter-class>org.keycloak.provider.wildfly.WildFlyClientConnectionServletFilter</filter-class>
|
||||
<filter-class>org.keycloak.provider.wildfly.WildFlyRequestFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
|
||||
|
|
|
@ -69,10 +69,10 @@
|
|||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>../../quarkus/server/target</directory>
|
||||
<outputDirectory>lib/</outputDirectory>
|
||||
<directory>../../quarkus/server/target/lib</directory>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>keycloak-runner.jar</include>
|
||||
<include>**/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
|
|
|
@ -28,6 +28,8 @@ SERVER_OPTS="-Dkeycloak.home.dir=$DIRNAME/../ -Djboss.server.config.dir=$DIRNAME
|
|||
DEBUG_MODE="${DEBUG:-false}"
|
||||
DEBUG_PORT="${DEBUG_PORT:-8787}"
|
||||
|
||||
IS_CONFIGURE="false"
|
||||
|
||||
while [ "$#" -gt 0 ]
|
||||
do
|
||||
case "$1" in
|
||||
|
@ -38,10 +40,13 @@ do
|
|||
shift
|
||||
fi
|
||||
;;
|
||||
--config)
|
||||
--config-file)
|
||||
SERVER_OPTS="$SERVER_OPTS -Dkeycloak.config.file=$2"
|
||||
shift
|
||||
;;
|
||||
config)
|
||||
IS_CONFIGURE=true
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break;;
|
||||
|
@ -71,6 +76,11 @@ if [ "$DEBUG_MODE" = "true" ]; then
|
|||
fi
|
||||
fi
|
||||
|
||||
CLASSPATH_OPTS="$DIRNAME/../providers/*:$DIRNAME/../lib/keycloak-runner.jar"
|
||||
CLASSPATH_OPTS="$DIRNAME/../lib/quarkus-run.jar:$DIRNAME/../lib/main/*"
|
||||
|
||||
exec java $JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.runner.GeneratedMain "$@"
|
||||
if [ "$IS_CONFIGURE" = true ] ; then
|
||||
echo "Updating the configuration and installing your custom providers, if any. Please wait."
|
||||
exec java -Dquarkus.launch.rebuild=true $JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint "$@"
|
||||
else
|
||||
exec java $JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint "$@"
|
||||
fi
|
|
@ -1,5 +1,5 @@
|
|||
# Datasource
|
||||
datasource.dialect=org.hibernate.dialect.H2Dialect
|
||||
# Default Non-Production Grade Datasource
|
||||
hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
|
||||
datasource.driver=org.h2.jdbcx.JdbcDataSource
|
||||
datasource.url = jdbc:h2:file:${keycloak.home.dir}/data/keycloakdb;;AUTO_SERVER=TRUE
|
||||
datasource.username = sa
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# Installing Custom Providers
|
||||
|
||||
You should add to this directory your custom provider JAR files.
|
||||
|
||||
Once you have your providers in this directory you should run the following command to complete the installation:
|
||||
|
||||
```
|
||||
${keycloak.home.dir}/bin/kc.sh config
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
Copy custom providers into this directory then restart the Keycloak server.
|
|
@ -24,6 +24,11 @@
|
|||
<artifactId>quarkus-arc-deployment</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-vertx-web-deployment</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-quarkus-server</artifactId>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2020 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.quarkus.deployment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
public class BuildClassLoader extends URLClassLoader {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BuildClassLoader.class);
|
||||
|
||||
public BuildClassLoader() {
|
||||
super(new URL[] {}, Thread.currentThread().getContextClassLoader());
|
||||
String homeDir = System.getProperty("keycloak.home.dir");
|
||||
|
||||
if (homeDir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
File providersDir = new File(homeDir + File.separator + "providers");
|
||||
|
||||
if (providersDir.isDirectory()) {
|
||||
for (File file : providersDir.listFiles(new JarFilter())) {
|
||||
try {
|
||||
addURL(file.toURI().toURL());
|
||||
logger.debug("Loading providers from " + file.getAbsolutePath());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException("Failed to add provider JAR at " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JarFilter implements FilenameFilter {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.toLowerCase().endsWith(".jar");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.quarkus.deployment;
|
||||
|
||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -7,24 +8,21 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||
|
||||
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
|
||||
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.provider.quarkus.QuarkusClientConnectionFilter;
|
||||
import org.keycloak.provider.quarkus.QuarkusRequestFilter;
|
||||
import org.keycloak.runtime.KeycloakRecorder;
|
||||
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
||||
|
||||
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
import io.quarkus.deployment.annotations.BuildStep;
|
||||
import io.quarkus.deployment.annotations.ExecutionTime;
|
||||
|
@ -32,6 +30,7 @@ import io.quarkus.deployment.annotations.Record;
|
|||
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
|
||||
import io.quarkus.vertx.http.deployment.FilterBuildItem;
|
||||
import org.keycloak.util.Environment;
|
||||
|
||||
class KeycloakProcessor {
|
||||
|
||||
|
@ -40,41 +39,50 @@ class KeycloakProcessor {
|
|||
return new FeatureBuildItem("keycloak");
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
void configureHibernate(KeycloakRecorder recorder, List<PersistenceUnitDescriptorBuildItem> descriptors) {
|
||||
// TODO: ORM extension is going to provide build items that we can rely on to create our own PU instead of relying
|
||||
// on the parsed descriptor and assume that the order that build steps are executed is always the same (although dialect
|
||||
// is only created during runtime)
|
||||
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)
|
||||
@BuildStep
|
||||
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>Configures the persistence unit for Quarkus.
|
||||
*
|
||||
* <p>
|
||||
* User-defined providers are going to be loaded at startup
|
||||
* </p>
|
||||
* <p>The main reason we have this build step is because we re-use the same persistence unit from {@code keycloak-model-jpa}
|
||||
* module, the same used by the Wildfly distribution. The {@code hibernate-orm} extension expects that the dialect is statically
|
||||
* set to the persistence unit if there is any from the classpath and use this method to obtain the dialect from the configuration
|
||||
* file so that we can re-augment the application with whatever dialect we want. In addition to the dialect, we should also be
|
||||
* allowed to set any additional defaults that we think that makes sense.
|
||||
*
|
||||
* @param recorder
|
||||
* @param config
|
||||
* @param descriptors
|
||||
*/
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
void configureBuiltInProviders(KeycloakRecorder recorder) {
|
||||
recorder.configSessionFactory(loadBuiltInFactories());
|
||||
void configureHibernate(KeycloakRecorder recorder, HibernateOrmConfig config, List<PersistenceUnitDescriptorBuildItem> descriptors) {
|
||||
PersistenceUnitDescriptor unit = descriptors.get(0).asOutputPersistenceUnitDefinition().getActualHibernateDescriptor();
|
||||
|
||||
unit.getProperties().setProperty(AvailableSettings.DIALECT, config.dialect.get());
|
||||
unit.getProperties().setProperty(AvailableSettings.JPA_TRANSACTION_TYPE, PersistenceUnitTransactionType.JTA.name());
|
||||
unit.getProperties().setProperty(AvailableSettings.QUERY_STARTUP_CHECKING, Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
private Map<Spi, Set<Class<? extends ProviderFactory>>> loadBuiltInFactories() {
|
||||
/**
|
||||
* <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>
|
||||
*
|
||||
* @param recorder
|
||||
*/
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
void configureProviders(KeycloakRecorder recorder) {
|
||||
recorder.configSessionFactory(loadFactories(), Environment.isRebuild());
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void initializeRouter(BuildProducer<FilterBuildItem> routes) {
|
||||
routes.produce(new FilterBuildItem(new QuarkusRequestFilter(), FilterBuildItem.AUTHORIZATION - 10));
|
||||
}
|
||||
|
||||
private Map<Spi, Set<Class<? extends ProviderFactory>>> loadFactories() {
|
||||
ProviderManager pm = new ProviderManager(
|
||||
KeycloakDeploymentInfo.create().services(), Thread.currentThread().getContextClassLoader(),
|
||||
KeycloakDeploymentInfo.create().services(), new BuildClassLoader(),
|
||||
Config.scope().getArray("providers"));
|
||||
Map<Spi, Set<Class<? extends ProviderFactory>>> result = new HashMap<>();
|
||||
|
||||
|
@ -98,9 +106,4 @@ class KeycloakProcessor {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
@BuildStep
|
||||
void initializeRouter(BuildProducer<FilterBuildItem> routes) {
|
||||
routes.produce(new FilterBuildItem(new QuarkusClientConnectionFilter(), FilterBuildItem.AUTHORIZATION - 10));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
quarkus.log.level = INFO
|
||||
|
||||
quarkus.http.root-path=/auth
|
||||
quarkus.application.name=Keycloak
|
||||
resteasy.disable.html.sanitizer = true
|
||||
quarkus.banner.enabled=false
|
|
@ -1,7 +1,7 @@
|
|||
hostname.default.frontendUrl = ${keycloak.frontendUrl:}
|
||||
|
||||
# Datasource
|
||||
datasource.dialect=org.hibernate.dialect.H2Dialect
|
||||
hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
|
||||
datasource.jdbc.transactions=xa
|
||||
datasource.driver=org.h2.jdbcx.JdbcDataSource
|
||||
datasource.url = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.version>1.6.0.CR1</quarkus.version>
|
||||
<quarkus.version>999-SNAPSHOT</quarkus.version>
|
||||
<resteasy.version>4.5.5.Final</resteasy.version>
|
||||
<jackson.version>2.10.4</jackson.version>
|
||||
<jackson.databind.version>${jackson.version}</jackson.databind.version>
|
||||
|
@ -137,5 +137,33 @@
|
|||
<module>runtime</module>
|
||||
<module>server</module>
|
||||
</modules>
|
||||
|
||||
<!-- Temporary definition while Quarkus 1.7.0.Final is not released -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dependency-snapshots-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<!-- Temporary definition while Quarkus 1.7.0.Final is not released -->
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>dependency-snapshots-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -13,7 +14,6 @@ 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.ProviderManagerRegistry;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||
|
@ -37,23 +37,25 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
|
|||
}
|
||||
|
||||
private static QuarkusKeycloakSessionFactory INSTANCE;
|
||||
private Map<Spi, Set<Class<? extends ProviderFactory>>> factories;
|
||||
private final Boolean reaugmented;
|
||||
private final Map<Spi, Set<Class<? extends ProviderFactory>>> factories;
|
||||
|
||||
public QuarkusKeycloakSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories) {
|
||||
public QuarkusKeycloakSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories, Boolean reaugmented) {
|
||||
this.factories = factories;
|
||||
this.reaugmented = reaugmented;
|
||||
}
|
||||
|
||||
private QuarkusKeycloakSessionFactory() {
|
||||
reaugmented = false;
|
||||
factories = Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
serverStartupTimestamp = System.currentTimeMillis();
|
||||
ProviderLoader userProviderLoader = createUserProviderLoader();
|
||||
spis = loadRuntimeSpis(userProviderLoader);
|
||||
spis = factories.keySet();
|
||||
|
||||
for (Spi spi : spis) {
|
||||
loadUserProviders(spi, userProviderLoader);
|
||||
for (Class<? extends ProviderFactory> factoryClazz : factories.get(spi)) {
|
||||
ProviderFactory factory = lookupProviderFactory(factoryClazz);
|
||||
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
|
||||
|
@ -86,27 +88,6 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
|
|||
ProviderManagerRegistry.SINGLETON.setDeployer(this);
|
||||
}
|
||||
|
||||
private Set<Spi> loadRuntimeSpis(ProviderLoader runtimeLoader) {
|
||||
// most of the time SPIs loaded at build time are enough but under certain circumstances (e.g.: testsuite) we may
|
||||
// want to load additional SPIs at runtime only from the JARs deployed at the providers dir
|
||||
List<Spi> loaded = runtimeLoader.loadSpis();
|
||||
|
||||
if (loaded.isEmpty()) {
|
||||
return factories.keySet();
|
||||
}
|
||||
|
||||
Set<Spi> spis = new HashSet<>(factories.keySet());
|
||||
|
||||
spis.addAll(loaded);
|
||||
|
||||
return spis;
|
||||
}
|
||||
|
||||
private ProviderLoader createUserProviderLoader() {
|
||||
return UserProviderLoader
|
||||
.create(KeycloakDeploymentInfo.create().services(), Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
private ProviderFactory lookupProviderFactory(Class<? extends ProviderFactory> factoryClazz) {
|
||||
ProviderFactory factory;
|
||||
|
||||
|
@ -154,19 +135,4 @@ public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionF
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
package org.keycloak;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
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) {
|
||||
// don't load resources from classpath
|
||||
return new ClassLoader() {
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
return Collections.emptyEnumeration();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -125,6 +125,13 @@ public abstract class KeycloakPropertiesConfigSource extends PropertiesConfigSou
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need a better namespace resolution so that we don't need to add Quarkus extensions manually. Maybe the easiest
|
||||
* path is to just have the "keycloak" namespace for Keycloak-specific properties.
|
||||
*
|
||||
* @param key the key to transform
|
||||
* @return the same key but prefixed with the namespace
|
||||
*/
|
||||
private static String transformKey(String key) {
|
||||
String namespace;
|
||||
String[] keyParts = key.split("\\.");
|
||||
|
@ -138,6 +145,7 @@ public abstract class KeycloakPropertiesConfigSource extends PropertiesConfigSou
|
|||
}
|
||||
|
||||
switch (extension) {
|
||||
case "hibernate-orm":
|
||||
case "datasource":
|
||||
case "http":
|
||||
case "log":
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
|
||||
package org.keycloak.provider.quarkus;
|
||||
|
||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
import org.keycloak.services.filters.AbstractClientConnectionFilter;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.filters.AbstractRequestFilter;
|
||||
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Handler;
|
||||
|
@ -35,8 +33,9 @@ import io.vertx.ext.web.RoutingContext;
|
|||
* <p>The filter itself runs in a event loop and should delegate to worker threads any blocking code (for now, all requests are handled
|
||||
* as blocking).
|
||||
*/
|
||||
public class QuarkusClientConnectionFilter extends AbstractClientConnectionFilter implements Handler<RoutingContext> {
|
||||
public class QuarkusRequestFilter extends AbstractRequestFilter implements Handler<RoutingContext> {
|
||||
|
||||
private static final String KEYCLOAK_SESSION_KEY = KeycloakSession.class.getName();
|
||||
private static final Handler<AsyncResult<Object>> EMPTY_RESULT = result -> {
|
||||
// we don't really care about the result because any exception thrown should be handled by the parent class
|
||||
};
|
||||
|
@ -49,6 +48,10 @@ public class QuarkusClientConnectionFilter extends AbstractClientConnectionFilte
|
|||
// in the event loop
|
||||
context.vertx().executeBlocking(promise -> filter(clientConnection, (session) -> {
|
||||
try {
|
||||
// we need to close the session before response is sent to the client, otherwise subsequent requests could
|
||||
// not get the latest state because the session from the previous request is still being closed
|
||||
// other methods from Vert.x to add a handler to the response works asynchronously
|
||||
context.response().headersEndHandler(event -> close(session));
|
||||
context.next();
|
||||
promise.complete();
|
||||
} catch (Exception cause) {
|
||||
|
@ -59,6 +62,11 @@ public class QuarkusClientConnectionFilter extends AbstractClientConnectionFilte
|
|||
}), EMPTY_RESULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAutoClose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private ClientConnection createClientConnection(HttpServerRequest request) {
|
||||
return new ClientConnection() {
|
||||
@Override
|
|
@ -29,10 +29,6 @@ public class KeycloakRecorder {
|
|||
CONFIG = (SmallRyeConfig) SmallRyeConfigProviderResolver.instance().getConfig();
|
||||
}
|
||||
|
||||
public static String getDatabaseDialect() {
|
||||
return CONFIG.getRawValue("quarkus.datasource.dialect");
|
||||
}
|
||||
|
||||
public void configureLiquibase(Map<String, List<String>> services) {
|
||||
LogFactory.setInstance(new LogFactory() {
|
||||
KeycloakLogger logger = new KeycloakLogger();
|
||||
|
@ -57,19 +53,7 @@ public class KeycloakRecorder {
|
|||
ServiceLocator.setInstance(new FastServiceLocator(services));
|
||||
}
|
||||
|
||||
public BeanContainerListener configureDataSource() {
|
||||
return new BeanContainerListener() {
|
||||
@Override
|
||||
public void created(BeanContainer container) {
|
||||
String driver = CONFIG.getRawValue("quarkus.datasource.driver");
|
||||
DataSourceSupport instance = container.instance(DataSourceSupport.class);
|
||||
DataSourceSupport.Entry entry = instance.entries.get(DataSourceUtil.DEFAULT_DATASOURCE_NAME);
|
||||
entry.resolvedDriverClass = driver;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void configSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories) {
|
||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories));
|
||||
public void configSessionFactory(Map<Spi, Set<Class<? extends ProviderFactory>>> factories, Boolean reaugmented) {
|
||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, reaugmented));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2020 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.util;
|
||||
|
||||
public final class Environment {
|
||||
|
||||
public static Boolean isRebuild() {
|
||||
return Boolean.valueOf(System.getProperty("quarkus.launch.rebuild"));
|
||||
}
|
||||
}
|
|
@ -63,7 +63,6 @@
|
|||
<artifactId>quarkus-maven-plugin</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
<configuration>
|
||||
<uberJar>true</uberJar>
|
||||
<finalName>keycloak</finalName>
|
||||
</configuration>
|
||||
<executions>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
hostname.default.frontendUrl = ${keycloak.frontendUrl:}
|
||||
|
||||
# Datasource
|
||||
datasource.dialect=org.hibernate.dialect.H2Dialect
|
||||
# Default Non-Production Grade Datasource
|
||||
hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
|
||||
datasource.driver=org.h2.jdbcx.JdbcDataSource
|
||||
datasource.url = jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
|
||||
datasource.url = jdbc:h2:file:${keycloak.home.dir:~}/data/keycloakdb;;AUTO_SERVER=TRUE
|
||||
datasource.username = sa
|
||||
datasource.password = keycloak
|
||||
datasource.jdbc.transactions=xa
|
|
@ -1,4 +1,9 @@
|
|||
#quarkus.log.level = DEBUG
|
||||
quarkus.package.output-name=keycloak
|
||||
quarkus.package.type=mutable-jar
|
||||
quarkus.package.output-directory=lib
|
||||
quarkus.package.user-providers-directory=../providers
|
||||
|
||||
quarkus.http.root-path=/auth
|
||||
quarkus.application.name=Keycloak
|
||||
quarkus.banner.enabled=false
|
|
@ -18,8 +18,6 @@ package org.keycloak.services.filters;
|
|||
*/
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
|
@ -29,9 +27,9 @@ import org.keycloak.models.KeycloakTransactionManager;
|
|||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
|
||||
public abstract class AbstractClientConnectionFilter {
|
||||
public abstract class AbstractRequestFilter {
|
||||
|
||||
public void filter(ClientConnection clientConnection, Consumer<KeycloakSession> next) {
|
||||
protected void filter(ClientConnection clientConnection, Consumer<KeycloakSession> next) {
|
||||
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
|
||||
|
@ -47,11 +45,13 @@ public abstract class AbstractClientConnectionFilter {
|
|||
tx.setRollbackOnly();
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
close(session);
|
||||
if (isAutoClose()) {
|
||||
close(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close(KeycloakSession session) {
|
||||
protected void close(KeycloakSession session) {
|
||||
KeycloakTransactionManager tx = session.getTransactionManager();
|
||||
if (tx.isActive()) {
|
||||
if (tx.getRollbackOnly()) {
|
||||
|
@ -63,4 +63,14 @@ public abstract class AbstractClientConnectionFilter {
|
|||
|
||||
session.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Indicates whether or not resources should be close as part of the execution of the {@link #filter(ClientConnection, Consumer)}
|
||||
* method.
|
||||
*
|
||||
* @return true if resources should be close automatically. Otherwise, false.
|
||||
*/
|
||||
protected boolean isAutoClose() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<project>
|
||||
|
||||
<target name="config">
|
||||
<bin-chmod/>
|
||||
<echo>Re-augmenting...</echo>
|
||||
<exec osfamily="unix" dir="${auth.server.home}/bin" executable="./kc.sh" failonerror="true">
|
||||
<arg value="config"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<macrodef name="bin-chmod">
|
||||
<sequential>
|
||||
<chmod perm="ug+x">
|
||||
<fileset dir="${auth.server.home}/bin">
|
||||
<include name="*.sh"/>
|
||||
</fileset>
|
||||
</chmod>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
</project>
|
|
@ -172,6 +172,26 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>ant-generate-default</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<target>
|
||||
<ant antfile="ant/configure.xml" target="config" >
|
||||
<property name="auth.server.home">${auth.server.home}</property>
|
||||
</ant>
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ hostname.default.frontendUrl = ${keycloak.frontendUrl:}
|
|||
datasource.jdbc.transactions=xa
|
||||
|
||||
# H2
|
||||
datasource.dialect=org.hibernate.dialect.H2Dialect
|
||||
hibernate-orm.dialect=org.hibernate.dialect.H2Dialect
|
||||
datasource.driver=org.h2.jdbcx.JdbcDataSource
|
||||
datasource.url = jdbc:h2:file:${keycloak.home.dir}/data/keycloakdb;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1
|
||||
datasource.username = sa
|
||||
|
|
|
@ -50,7 +50,7 @@ import org.keycloak.services.managers.ApplianceBootstrap;
|
|||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.testsuite.JsonConfigProviderFactory;
|
||||
import org.keycloak.testsuite.KeycloakServer;
|
||||
import org.keycloak.testsuite.UndertowClientConnectionServletFilter;
|
||||
import org.keycloak.testsuite.UndertowRequestFilter;
|
||||
import org.keycloak.testsuite.utils.tls.TLSUtils;
|
||||
import org.keycloak.testsuite.utils.undertow.UndertowDeployerHelper;
|
||||
import org.keycloak.testsuite.utils.undertow.UndertowWarClassLoader;
|
||||
|
@ -99,7 +99,7 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
|
|||
di.setDefaultServletConfig(new DefaultServletConfig(true));
|
||||
di.addWelcomePage("theme/keycloak/welcome/resources/index.html");
|
||||
|
||||
FilterInfo filter = Servlets.filter("SessionFilter", UndertowClientConnectionServletFilter.class);
|
||||
FilterInfo filter = Servlets.filter("SessionFilter", UndertowRequestFilter.class);
|
||||
di.addFilter(filter);
|
||||
di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST);
|
||||
filter.setAsyncSupported(true);
|
||||
|
|
|
@ -30,6 +30,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
|||
private HashMap<String, Object> keycloakConfigPropertyOverridesMap;
|
||||
private String profile;
|
||||
private String javaOpts;
|
||||
private boolean reaugmentBeforeStart;
|
||||
|
||||
@Override
|
||||
public void validate() throws ConfigurationException {
|
||||
|
@ -136,4 +137,12 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
|||
public String getJavaOpts() {
|
||||
return javaOpts;
|
||||
}
|
||||
|
||||
public boolean isReaugmentBeforeStart() {
|
||||
return reaugmentBeforeStart;
|
||||
}
|
||||
|
||||
public void setReaugmentBeforeStart(boolean reaugmentBeforeStart) {
|
||||
this.reaugmentBeforeStart = reaugmentBeforeStart;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,19 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
|||
FileUtils.deleteDirectory(configuration.getProvidersPath().resolve("data").toFile());
|
||||
}
|
||||
|
||||
if (configuration.isReaugmentBeforeStart()) {
|
||||
ProcessBuilder reaugment = new ProcessBuilder("./kc.sh", "config");
|
||||
|
||||
reaugment.directory(wrkDir).inheritIO();
|
||||
|
||||
try {
|
||||
log.infof("Re-building the server with the new configuration");
|
||||
reaugment.start().waitFor(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Timeout while waiting for re-augmentation", e);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ public class DatasetTest extends EntityTest<Dataset> implements Loggable {
|
|||
};
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(Include.NON_NULL);
|
||||
Map<String, Object> map = mapper.convertValue(realm, typeRef);
|
||||
Map<String, Object> map = (Map<String, Object>) mapper.convertValue(realm, typeRef);
|
||||
map.put("index", 1000);
|
||||
|
||||
logger().info("MAP:");
|
||||
|
|
|
@ -22,9 +22,7 @@ import io.undertow.servlet.Servlets;
|
|||
import io.undertow.servlet.api.DefaultServletConfig;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.FilterInfo;
|
||||
import io.undertow.servlet.api.ServletInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher;
|
||||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
|
||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||
|
@ -405,7 +403,7 @@ public class KeycloakServer {
|
|||
// KEYCLOAK-14178
|
||||
deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, true);
|
||||
|
||||
FilterInfo filter = Servlets.filter("SessionFilter", UndertowClientConnectionServletFilter.class);
|
||||
FilterInfo filter = Servlets.filter("SessionFilter", UndertowRequestFilter.class);
|
||||
filter.setAsyncSupported(true);
|
||||
|
||||
di.addFilter(filter);
|
||||
|
|
|
@ -27,9 +27,9 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.services.filters.AbstractClientConnectionFilter;
|
||||
import org.keycloak.services.filters.AbstractRequestFilter;
|
||||
|
||||
public class UndertowClientConnectionServletFilter extends AbstractClientConnectionFilter implements Filter {
|
||||
public class UndertowRequestFilter extends AbstractRequestFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
|
@ -26,9 +26,9 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.services.filters.AbstractClientConnectionFilter;
|
||||
import org.keycloak.services.filters.AbstractRequestFilter;
|
||||
|
||||
public class WildFlyClientConnectionServletFilter extends AbstractClientConnectionFilter implements Filter {
|
||||
public class WildFlyRequestFilter extends AbstractRequestFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
Loading…
Reference in a new issue