From 9c4da9b3ce25bf609ffa9f53fe6a9d68b7b00d5c Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 3 Jul 2020 13:19:23 -0300 Subject: [PATCH] [KEYCLOAK-14147] - Request filter refactoring Co-authored-by: Stian Thorgersen Co-authored-by: Martin Kanis --- .../main/server-war/WEB-INF/web.xml | 6 +- .../keycloak-services/main/module.xml | 1 + pom.xml | 5 + quarkus/deployment/pom.xml | 5 + .../quarkus/deployment/KeycloakProcessor.java | 17 ++- quarkus/pom.xml | 8 +- quarkus/runtime/pom.xml | 4 - .../keycloak/QuarkusKeycloakApplication.java | 7 + .../quarkus/KeycloakConfigSourceProvider.java | 14 +- .../QuarkusClientConnectionFilter.java | 90 ++++++++++++ .../provider/quarkus/QuarkusFilter.java | 118 --------------- .../quarkus/QuarkusLifecycleObserver.java | 11 +- .../org/keycloak/models/KeycloakContext.java | 2 - services/pom.xml | 4 + .../services/DefaultKeycloakContext.java | 8 +- .../services/error/KeycloakErrorHandler.java | 14 +- .../AbstractClientConnectionFilter.java | 66 +++++++++ .../KeycloakSecurityHeadersFilter.java | 10 +- .../filters/KeycloakTransactionCommitter.java | 50 ------- .../resources/KeycloakApplication.java | 11 +- .../resources/admin/ClientResource.java | 8 - .../resources/admin/RealmsAdminResource.java | 6 +- .../admin/RoleContainerResource.java | 26 ++-- .../services/util/ObjectMapperResolver.java | 16 +- .../undertow/KeycloakOnUndertow.java | 7 +- .../AbstractTestRealmKeycloakTest.java | 4 +- .../keycloak/testsuite/KeycloakServer.java | 4 +- .../KeycloakSessionServletFilter.java | 138 ------------------ .../TestKeycloakSessionServletFilter.java | 49 ------- .../org/keycloak/testsuite/TestPlatform.java | 8 - ...UndertowClientConnectionServletFilter.java | 91 ++++++++++++ .../wildfly/KeycloakSessionServletFilter.java | 138 ------------------ .../WildFlyClientConnectionServletFilter.java | 76 ++++++++++ .../provider/wildfly/WildflyPlatform.java | 8 - 34 files changed, 423 insertions(+), 607 deletions(-) create mode 100644 quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusClientConnectionFilter.java delete mode 100644 quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusFilter.java create mode 100644 services/src/main/java/org/keycloak/services/filters/AbstractClientConnectionFilter.java delete mode 100644 services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java delete mode 100755 testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakSessionServletFilter.java delete mode 100644 testsuite/utils/src/main/java/org/keycloak/testsuite/TestKeycloakSessionServletFilter.java create mode 100755 testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowClientConnectionServletFilter.java delete mode 100755 wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/KeycloakSessionServletFilter.java create mode 100755 wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildFlyClientConnectionServletFilter.java diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml index f561b268e1..5ae73fd33a 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml @@ -48,13 +48,13 @@ - Keycloak Session Management - org.keycloak.provider.wildfly.KeycloakSessionServletFilter + Client Connection Filter + org.keycloak.provider.wildfly.WildFlyClientConnectionServletFilter true - Keycloak Session Management + Client Connection Filter /* diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml index 30c0fdf101..a70a57eb8d 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml @@ -59,6 +59,7 @@ + diff --git a/pom.xml b/pom.xml index 9826381e01..e19019a7b9 100755 --- a/pom.xml +++ b/pom.xml @@ -420,6 +420,11 @@ jackson-module-jaxb-annotations ${jackson.version} + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + com.fasterxml.jackson.jaxrs jackson-jaxrs-json-provider diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index b42b657d01..92a0e64bee 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -59,6 +59,11 @@ quarkus-jdbc-mariadb-deployment ${quarkus.version} + + io.quarkus + quarkus-vertx-web-deployment + ${quarkus.version} + io.quarkus quarkus-bootstrap-core diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 241b9c11e6..5fa1e3bb96 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -20,6 +20,7 @@ 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.runtime.KeycloakRecorder; 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.builditem.FeatureBuildItem; import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem; +import io.quarkus.vertx.http.deployment.FilterBuildItem; class KeycloakProcessor { @@ -57,9 +59,12 @@ class KeycloakProcessor { } /** - *

Load the built-in provider factories during build time so we don't spend time looking up them at runtime. + *

+ * Load the built-in provider factories during build time so we don't spend time looking up them at runtime. * - *

User-defined providers are going to be loaded at startup

+ *

+ * User-defined providers are going to be loaded at startup + *

*/ @Record(ExecutionTime.STATIC_INIT) @BuildStep @@ -69,7 +74,8 @@ class KeycloakProcessor { private Map>> loadBuiltInFactories() { 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>> result = new HashMap<>(); for (Spi spi : pm.loadSpis()) { @@ -92,4 +98,9 @@ class KeycloakProcessor { return result; } + + @BuildStep + void initializeRouter(BuildProducer routes) { + routes.produce(new FilterBuildItem(new QuarkusClientConnectionFilter(), FilterBuildItem.AUTHORIZATION - 10)); + } } diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 19e0f51170..f76183f1b1 100755 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -31,11 +31,11 @@ pom - 1.5.1.Final - 4.5.3.Final - 2.10.2 + 1.6.0.CR1 + 4.5.5.Final + 2.10.4 ${jackson.version} - 5.4.16.Final + 5.4.18.Final 1.20 2.22.0 diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index 0623375b85..7e23ca311e 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -410,10 +410,6 @@ org.apache.commons commons-lang3
- - io.quarkus - quarkus-vertx-web - org.liquibase liquibase-core diff --git a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java index b18979877f..9964c7099c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java +++ b/quarkus/runtime/src/main/java/org/keycloak/QuarkusKeycloakApplication.java @@ -10,6 +10,9 @@ import javax.inject.Inject; import javax.persistence.EntityManagerFactory; 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.services.resources.KeycloakApplication; import org.keycloak.services.resources.QuarkusWelcomeResource; @@ -30,6 +33,10 @@ public class QuarkusKeycloakApplication extends KeycloakApplication { @Override public Set 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 singletons = new HashSet<>(super.getSingletons().stream().filter(new Predicate() { @Override public boolean test(Object o) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/KeycloakConfigSourceProvider.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/KeycloakConfigSourceProvider.java index eed436c6ba..1bf69cc93c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/KeycloakConfigSourceProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/KeycloakConfigSourceProvider.java @@ -22,9 +22,6 @@ import java.nio.file.Paths; import java.util.ArrayList; 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.ConfigSourceProvider; import org.jboss.logging.Logger; @@ -58,23 +55,16 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider { } 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 if (sources.isEmpty()) { log.debug("Loading the default server configuration"); - sources.add(wrap(new KeycloakPropertiesConfigSource.InJar())); + sources.add(new KeycloakPropertiesConfigSource.InJar()); } return sources; } - - private ConfigSource wrap(ConfigSource source) { - return ExpandingConfigSource.wrapper(new ExpandingConfigSource.Cache()) - .compose(DeploymentProfileConfigSource.wrapper()) - .apply(source); - } - } diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusClientConnectionFilter.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusClientConnectionFilter.java new file mode 100644 index 0000000000..e2d7309e37 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusClientConnectionFilter.java @@ -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; + +/** + *

This filter is responsible for managing the request lifecycle as well as setting up the necessary context to process incoming + * requests. + * + *

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 { + + private static final Handler> 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(); + } + }; + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusFilter.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusFilter.java deleted file mode 100644 index 2c69125942..0000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusFilter.java +++ /dev/null @@ -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(); - } - }; - } -} \ No newline at end of file diff --git a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusLifecycleObserver.java b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusLifecycleObserver.java index 03aab42ed4..d426c4d87c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusLifecycleObserver.java +++ b/quarkus/runtime/src/main/java/org/keycloak/provider/quarkus/QuarkusLifecycleObserver.java @@ -21,9 +21,11 @@ import io.quarkus.runtime.ShutdownEvent; import io.quarkus.runtime.StartupEvent; import javax.enterprise.context.ApplicationScoped; 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.common.util.Resteasy; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; 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_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD"; - @Inject - KeycloakApplication application; - void onStartupEvent(@Observes StartupEvent event) { - Runnable startupHook = ((QuarkusPlatform) Platform.getPlatform()).startupHook; if (startupHook != null) { startupHook.run(); createAdminUser(); } - } void onShutdownEvent(@Observes ShutdownEvent event) { @@ -70,7 +67,7 @@ public class QuarkusLifecycleObserver { return; } - KeycloakSessionFactory sessionFactory = application.getSessionFactory(); + KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory(); KeycloakSession session = sessionFactory.create(); KeycloakTransactionManager transaction = session.getTransactionManager(); diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java index 725c3d7c6b..eb3e0a39fc 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java @@ -65,8 +65,6 @@ public interface KeycloakContext { ClientConnection getConnection(); - void setConnection(ClientConnection connection); - Locale resolveLocale(UserModel user); /** diff --git a/services/pom.xml b/services/pom.xml index 4c0a99c01b..9c18195229 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -132,6 +132,10 @@ com.fasterxml.jackson.core jackson-annotations + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + com.fasterxml.woodstox woodstox-core diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index 1bab1ff1ea..eb47d66a09 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -118,12 +118,7 @@ public class DefaultKeycloakContext implements KeycloakContext { @Override public ClientConnection getConnection() { - return connection; - } - - @Override - public void setConnection(ClientConnection connection) { - this.connection = connection; + return getContextObject(ClientConnection.class); } @Override @@ -140,4 +135,5 @@ public class DefaultKeycloakContext implements KeycloakContext { public void setAuthenticationSession(AuthenticationSessionModel authenticationSession) { this.authenticationSession = authenticationSession; } + } diff --git a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java index eab6b0a749..a2b23091f3 100644 --- a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java +++ b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java @@ -46,9 +46,6 @@ public class KeycloakErrorHandler implements ExceptionMapper { public static final String UNCAUGHT_SERVER_ERROR_TEXT = "Uncaught server error"; - @Context - private KeycloakSession session; - @Context private HttpHeaders headers; @@ -57,7 +54,8 @@ public class KeycloakErrorHandler implements ExceptionMapper { @Override public Response toResponse(Throwable throwable) { - KeycloakTransaction tx = Resteasy.getContextData(KeycloakTransaction.class); + KeycloakSession session = Resteasy.getContextData(KeycloakSession.class); + KeycloakTransaction tx = session.getTransactionManager(); tx.setRollbackOnly(); int statusCode = getStatusCode(throwable); @@ -78,14 +76,14 @@ public class KeycloakErrorHandler implements ExceptionMapper { } try { - RealmModel realm = resolveRealm(); + RealmModel realm = resolveRealm(session); Theme theme = session.theme().getTheme(Theme.Type.LOGIN); Locale locale = session.getContext().resolveLocale(null); FreeMarkerUtil freeMarker = new FreeMarkerUtil(); - Map attributes = initAttributes(realm, theme, locale, statusCode); + Map attributes = initAttributes(session, realm, theme, locale, statusCode); String templateName = "error.ftl"; @@ -121,7 +119,7 @@ public class KeycloakErrorHandler implements ExceptionMapper { return "unknown_error"; } - private RealmModel resolveRealm() { + private RealmModel resolveRealm(KeycloakSession session) { String path = session.getContext().getUri().getPath(); Matcher m = realmNamePattern.matcher(path); String realmName; @@ -142,7 +140,7 @@ public class KeycloakErrorHandler implements ExceptionMapper { return realm; } - private Map initAttributes(RealmModel realm, Theme theme, Locale locale, int statusCode) throws IOException { + private Map initAttributes(KeycloakSession session, RealmModel realm, Theme theme, Locale locale, int statusCode) throws IOException { Map attributes = new HashMap<>(); Properties messagesBundle = theme.getMessages(locale); diff --git a/services/src/main/java/org/keycloak/services/filters/AbstractClientConnectionFilter.java b/services/src/main/java/org/keycloak/services/filters/AbstractClientConnectionFilter.java new file mode 100644 index 0000000000..32f03e883a --- /dev/null +++ b/services/src/main/java/org/keycloak/services/filters/AbstractClientConnectionFilter.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java index 87b883766c..96fecef162 100644 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java @@ -23,17 +23,19 @@ import org.keycloak.models.KeycloakSession; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.container.PreMatching; /** * @author Stian Thorgersen */ +@PreMatching public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter { @Override - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { + public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) { 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); + } } diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java deleted file mode 100644 index 677f0764b6..0000000000 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakTransactionCommitter.java +++ /dev/null @@ -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 Stian Thorgersen - */ -@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(); - } - } - } - -} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index a6292f2dba..a0a6ef1401 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -43,7 +43,6 @@ import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.ServicesLogger; import org.keycloak.services.error.KeycloakErrorHandler; import org.keycloak.services.filters.KeycloakSecurityHeadersFilter; -import org.keycloak.services.filters.KeycloakTransactionCommitter; import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.UserStorageSyncManager; @@ -89,7 +88,7 @@ public class KeycloakApplication extends Application { protected Set singletons = new HashSet(); protected Set> classes = new HashSet>(); - protected KeycloakSessionFactory sessionFactory; + protected static KeycloakSessionFactory sessionFactory; public KeycloakApplication() { @@ -100,9 +99,6 @@ public class KeycloakApplication extends Application { loadConfig(); - Resteasy.pushDefaultContextObject(KeycloakApplication.class, this); - Resteasy.pushContext(KeycloakApplication.class, this); // for injection - singletons.add(new RobotsResource()); singletons.add(new RealmsResource()); singletons.add(new AdminRoot()); @@ -110,10 +106,9 @@ public class KeycloakApplication extends Application { classes.add(JsResource.class); classes.add(KeycloakSecurityHeadersFilter.class); - classes.add(KeycloakTransactionCommitter.class); classes.add(KeycloakErrorHandler.class); - singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); + singletons.add(new ObjectMapperResolver()); singletons.add(new WelcomeResource()); platform.onStartup(this::startup); @@ -281,7 +276,7 @@ public class KeycloakApplication extends Application { } } - public KeycloakSessionFactory getSessionFactory() { + public static KeycloakSessionFactory getSessionFactory() { return sessionFactory; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 8eaf52caa1..36d7ffb6d8 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -56,7 +56,6 @@ import org.keycloak.services.clientregistration.policy.RegistrationAuth; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; 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.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; @@ -105,16 +104,9 @@ public class ClientResource { protected ClientModel client; protected KeycloakSession session; - @Context - protected KeycloakApplication keycloak; - @Context protected ClientConnection clientConnection; - protected KeycloakApplication getKeycloakApplication() { - return keycloak; - } - public ClientResource(RealmModel realm, AdminPermissionEvaluator auth, ClientModel clientModel, KeycloakSession session, AdminEventBuilder adminEvent) { this.realm = realm; this.auth = auth; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java index 75405bb734..01caadfd16 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmsAdminResource.java @@ -18,7 +18,6 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; -import javax.ws.rs.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.common.ClientConnection; import org.keycloak.models.AdminRoles; @@ -34,12 +33,12 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.ForbiddenException; 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.AdminPermissions; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -68,9 +67,6 @@ public class RealmsAdminResource { @Context protected KeycloakSession session; - @Context - protected KeycloakApplication keycloak; - @Context protected ClientConnection clientConnection; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java index 707f73b407..80f389b83d 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java @@ -55,11 +55,12 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @resource Roles @@ -94,13 +95,13 @@ public class RoleContainerResource extends RoleResource { @GET @NoCache @Produces(MediaType.APPLICATION_JSON) - public List getRoles(@QueryParam("search") @DefaultValue("") String search, - @QueryParam("first") Integer firstResult, - @QueryParam("max") Integer maxResults, - @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { + public Stream getRoles(@QueryParam("search") @DefaultValue("") String search, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults, + @QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation) { auth.roles().requireList(roleContainer); - Set roleModels = new HashSet(); + Set roleModels; if(search != null && search.trim().length() > 0) { roleModels = roleContainer.searchForRoles(search, firstResult, maxResults); @@ -110,15 +111,10 @@ public class RoleContainerResource extends RoleResource { roleModels = roleContainer.getRoles(); } - List roles = new ArrayList(); - for (RoleModel roleModel : roleModels) { - if(briefRepresentation) { - roles.add(ModelToRepresentation.toBriefRepresentation(roleModel)); - } else { - roles.add(ModelToRepresentation.toRepresentation(roleModel)); - } - } - return roles; + Function toRoleRepresentation = briefRepresentation ? + ModelToRepresentation::toBriefRepresentation : + ModelToRepresentation::toRepresentation; + return roleModels.stream().map(toRoleRepresentation); } /** diff --git a/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java b/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java index e98ecb9492..6aa1169ba0 100755 --- a/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java +++ b/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java @@ -18,11 +18,16 @@ package org.keycloak.services.util; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; 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.Provider; +import java.util.stream.Stream; /** * 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 { 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); - if (indent) { + if (Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))) { mapper.enable(SerializationFeature.INDENT_OUTPUT); } } diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java index 362dc6cfad..ab251bdfc6 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/KeycloakOnUndertow.java @@ -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.TestKeycloakSessionServletFilter; +import org.keycloak.testsuite.UndertowClientConnectionServletFilter; 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 DeployableContainerBill Burke - * @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() { - } -} diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestKeycloakSessionServletFilter.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/TestKeycloakSessionServletFilter.java deleted file mode 100644 index 629121c167..0000000000 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestKeycloakSessionServletFilter.java +++ /dev/null @@ -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 Pedro Igor - */ -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); - } -} \ No newline at end of file diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java index 9863dd3e50..5dfb48d654 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/TestPlatform.java @@ -17,21 +17,13 @@ package org.keycloak.testsuite; -import org.keycloak.common.util.Resteasy; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.platform.PlatformProvider; -import org.keycloak.services.resources.KeycloakApplication; - -import javax.servlet.ServletContext; public class TestPlatform implements PlatformProvider { @Override public void onStartup(Runnable startupHook) { startupHook.run(); - KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class); - ServletContext context = Resteasy.getContextData(ServletContext.class); - context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory()); } @Override diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowClientConnectionServletFilter.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowClientConnectionServletFilter.java new file mode 100755 index 0000000000..3fa1d6c5fe --- /dev/null +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowClientConnectionServletFilter.java @@ -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() { + } +} diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/KeycloakSessionServletFilter.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/KeycloakSessionServletFilter.java deleted file mode 100755 index 6dfe63246a..0000000000 --- a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/KeycloakSessionServletFilter.java +++ /dev/null @@ -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 Bill Burke - * @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() { - } -} diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildFlyClientConnectionServletFilter.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildFlyClientConnectionServletFilter.java new file mode 100755 index 0000000000..f682c73e4d --- /dev/null +++ b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildFlyClientConnectionServletFilter.java @@ -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(); + } + }; + } +} diff --git a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyPlatform.java b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyPlatform.java index ad7fda04b9..103e318076 100644 --- a/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyPlatform.java +++ b/wildfly/extensions/src/main/java/org/keycloak/provider/wildfly/WildflyPlatform.java @@ -17,13 +17,8 @@ package org.keycloak.provider.wildfly; -import org.keycloak.common.util.Resteasy; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.platform.PlatformProvider; import org.keycloak.services.ServicesLogger; -import org.keycloak.services.resources.KeycloakApplication; - -import javax.servlet.ServletContext; public class WildflyPlatform implements PlatformProvider { @@ -32,9 +27,6 @@ public class WildflyPlatform implements PlatformProvider { @Override public void onStartup(Runnable startupHook) { startupHook.run(); - KeycloakApplication keycloakApplication = Resteasy.getContextData(KeycloakApplication.class); - ServletContext context = Resteasy.getContextData(ServletContext.class); - context.setAttribute(KeycloakSessionFactory.class.getName(), keycloakApplication.getSessionFactory()); } @Override