[KEYCLOAK-14147] - Request filter refactoring

Co-authored-by: Stian Thorgersen <stian@redhat.com>
Co-authored-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
Pedro Igor 2020-07-03 13:19:23 -03:00
parent 71dca9e1b9
commit 9c4da9b3ce
34 changed files with 423 additions and 607 deletions

View file

@ -48,13 +48,13 @@
</listener> </listener>
<filter> <filter>
<filter-name>Keycloak Session Management</filter-name> <filter-name>Client Connection Filter</filter-name>
<filter-class>org.keycloak.provider.wildfly.KeycloakSessionServletFilter</filter-class> <filter-class>org.keycloak.provider.wildfly.WildFlyClientConnectionServletFilter</filter-class>
<async-supported>true</async-supported> <async-supported>true</async-supported>
</filter> </filter>
<filter-mapping> <filter-mapping>
<filter-name>Keycloak Session Management</filter-name> <filter-name>Client Connection Filter</filter-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</filter-mapping> </filter-mapping>

View file

@ -59,6 +59,7 @@
<module name="com.fasterxml.jackson.core.jackson-core"/> <module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-annotations"/> <module name="com.fasterxml.jackson.core.jackson-annotations"/>
<module name="com.fasterxml.jackson.core.jackson-databind"/> <module name="com.fasterxml.jackson.core.jackson-databind"/>
<module name="com.fasterxml.jackson.datatype.jackson-datatype-jdk8"/>
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/> <module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
<module name="com.google.zxing.core"/> <module name="com.google.zxing.core"/>
<module name="com.google.zxing.javase"/> <module name="com.google.zxing.javase"/>

View file

@ -420,6 +420,11 @@
<artifactId>jackson-module-jaxb-annotations</artifactId> <artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId> <groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId> <artifactId>jackson-jaxrs-json-provider</artifactId>

View file

@ -59,6 +59,11 @@
<artifactId>quarkus-jdbc-mariadb-deployment</artifactId> <artifactId>quarkus-jdbc-mariadb-deployment</artifactId>
<version>${quarkus.version}</version> <version>${quarkus.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web-deployment</artifactId>
<version>${quarkus.version}</version>
</dependency>
<dependency> <dependency>
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-core</artifactId> <artifactId>quarkus-bootstrap-core</artifactId>

View file

@ -20,6 +20,7 @@ import org.keycloak.provider.KeycloakDeploymentInfo;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager; import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi; import org.keycloak.provider.Spi;
import org.keycloak.provider.quarkus.QuarkusClientConnectionFilter;
import org.keycloak.runtime.KeycloakRecorder; import org.keycloak.runtime.KeycloakRecorder;
import org.keycloak.transaction.JBossJtaTransactionManagerLookup; import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
@ -30,6 +31,7 @@ import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.vertx.http.deployment.FilterBuildItem;
class KeycloakProcessor { class KeycloakProcessor {
@ -57,9 +59,12 @@ class KeycloakProcessor {
} }
/** /**
* <p>Load the built-in provider factories during build time so we don't spend time looking up them at runtime. * <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> * <p>
* User-defined providers are going to be loaded at startup
* </p>
*/ */
@Record(ExecutionTime.STATIC_INIT) @Record(ExecutionTime.STATIC_INIT)
@BuildStep @BuildStep
@ -69,7 +74,8 @@ class KeycloakProcessor {
private Map<Spi, Set<Class<? extends ProviderFactory>>> loadBuiltInFactories() { private Map<Spi, Set<Class<? extends ProviderFactory>>> loadBuiltInFactories() {
ProviderManager pm = new ProviderManager( ProviderManager pm = new ProviderManager(
KeycloakDeploymentInfo.create().services(), Thread.currentThread().getContextClassLoader(), Config.scope().getArray("providers")); KeycloakDeploymentInfo.create().services(), Thread.currentThread().getContextClassLoader(),
Config.scope().getArray("providers"));
Map<Spi, Set<Class<? extends ProviderFactory>>> result = new HashMap<>(); Map<Spi, Set<Class<? extends ProviderFactory>>> result = new HashMap<>();
for (Spi spi : pm.loadSpis()) { for (Spi spi : pm.loadSpis()) {
@ -92,4 +98,9 @@ class KeycloakProcessor {
return result; return result;
} }
@BuildStep
void initializeRouter(BuildProducer<FilterBuildItem> routes) {
routes.produce(new FilterBuildItem(new QuarkusClientConnectionFilter(), FilterBuildItem.AUTHORIZATION - 10));
}
} }

View file

@ -31,11 +31,11 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<quarkus.version>1.5.1.Final</quarkus.version> <quarkus.version>1.6.0.CR1</quarkus.version>
<resteasy.version>4.5.3.Final</resteasy.version> <resteasy.version>4.5.5.Final</resteasy.version>
<jackson.version>2.10.2</jackson.version> <jackson.version>2.10.4</jackson.version>
<jackson.databind.version>${jackson.version}</jackson.databind.version> <jackson.databind.version>${jackson.version}</jackson.databind.version>
<hibernate.version>5.4.16.Final</hibernate.version> <hibernate.version>5.4.18.Final</hibernate.version>
<snakeyaml.version>1.20</snakeyaml.version> <snakeyaml.version>1.20</snakeyaml.version>
<surefire-plugin.version>2.22.0</surefire-plugin.version> <surefire-plugin.version>2.22.0</surefire-plugin.version>

View file

@ -410,10 +410,6 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>liquibase-core</artifactId>

View file

@ -10,6 +10,9 @@ import javax.inject.Inject;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.ws.rs.ApplicationPath; import javax.ws.rs.ApplicationPath;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.utils.PostMigrationEvent; import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.QuarkusWelcomeResource; import org.keycloak.services.resources.QuarkusWelcomeResource;
@ -30,6 +33,10 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
@Override @Override
public Set<Object> getSingletons() { public Set<Object> getSingletons() {
//TODO: a temporary hack for https://github.com/quarkusio/quarkus/issues/9647, we need to disable the sanitizer to avoid
// escaping text/html responses from the server
Resteasy.getContextData(ResteasyDeployment.class).setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, Boolean.TRUE);
HashSet<Object> singletons = new HashSet<>(super.getSingletons().stream().filter(new Predicate<Object>() { HashSet<Object> singletons = new HashSet<>(super.getSingletons().stream().filter(new Predicate<Object>() {
@Override @Override
public boolean test(Object o) { public boolean test(Object o) {

View file

@ -22,9 +22,6 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.quarkus.runtime.configuration.DeploymentProfileConfigSource;
import io.quarkus.runtime.configuration.ExpandingConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -58,23 +55,16 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
} }
if (filePath != null) { if (filePath != null) {
sources.add(wrap(new KeycloakPropertiesConfigSource.InFileSystem(filePath))); sources.add(new KeycloakPropertiesConfigSource.InFileSystem(filePath));
} }
// fall back to the default configuration within the server classpath // fall back to the default configuration within the server classpath
if (sources.isEmpty()) { if (sources.isEmpty()) {
log.debug("Loading the default server configuration"); log.debug("Loading the default server configuration");
sources.add(wrap(new KeycloakPropertiesConfigSource.InJar())); sources.add(new KeycloakPropertiesConfigSource.InJar());
} }
return sources; return sources;
} }
private ConfigSource wrap(ConfigSource source) {
return ExpandingConfigSource.wrapper(new ExpandingConfigSource.Cache())
.compose(DeploymentProfileConfigSource.wrapper())
.apply(source);
}
} }

View file

@ -0,0 +1,90 @@
/*
* Copyright 2019 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.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 io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;
/**
* <p>This filter is responsible for managing the request lifecycle as well as setting up the necessary context to process incoming
* requests.
*
* <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> {
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
};
@Override
public void handle(RoutingContext context) {
ClientConnection clientConnection = createClientConnection(context.request());
// our code should always be run as blocking until we don't provide a better support for running non-blocking code
// in the event loop
context.vertx().executeBlocking(promise -> filter(clientConnection, (session) -> {
try {
context.next();
promise.complete();
} catch (Exception cause) {
promise.fail(cause);
// re-throw so that the any exception is handled from parent
throw new RuntimeException(cause);
}
}), EMPTY_RESULT);
}
private ClientConnection createClientConnection(HttpServerRequest request) {
return new ClientConnection() {
@Override
public String getRemoteAddr() {
return request.remoteAddress().host();
}
@Override
public String getRemoteHost() {
return request.remoteAddress().host();
}
@Override
public int getRemotePort() {
return request.remoteAddress().port();
}
@Override
public String getLocalAddr() {
return request.localAddress().host();
}
@Override
public int getLocalPort() {
return request.localAddress().port();
}
};
}
}

View file

@ -1,118 +0,0 @@
/*
* Copyright 2019 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.provider.quarkus;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;
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.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.services.resources.KeycloakApplication;
import javax.annotation.Priority;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
@PreMatching
@Provider
@Priority(1)
public class QuarkusFilter implements javax.ws.rs.container.ContainerRequestFilter,
javax.ws.rs.container.ContainerResponseFilter {
@Inject
KeycloakApplication keycloakApplication;
@Inject
RoutingContext routingContext;
public QuarkusFilter() {
//TODO: a temporary hack for https://github.com/quarkusio/quarkus/issues/9647, we need to disable the sanitizer to avoid
// escaping text/html responses from the server
Resteasy.getContextData(ResteasyDeployment.class).setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, Boolean.TRUE);
}
@Override
public void filter(ContainerRequestContext containerRequestContext) {
KeycloakSessionFactory sessionFactory = keycloakApplication.getSessionFactory();
KeycloakSession session = sessionFactory.create();
Resteasy.pushContext(KeycloakSession.class, session);
HttpServerRequest request = routingContext.request();
session.getContext().setConnection(createConnection(request));
Resteasy.pushContext(ClientConnection.class, session.getContext().getConnection());
KeycloakTransaction tx = session.getTransactionManager();
Resteasy.pushContext(KeycloakTransaction.class, tx);
tx.begin();
}
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
//End the session and clear context
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
// KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
// should be rolled back
if (session.getTransactionManager() != null && session.getTransactionManager().isActive()) {
session.getTransactionManager().rollback();
}
session.close();
Resteasy.clearContextData();
}
private ClientConnection createConnection(HttpServerRequest request) {
return new ClientConnection() {
@Override
public String getRemoteAddr() {
return request.remoteAddress().host();
}
@Override
public String getRemoteHost() {
return request.remoteAddress().host();
}
@Override
public int getRemotePort() {
return request.remoteAddress().port();
}
@Override
public String getLocalAddr() {
return request.localAddress().host();
}
@Override
public int getLocalPort() {
return request.localAddress().port();
}
};
}
}

View file

@ -21,9 +21,11 @@ import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent; import io.quarkus.runtime.StartupEvent;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes; import javax.enterprise.event.Observes;
import javax.inject.Inject;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager; import org.keycloak.models.KeycloakTransactionManager;
@ -38,18 +40,13 @@ public class QuarkusLifecycleObserver {
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN"; private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD"; private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
@Inject
KeycloakApplication application;
void onStartupEvent(@Observes StartupEvent event) { void onStartupEvent(@Observes StartupEvent event) {
Runnable startupHook = ((QuarkusPlatform) Platform.getPlatform()).startupHook; Runnable startupHook = ((QuarkusPlatform) Platform.getPlatform()).startupHook;
if (startupHook != null) { if (startupHook != null) {
startupHook.run(); startupHook.run();
createAdminUser(); createAdminUser();
} }
} }
void onShutdownEvent(@Observes ShutdownEvent event) { void onShutdownEvent(@Observes ShutdownEvent event) {
@ -70,7 +67,7 @@ public class QuarkusLifecycleObserver {
return; return;
} }
KeycloakSessionFactory sessionFactory = application.getSessionFactory(); KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakSession session = sessionFactory.create(); KeycloakSession session = sessionFactory.create();
KeycloakTransactionManager transaction = session.getTransactionManager(); KeycloakTransactionManager transaction = session.getTransactionManager();

View file

@ -65,8 +65,6 @@ public interface KeycloakContext {
ClientConnection getConnection(); ClientConnection getConnection();
void setConnection(ClientConnection connection);
Locale resolveLocale(UserModel user); Locale resolveLocale(UserModel user);
/** /**

View file

@ -132,6 +132,10 @@
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.woodstox</groupId> <groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId> <artifactId>woodstox-core</artifactId>

View file

@ -118,12 +118,7 @@ public class DefaultKeycloakContext implements KeycloakContext {
@Override @Override
public ClientConnection getConnection() { public ClientConnection getConnection() {
return connection; return getContextObject(ClientConnection.class);
}
@Override
public void setConnection(ClientConnection connection) {
this.connection = connection;
} }
@Override @Override
@ -140,4 +135,5 @@ public class DefaultKeycloakContext implements KeycloakContext {
public void setAuthenticationSession(AuthenticationSessionModel authenticationSession) { public void setAuthenticationSession(AuthenticationSessionModel authenticationSession) {
this.authenticationSession = authenticationSession; this.authenticationSession = authenticationSession;
} }
} }

View file

@ -46,9 +46,6 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
public static final String UNCAUGHT_SERVER_ERROR_TEXT = "Uncaught server error"; public static final String UNCAUGHT_SERVER_ERROR_TEXT = "Uncaught server error";
@Context
private KeycloakSession session;
@Context @Context
private HttpHeaders headers; private HttpHeaders headers;
@ -57,7 +54,8 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
@Override @Override
public Response toResponse(Throwable throwable) { public Response toResponse(Throwable throwable) {
KeycloakTransaction tx = Resteasy.getContextData(KeycloakTransaction.class); KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
KeycloakTransaction tx = session.getTransactionManager();
tx.setRollbackOnly(); tx.setRollbackOnly();
int statusCode = getStatusCode(throwable); int statusCode = getStatusCode(throwable);
@ -78,14 +76,14 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
} }
try { try {
RealmModel realm = resolveRealm(); RealmModel realm = resolveRealm(session);
Theme theme = session.theme().getTheme(Theme.Type.LOGIN); Theme theme = session.theme().getTheme(Theme.Type.LOGIN);
Locale locale = session.getContext().resolveLocale(null); Locale locale = session.getContext().resolveLocale(null);
FreeMarkerUtil freeMarker = new FreeMarkerUtil(); FreeMarkerUtil freeMarker = new FreeMarkerUtil();
Map<String, Object> attributes = initAttributes(realm, theme, locale, statusCode); Map<String, Object> attributes = initAttributes(session, realm, theme, locale, statusCode);
String templateName = "error.ftl"; String templateName = "error.ftl";
@ -121,7 +119,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
return "unknown_error"; return "unknown_error";
} }
private RealmModel resolveRealm() { private RealmModel resolveRealm(KeycloakSession session) {
String path = session.getContext().getUri().getPath(); String path = session.getContext().getUri().getPath();
Matcher m = realmNamePattern.matcher(path); Matcher m = realmNamePattern.matcher(path);
String realmName; String realmName;
@ -142,7 +140,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
return realm; return realm;
} }
private Map<String, Object> initAttributes(RealmModel realm, Theme theme, Locale locale, int statusCode) throws IOException { private Map<String, Object> initAttributes(KeycloakSession session, RealmModel realm, Theme theme, Locale locale, int statusCode) throws IOException {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
Properties messagesBundle = theme.getMessages(locale); Properties messagesBundle = theme.getMessages(locale);

View file

@ -0,0 +1,66 @@
package org.keycloak.services.filters;
/*
* 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.
*/
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;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.services.resources.KeycloakApplication;
public abstract class AbstractClientConnectionFilter {
public void filter(ClientConnection clientConnection, Consumer<KeycloakSession> next) {
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
KeycloakSession session = sessionFactory.create();
KeycloakTransactionManager tx = session.getTransactionManager();
tx.begin();
try {
Resteasy.pushContext(ClientConnection.class, clientConnection);
Resteasy.pushContext(KeycloakSession.class, session);
next.accept(session);
} catch (Exception e) {
tx.setRollbackOnly();
throw new RuntimeException(e);
} finally {
close(session);
}
}
public void close(KeycloakSession session) {
KeycloakTransactionManager tx = session.getTransactionManager();
if (tx.isActive()) {
if (tx.getRollbackOnly()) {
tx.rollback();
} else {
tx.commit();
}
}
session.close();
}
}

View file

@ -23,17 +23,19 @@ import org.keycloak.models.KeycloakSession;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@PreMatching
public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter { public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter {
@Override @Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) {
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class); KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);
securityHeadersProvider.addHeaders(requestContext, responseContext);
}
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);
securityHeadersProvider.addHeaders(containerRequestContext, containerResponseContext);
}
} }

View file

@ -1,50 +0,0 @@
/*
* 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.filters;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakTransaction;
import javax.annotation.Priority;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import java.io.IOException;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@Priority(2)
public class KeycloakTransactionCommitter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
KeycloakTransaction tx = Resteasy.getContextData(KeycloakTransaction.class);
if (tx != null && tx.isActive()) {
if (tx.getRollbackOnly()) {
tx.rollback();
} else {
tx.commit();
}
}
}
}

View file

@ -43,7 +43,6 @@ import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.error.KeycloakErrorHandler; import org.keycloak.services.error.KeycloakErrorHandler;
import org.keycloak.services.filters.KeycloakSecurityHeadersFilter; import org.keycloak.services.filters.KeycloakSecurityHeadersFilter;
import org.keycloak.services.filters.KeycloakTransactionCommitter;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UserStorageSyncManager; import org.keycloak.services.managers.UserStorageSyncManager;
@ -89,7 +88,7 @@ public class KeycloakApplication extends Application {
protected Set<Object> singletons = new HashSet<Object>(); protected Set<Object> singletons = new HashSet<Object>();
protected Set<Class<?>> classes = new HashSet<Class<?>>(); protected Set<Class<?>> classes = new HashSet<Class<?>>();
protected KeycloakSessionFactory sessionFactory; protected static KeycloakSessionFactory sessionFactory;
public KeycloakApplication() { public KeycloakApplication() {
@ -100,9 +99,6 @@ public class KeycloakApplication extends Application {
loadConfig(); loadConfig();
Resteasy.pushDefaultContextObject(KeycloakApplication.class, this);
Resteasy.pushContext(KeycloakApplication.class, this); // for injection
singletons.add(new RobotsResource()); singletons.add(new RobotsResource());
singletons.add(new RealmsResource()); singletons.add(new RealmsResource());
singletons.add(new AdminRoot()); singletons.add(new AdminRoot());
@ -110,10 +106,9 @@ public class KeycloakApplication extends Application {
classes.add(JsResource.class); classes.add(JsResource.class);
classes.add(KeycloakSecurityHeadersFilter.class); classes.add(KeycloakSecurityHeadersFilter.class);
classes.add(KeycloakTransactionCommitter.class);
classes.add(KeycloakErrorHandler.class); classes.add(KeycloakErrorHandler.class);
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); singletons.add(new ObjectMapperResolver());
singletons.add(new WelcomeResource()); singletons.add(new WelcomeResource());
platform.onStartup(this::startup); platform.onStartup(this::startup);
@ -281,7 +276,7 @@ public class KeycloakApplication extends Application {
} }
} }
public KeycloakSessionFactory getSessionFactory() { public static KeycloakSessionFactory getSessionFactory() {
return sessionFactory; return sessionFactory;
} }

View file

@ -56,7 +56,6 @@ import org.keycloak.services.clientregistration.policy.RegistrationAuth;
import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
@ -105,16 +104,9 @@ public class ClientResource {
protected ClientModel client; protected ClientModel client;
protected KeycloakSession session; protected KeycloakSession session;
@Context
protected KeycloakApplication keycloak;
@Context @Context
protected ClientConnection clientConnection; protected ClientConnection clientConnection;
protected KeycloakApplication getKeycloakApplication() {
return keycloak;
}
public ClientResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) { public ClientResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) {
this.realm = realm; this.realm = realm;
this.auth = auth; this.auth = auth;

View file

@ -18,7 +18,6 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.annotations.cache.NoCache;
import javax.ws.rs.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
@ -34,12 +33,12 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST; import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
@ -68,9 +67,6 @@ public class RealmsAdminResource {
@Context @Context
protected KeycloakSession session; protected KeycloakSession session;
@Context
protected KeycloakApplication keycloak;
@Context @Context
protected ClientConnection clientConnection; protected ClientConnection clientConnection;

View file

@ -55,11 +55,12 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @resource Roles * @resource Roles
@ -94,13 +95,13 @@ public class RoleContainerResource extends RoleResource {
@GET @GET
@NoCache @NoCache
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public List<RoleRepresentation> getRoles(@QueryParam("search") @DefaultValue("") String search, public Stream<RoleRepresentation> getRoles(@QueryParam("search") @DefaultValue("") String search,
@QueryParam("first") Integer firstResult, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults, @QueryParam("max") Integer maxResults,
@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) {
auth.roles().requireList(roleContainer); auth.roles().requireList(roleContainer);
Set<RoleModel> roleModels = new HashSet<RoleModel>(); Set<RoleModel> roleModels;
if(search != null && search.trim().length() > 0) { if(search != null && search.trim().length() > 0) {
roleModels = roleContainer.searchForRoles(search, firstResult, maxResults); roleModels = roleContainer.searchForRoles(search, firstResult, maxResults);
@ -110,15 +111,10 @@ public class RoleContainerResource extends RoleResource {
roleModels = roleContainer.getRoles(); roleModels = roleContainer.getRoles();
} }
List<RoleRepresentation> roles = new ArrayList<RoleRepresentation>(); Function<RoleModel, RoleRepresentation> toRoleRepresentation = briefRepresentation ?
for (RoleModel roleModel : roleModels) { ModelToRepresentation::toBriefRepresentation :
if(briefRepresentation) { ModelToRepresentation::toRepresentation;
roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); return roleModels.stream().map(toRoleRepresentation);
} else {
roles.add(ModelToRepresentation.toRepresentation(roleModel));
}
}
return roles;
} }
/** /**

View file

@ -18,11 +18,16 @@
package org.keycloak.services.util; package org.keycloak.services.util;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jdk8.StreamSerializer;
import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.Provider;
import java.util.stream.Stream;
/** /**
* Any class with package org.jboss.resteasy.skeleton.key will use NON_DEFAULT inclusion * Any class with package org.jboss.resteasy.skeleton.key will use NON_DEFAULT inclusion
@ -34,9 +39,16 @@ import javax.ws.rs.ext.Provider;
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> { public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
protected ObjectMapper mapper = new ObjectMapper(); protected ObjectMapper mapper = new ObjectMapper();
public ObjectMapperResolver(boolean indent) { public ObjectMapperResolver() {
JavaType type = TypeFactory.unknownType();
JavaType streamType = mapper.getTypeFactory().constructParametricType(Stream.class, type);
SimpleModule module = new SimpleModule();
module.addSerializer(new StreamSerializer(streamType, type));
mapper.registerModule(module);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
if (indent) { if (Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))) {
mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.enable(SerializationFeature.INDENT_OUTPUT);
} }
} }

View file

@ -50,7 +50,7 @@ import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.testsuite.JsonConfigProviderFactory; import org.keycloak.testsuite.JsonConfigProviderFactory;
import org.keycloak.testsuite.KeycloakServer; import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.TestKeycloakSessionServletFilter; import org.keycloak.testsuite.UndertowClientConnectionServletFilter;
import org.keycloak.testsuite.utils.tls.TLSUtils; import org.keycloak.testsuite.utils.tls.TLSUtils;
import org.keycloak.testsuite.utils.undertow.UndertowDeployerHelper; import org.keycloak.testsuite.utils.undertow.UndertowDeployerHelper;
import org.keycloak.testsuite.utils.undertow.UndertowWarClassLoader; import org.keycloak.testsuite.utils.undertow.UndertowWarClassLoader;
@ -99,7 +99,7 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
di.setDefaultServletConfig(new DefaultServletConfig(true)); di.setDefaultServletConfig(new DefaultServletConfig(true));
di.addWelcomePage("theme/keycloak/welcome/resources/index.html"); di.addWelcomePage("theme/keycloak/welcome/resources/index.html");
FilterInfo filter = Servlets.filter("SessionFilter", TestKeycloakSessionServletFilter.class); FilterInfo filter = Servlets.filter("SessionFilter", UndertowClientConnectionServletFilter.class);
di.addFilter(filter); di.addFilter(filter);
di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST); di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST);
filter.setAsyncSupported(true); filter.setAsyncSupported(true);
@ -203,8 +203,7 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
DeploymentInfo di = createAuthServerDeploymentInfo(); DeploymentInfo di = createAuthServerDeploymentInfo();
undertow.deploy(di); undertow.deploy(di);
ResteasyDeployment deployment = (ResteasyDeployment) di.getServletContextAttributes().get(ResteasyDeployment.class.getName()); sessionFactory = KeycloakApplication.getSessionFactory();
sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory();
setupDevConfig(); setupDevConfig();

View file

@ -19,6 +19,8 @@ package org.keycloak.testsuite;
import org.junit.After; import org.junit.After;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Resteasy;
import org.keycloak.common.util.reflections.Reflections; import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -120,7 +122,7 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest
/** KEYCLOAK-12065 Inherit Client Connection from parent session **/ /** KEYCLOAK-12065 Inherit Client Connection from parent session **/
public static KeycloakSession inheritClientConnection(KeycloakSession parentSession, KeycloakSession currentSession) { public static KeycloakSession inheritClientConnection(KeycloakSession parentSession, KeycloakSession currentSession) {
currentSession.getContext().setConnection(parentSession.getContext().getConnection()); Resteasy.pushContext(ClientConnection.class, parentSession.getContext().getConnection());
return currentSession; return currentSession;
} }
} }

View file

@ -405,7 +405,7 @@ public class KeycloakServer {
// KEYCLOAK-14178 // KEYCLOAK-14178
deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, true); deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, true);
FilterInfo filter = Servlets.filter("SessionFilter", TestKeycloakSessionServletFilter.class); FilterInfo filter = Servlets.filter("SessionFilter", UndertowClientConnectionServletFilter.class);
filter.setAsyncSupported(true); filter.setAsyncSupported(true);
di.addFilter(filter); di.addFilter(filter);
@ -413,7 +413,7 @@ public class KeycloakServer {
server.deploy(di); server.deploy(di);
sessionFactory = ((KeycloakApplication) deployment.getApplication()).getSessionFactory(); sessionFactory = KeycloakApplication.getSessionFactory();
setupDevConfig(); setupDevConfig();

View file

@ -1,138 +0,0 @@
/*
* 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.testsuite;
import java.io.IOException;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransaction;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakSessionServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
final HttpServletRequest request = (HttpServletRequest)servletRequest;
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
KeycloakSession session = sessionFactory.create();
Resteasy.pushContext(KeycloakSession.class, session);
ClientConnection connection = new ClientConnection() {
@Override
public String getRemoteAddr() {
return request.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return request.getRemoteHost();
}
@Override
public int getRemotePort() {
return request.getRemotePort();
}
@Override
public String getLocalAddr() {
return request.getLocalAddr();
}
@Override
public int getLocalPort() {
return request.getLocalPort();
}
};
session.getContext().setConnection(connection);
Resteasy.pushContext(ClientConnection.class, connection);
KeycloakTransaction tx = session.getTransactionManager();
Resteasy.pushContext(KeycloakTransaction.class, tx);
tx.begin();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (servletRequest.isAsyncStarted()) {
servletRequest.getAsyncContext().addListener(createAsyncLifeCycleListener(session));
} else {
closeSession(session);
}
}
}
private AsyncListener createAsyncLifeCycleListener(final KeycloakSession session) {
return new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) {
closeSession(session);
}
@Override
public void onTimeout(AsyncEvent event) {
closeSession(session);
}
@Override
public void onError(AsyncEvent event) {
closeSession(session);
}
@Override
public void onStartAsync(AsyncEvent event) {
}
};
}
private void closeSession(KeycloakSession session) {
// KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
// should be rolled back
if (session.getTransactionManager() != null && session.getTransactionManager().isActive()) {
session.getTransactionManager().rollback();
}
session.close();
Resteasy.clearContextData();
}
@Override
public void destroy() {
}
}

View file

@ -1,49 +0,0 @@
/*
* Copyright 2019 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.testsuite;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class TestKeycloakSessionServletFilter extends KeycloakSessionServletFilter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
super.doFilter(new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {
@Override
public String getRemoteAddr() {
String forwardedFor = getHeader("X-Forwarded-For");
if (forwardedFor != null) {
return forwardedFor;
}
return super.getRemoteAddr();
}
}, servletResponse, filterChain);
}
}

View file

@ -17,21 +17,13 @@
package org.keycloak.testsuite; package org.keycloak.testsuite;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.platform.PlatformProvider; import org.keycloak.platform.PlatformProvider;
import org.keycloak.services.resources.KeycloakApplication;
import javax.servlet.ServletContext;
public class TestPlatform implements PlatformProvider { public class TestPlatform implements PlatformProvider {
@Override @Override
public void onStartup(Runnable startupHook) { public void onStartup(Runnable startupHook) {
startupHook.run(); startupHook.run();
KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class);
ServletContext context = Resteasy.getContextData(ServletContext.class);
context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory());
} }
@Override @Override

View file

@ -0,0 +1,91 @@
/*
* 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.testsuite;
import java.io.UnsupportedEncodingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.services.filters.AbstractClientConnectionFilter;
public class UndertowClientConnectionServletFilter extends AbstractClientConnectionFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws UnsupportedEncodingException {
servletRequest.setCharacterEncoding("UTF-8");
final HttpServletRequest request = (HttpServletRequest) servletRequest;
filter(createClientConnection(request), (session) -> {
try {
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private ClientConnection createClientConnection(HttpServletRequest request) {
return new ClientConnection() {
@Override
public String getRemoteAddr() {
String forwardedFor = request.getHeader("X-Forwarded-For");
if (forwardedFor != null) {
return forwardedFor;
}
return request.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return request.getRemoteHost();
}
@Override
public int getRemotePort() {
return request.getRemotePort();
}
@Override
public String getLocalAddr() {
return request.getLocalAddr();
}
@Override
public int getLocalPort() {
return request.getLocalPort();
}
};
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View file

@ -1,138 +0,0 @@
/*
* 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.provider.wildfly;
import java.io.IOException;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransaction;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakSessionServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
final HttpServletRequest request = (HttpServletRequest)servletRequest;
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
KeycloakSession session = sessionFactory.create();
Resteasy.pushContext(KeycloakSession.class, session);
ClientConnection connection = new ClientConnection() {
@Override
public String getRemoteAddr() {
return request.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return request.getRemoteHost();
}
@Override
public int getRemotePort() {
return request.getRemotePort();
}
@Override
public String getLocalAddr() {
return request.getLocalAddr();
}
@Override
public int getLocalPort() {
return request.getLocalPort();
}
};
session.getContext().setConnection(connection);
Resteasy.pushContext(ClientConnection.class, connection);
KeycloakTransaction tx = session.getTransactionManager();
Resteasy.pushContext(KeycloakTransaction.class, tx);
tx.begin();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (servletRequest.isAsyncStarted()) {
servletRequest.getAsyncContext().addListener(createAsyncLifeCycleListener(session));
} else {
closeSession(session);
}
}
}
private AsyncListener createAsyncLifeCycleListener(final KeycloakSession session) {
return new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) {
closeSession(session);
}
@Override
public void onTimeout(AsyncEvent event) {
closeSession(session);
}
@Override
public void onError(AsyncEvent event) {
closeSession(session);
}
@Override
public void onStartAsync(AsyncEvent event) {
}
};
}
private void closeSession(KeycloakSession session) {
// KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
// should be rolled back
if (session.getTransactionManager() != null && session.getTransactionManager().isActive()) {
session.getTransactionManager().rollback();
}
session.close();
Resteasy.clearContextData();
}
@Override
public void destroy() {
}
}

View file

@ -0,0 +1,76 @@
/*
* 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.provider.wildfly;
import java.io.UnsupportedEncodingException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.common.ClientConnection;
import org.keycloak.services.filters.AbstractClientConnectionFilter;
public class WildFlyClientConnectionServletFilter extends AbstractClientConnectionFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws UnsupportedEncodingException {
servletRequest.setCharacterEncoding("UTF-8");
ClientConnection clientConnection = createConnection((HttpServletRequest) servletRequest);
filter(clientConnection, (session) -> {
try {
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private ClientConnection createConnection(HttpServletRequest request) {
return new ClientConnection() {
@Override
public String getRemoteAddr() {
return request.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return request.getRemoteHost();
}
@Override
public int getRemotePort() {
return request.getRemotePort();
}
@Override
public String getLocalAddr() {
return request.getLocalAddr();
}
@Override
public int getLocalPort() {
return request.getLocalPort();
}
};
}
}

View file

@ -17,13 +17,8 @@
package org.keycloak.provider.wildfly; package org.keycloak.provider.wildfly;
import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.platform.PlatformProvider; import org.keycloak.platform.PlatformProvider;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.resources.KeycloakApplication;
import javax.servlet.ServletContext;
public class WildflyPlatform implements PlatformProvider { public class WildflyPlatform implements PlatformProvider {
@ -32,9 +27,6 @@ public class WildflyPlatform implements PlatformProvider {
@Override @Override
public void onStartup(Runnable startupHook) { public void onStartup(Runnable startupHook) {
startupHook.run(); startupHook.run();
KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class);
ServletContext context = Resteasy.getContextData(ServletContext.class);
context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory());
} }
@Override @Override