[KEYCLOAK-13141] - Supporting re-augmentation

This commit is contained in:
Pedro Igor 2020-07-09 12:28:22 -03:00
parent 48e4432e9d
commit 1db1deb066
35 changed files with 321 additions and 1243 deletions

View file

@ -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:

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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
```

View file

@ -1 +0,0 @@
Copy custom providers into this directory then restart the Keycloak server.

View file

@ -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>

View file

@ -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");
}
}
}

View file

@ -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));
}
}

View file

@ -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

View file

@ -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

View file

@ -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>
@ -138,4 +138,32 @@
<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>

View file

@ -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());
}
}
}

View file

@ -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");
}
}
}

View file

@ -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":

View file

@ -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

View file

@ -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));
}
}

View file

@ -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"));
}
}

View file

@ -63,7 +63,6 @@
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<configuration>
<uberJar>true</uberJar>
<finalName>keycloak</finalName>
</configuration>
<executions>

View file

@ -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

View file

@ -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

View file

@ -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 {
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;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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:");

View file

@ -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);

View file

@ -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)

View file

@ -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)