From be32f8b1bfb4fa709aa48552a1bbfdcf9fab6299 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Tue, 26 Mar 2024 13:43:41 -0400 Subject: [PATCH] fix: limit the use of Resteasy to the KeycloakSession (#28150) * fix: limit the use of Resteasy to the KeycloakSession contextualizes other state to the KeycloakSession close: #28152 --- .../org/keycloak/common/util/Resteasy.java | 64 ++++++++++------- .../common/util/ResteasyProvider.java | 6 -- .../resteasy/CreateSessionHandler.java | 5 +- .../resteasy/QuarkusKeycloakContext.java | 23 ++---- .../resteasy/ResteasyVertxProvider.java | 47 +++--------- .../models/utils/KeycloakModelUtils.java | 4 ++ .../org/keycloak/models/KeycloakContext.java | 24 +++++-- .../email/EmailEventListenerProvider.java | 3 +- .../keycloak/protocol/saml/SamlService.java | 40 ++++------- .../services/DefaultKeycloakContext.java | 39 +++++++--- .../filters/AbstractRequestFilter.java | 71 ------------------- .../resources/KeycloakApplication.java | 2 - .../oidc/utils/RedirectUtilsTest.java | 3 +- .../oidc/utils/ResteasyTestProvider.java | 45 ------------ .../services/resteasy/HttpRequestImpl.java | 5 +- .../resteasy/ResteasyKeycloakContext.java | 5 +- .../org.keycloak.common.util.ResteasyProvider | 17 ----- .../AbstractTestRealmKeycloakTest.java | 4 +- .../testsuite/model/ResteasyNullProvider.java | 45 ------------ .../org.keycloak.common.util.ResteasyProvider | 17 ----- .../testsuite/JsonConfigProviderFactory.java | 5 +- .../keycloak/testsuite/Resteasy4Provider.java | 30 -------- .../testsuite/UndertowRequestFilter.java | 16 ++--- .../org.keycloak.common.util.ResteasyProvider | 1 - 24 files changed, 147 insertions(+), 374 deletions(-) delete mode 100644 services/src/main/java/org/keycloak/services/filters/AbstractRequestFilter.java delete mode 100644 services/src/test/java/org/keycloak/protocol/oidc/utils/ResteasyTestProvider.java delete mode 100644 services/src/test/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider delete mode 100644 testsuite/model/src/main/java/org/keycloak/testsuite/model/ResteasyNullProvider.java delete mode 100644 testsuite/model/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider delete mode 100644 testsuite/utils/src/main/java/org/keycloak/testsuite/Resteasy4Provider.java delete mode 100644 testsuite/utils/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider diff --git a/common/src/main/java/org/keycloak/common/util/Resteasy.java b/common/src/main/java/org/keycloak/common/util/Resteasy.java index 754b25dbb3..1dfa0fbd41 100644 --- a/common/src/main/java/org/keycloak/common/util/Resteasy.java +++ b/common/src/main/java/org/keycloak/common/util/Resteasy.java @@ -17,16 +17,13 @@ package org.keycloak.common.util; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.ServiceLoader; /** - *

Provides a layer of indirection to abstract invocations to Resteasy internal APIs. Making also possible to use different - * versions of Resteasy (e.g.: v3 and v4) depending on the stack that the server is running. - * - *

The methods herein provided are basically related with accessing context data from Resteasy, which changed in latest versions of Resteasy. - * - *

It is important to use this class when access to context data is necessary in order to avoid incompatibilities with future - * versions of Resteasy. + *

Provides a layer of indirection to abstract invocations to Resteasy internal APIs for obtaining the KeycloakSession * * @author Pedro Igor */ @@ -35,38 +32,55 @@ public final class Resteasy { private static ResteasyProvider provider; static { - provider = ServiceLoader.load(ResteasyProvider.class, Resteasy.class.getClassLoader()).iterator().next(); + Iterator iter = ServiceLoader.load(ResteasyProvider.class, Resteasy.class.getClassLoader()).iterator(); + if (iter.hasNext()) { + provider = iter.next(); + } } + private static final ThreadLocal, Object>> contextualData = new ThreadLocal, Object>>() { + @Override + protected Map, Object> initialValue() { + return new HashMap<>(1); + }; + }; + public static ResteasyProvider getProvider() { return provider; } /** * Push the given {@code instance} with type/key {@code type} to the Resteasy context associated with the current thread. - * - * @param type the type/key to associate the {@code instance} with + *
Should not be called directly + * + * @param type the type/key to associate the {@code instance} with * @param instance the instance */ - public static void pushContext(Class type, Object instance) { - provider.pushContext(type, instance); - } - - /** - * Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread. - * - * @param type the type/key to lookup - * @return the instance associated with the given {@code type} or null if non-existent. - */ - public static R getContextData(Class type) { - return provider.getContextData(type); + public static R pushContext(Class type, R instance) { + return (R) contextualData.get().put(type, instance); } /** * Clear the Resteasy context associated with the current thread. + *
Should not be called directly */ public static void clearContextData() { - provider.clearContextData(); + contextualData.remove(); + } + + /** + * Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread, or from the provider. + *
Should only be used to obtain the KeycloakSession + * + * @param type the type/key to lookup + * @return the instance associated with the given {@code type} or null if non-existent. + */ + public static R getContextData(Class type) { + R result = (R) contextualData.get().get(type); + if (result != null) { + return result; + } + return provider.getContextData(type); } /** @@ -74,9 +88,11 @@ public final class Resteasy { * * @param type the type/key to associate the {@code instance} with * @param instance the instance + * @deprecated use {@link #pushContext(Class, Object)} */ + @Deprecated public static void pushDefaultContextObject(Class type, Object instance) { - provider.pushDefaultContextObject(type, instance); + pushContext(type, instance); } } diff --git a/common/src/main/java/org/keycloak/common/util/ResteasyProvider.java b/common/src/main/java/org/keycloak/common/util/ResteasyProvider.java index 5cca0ffceb..993736cff6 100644 --- a/common/src/main/java/org/keycloak/common/util/ResteasyProvider.java +++ b/common/src/main/java/org/keycloak/common/util/ResteasyProvider.java @@ -4,10 +4,4 @@ public interface ResteasyProvider { R getContextData(Class type); - void pushDefaultContextObject(Class type, Object instance); - - void pushContext(Class type, Object instance); - - void clearContextData(); - } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/CreateSessionHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/CreateSessionHandler.java index 816637bcf5..b836a6f46c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/CreateSessionHandler.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/CreateSessionHandler.java @@ -24,6 +24,8 @@ import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.keycloak.common.util.Resteasy; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler; import io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext; @@ -42,8 +44,9 @@ public final class CreateSessionHandler implements ServerRestHandler, Transactio // make sure the session is created once KeycloakSession session = create(); routingContext.put(KeycloakSession.class.getName(), session); + // the CloseSessionFilter is needed because it runs sooner than this callback + // this is just a catch-all if the CloseSessionFilter doesn't get a chance to run context.registerCompletionCallback(this); - Resteasy.pushContext(KeycloakSession.class, session); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/QuarkusKeycloakContext.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/QuarkusKeycloakContext.java index df9f1e16e1..81dce23c52 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/QuarkusKeycloakContext.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/QuarkusKeycloakContext.java @@ -21,7 +21,6 @@ import io.vertx.core.http.HttpServerRequest; import org.jboss.resteasy.reactive.server.core.CurrentRequestManager; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.keycloak.common.ClientConnection; -import org.keycloak.common.util.Resteasy; import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpResponse; import org.keycloak.models.KeycloakSession; @@ -29,8 +28,6 @@ import org.keycloak.services.DefaultKeycloakContext; public final class QuarkusKeycloakContext extends DefaultKeycloakContext { - private ClientConnection clientConnection; - public QuarkusKeycloakContext(KeycloakSession session) { super(session); } @@ -46,22 +43,10 @@ public final class QuarkusKeycloakContext extends DefaultKeycloakContext { } @Override - public ClientConnection getConnection() { - if (clientConnection == null) { - ClientConnection contextualObject = Resteasy.getContextData(ClientConnection.class); - - if (contextualObject == null) { - ResteasyReactiveRequestContext requestContext = getResteasyReactiveRequestContext(); - HttpServerRequest serverRequest = requestContext.unwrap(HttpServerRequest.class); - clientConnection = new QuarkusClientConnection(serverRequest); - } else { - // in case the request is dispatched to a different thread like when using JAX-RS async responses - // in this case, we expect the client connection available as a contextual data - clientConnection = contextualObject; - } - } - - return clientConnection; + protected ClientConnection createClientConnection() { + ResteasyReactiveRequestContext requestContext = getResteasyReactiveRequestContext(); + HttpServerRequest serverRequest = requestContext.unwrap(HttpServerRequest.class); + return new QuarkusClientConnection(serverRequest); } private ResteasyReactiveRequestContext getResteasyReactiveRequestContext() { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/ResteasyVertxProvider.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/ResteasyVertxProvider.java index 3a4d190d20..3d3ea402d8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/ResteasyVertxProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/resteasy/ResteasyVertxProvider.java @@ -22,51 +22,24 @@ import io.quarkus.vertx.http.runtime.CurrentVertxRequest; import io.vertx.ext.web.RoutingContext; import org.keycloak.common.util.ResteasyProvider; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; +import jakarta.enterprise.context.ContextNotActiveException; + public class ResteasyVertxProvider implements ResteasyProvider { - private static final ThreadLocal, Object>> contextualData = new ThreadLocal, Object>>() { - @Override - protected Map, Object> initialValue() { - return new HashMap<>(); - }; - }; - @Override public R getContextData(Class type) { - R data = (R) contextualData.get().get(type); + return (R) getRoutingContext().map(c -> c.get(type.getName())).orElse(null); + } - if (data == null) { - RoutingContext contextData = Optional.ofNullable(Arc.container()) - .map(c -> c.instance(CurrentVertxRequest.class).get()).map(CurrentVertxRequest::getCurrent) - .orElse(null); - - if (contextData == null) { - return null; - } - - return (R) contextData.data().get(type.getName()); + private static Optional getRoutingContext() { + try { + return Optional.ofNullable(Arc.container()) + .map(c -> c.instance(CurrentVertxRequest.class).get()).map(CurrentVertxRequest::getCurrent); + } catch (ContextNotActiveException e) { + return Optional.empty(); } - - return data; - } - - @Override - public void pushContext(Class type, Object instance) { - contextualData.get().put(type, instance); - } - - @Override - public void pushDefaultContextObject(Class type, Object instance) { - pushContext(type, instance); - } - - @Override - public void clearContextData() { - contextualData.remove(); } } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 894d72e35b..bf704d43ed 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -25,6 +25,7 @@ import org.keycloak.broker.social.SocialIdentityProviderFactory; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.PemUtils; +import org.keycloak.common.util.Resteasy; import org.keycloak.common.util.SecretGenerator; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; @@ -375,12 +376,15 @@ public final class KeycloakModelUtils { V result; try (KeycloakSession session = factory.create()) { session.getTransactionManager().begin(); + KeycloakSession old = Resteasy.pushContext(KeycloakSession.class, session); try { cloneContextRealmClientToSession(context, session); result = callable.run(session); } catch (Throwable t) { session.getTransactionManager().setRollbackOnly(); throw t; + } finally { + Resteasy.pushContext(KeycloakSession.class, old); } } return result; 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 4a114db589..a29d97ca71 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakContext.java @@ -55,7 +55,15 @@ public interface KeycloakContext { HttpHeaders getRequestHeaders(); - T getContextObject(Class clazz); + /** + * Will always return null. You should not need access to a general context object. + * + * @deprecated + */ + @Deprecated(forRemoval = true) + default T getContextObject(Class clazz) { + return null; + } RealmModel getRealm(); @@ -68,17 +76,23 @@ public interface KeycloakContext { ClientConnection getConnection(); Locale resolveLocale(UserModel user); - + /** * Get current AuthenticationSessionModel, can be null out of the AuthenticationSession context. - * + * * @return current AuthenticationSessionModel or null */ - AuthenticationSessionModel getAuthenticationSession(); - + AuthenticationSessionModel getAuthenticationSession(); + void setAuthenticationSession(AuthenticationSessionModel authenticationSession); HttpRequest getHttpRequest(); HttpResponse getHttpResponse(); + + void setConnection(ClientConnection clientConnection); + + void setHttpRequest(HttpRequest httpRequest); + + void setHttpResponse(HttpResponse httpResponse); } diff --git a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java index c1207be3e6..97d1c190e2 100755 --- a/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java +++ b/services/src/main/java/org/keycloak/events/email/EmailEventListenerProvider.java @@ -20,7 +20,6 @@ package org.keycloak.events.email; import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction; import org.jboss.logging.Logger; -import org.keycloak.common.util.Resteasy; import org.keycloak.email.EmailException; import org.keycloak.email.EmailTemplateProvider; import org.keycloak.events.Event; @@ -88,7 +87,7 @@ public class EmailEventListenerProvider implements EventListenerProvider { context.setClient(client); } - Resteasy.pushContext(HttpRequest.class, request); + context.setHttpRequest(request); UserModel user = session.users().getUserById(realm, event.getUserId()); diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 92bb1cbc7c..dca9bb97a9 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -27,7 +27,6 @@ import org.keycloak.broker.saml.SAMLDataMarshaller; import org.keycloak.common.ClientConnection; import org.keycloak.common.VerificationException; import org.keycloak.common.util.PemUtils; -import org.keycloak.common.util.Resteasy; import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.KeyStatus; @@ -56,7 +55,6 @@ import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeyManager; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.KeycloakUriInfo; import org.keycloak.models.RealmModel; import org.keycloak.models.SingleUseObjectProvider; @@ -197,7 +195,7 @@ public class SamlService extends AuthorizationEndpointBase { } return null; } - + protected boolean isDestinationRequired() { return true; } @@ -404,7 +402,7 @@ public class SamlService extends AuthorizationEndpointBase { ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("saml-artifact-pool"); - ArtifactResolutionRunnable artifactResolutionRunnable = new ArtifactResolutionRunnable(getBindingType(), asyncResponse, doc, clientArtifactBindingURI, relayState, session.getContext().getConnection()); + ArtifactResolutionRunnable artifactResolutionRunnable = new ArtifactResolutionRunnable(getBindingType(), asyncResponse, doc, clientArtifactBindingURI, relayState); ScheduledTaskRunner task = new ScheduledTaskRunner(session.getKeycloakSessionFactory(), artifactResolutionRunnable); executor.execute(task); @@ -935,7 +933,7 @@ public class SamlService extends AuthorizationEndpointBase { RealmsResource.protocolUrl(uriInfo).path(SamlService.ARTIFACT_RESOLUTION_SERVICE_PATH) .build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL), RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString(), - true, + true, signingKeys); } catch (Exception ex) { logger.error("Cannot generate IdP metadata", ex); @@ -1085,7 +1083,7 @@ public class SamlService extends AuthorizationEndpointBase { "ArtifactResolve message: %s", DocumentUtil.asString(soapBodyContents)); return Soap.createFault().reason("").detail("").build(); } - + try { return artifactResolve(artifactResolveType, samlDocumentHolder); } catch (Exception e) { @@ -1168,7 +1166,7 @@ public class SamlService extends AuthorizationEndpointBase { logger.errorf("Artifact to resolve was null"); return emptyArtifactResponseMessage(artifactResolveMessage, null, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.getUri()); } - + ArtifactResolver artifactResolver = getArtifactResolver(artifact); if (artifactResolver == null) { @@ -1252,7 +1250,7 @@ public class SamlService extends AuthorizationEndpointBase { return artifactResponseMessage(artifactResolveMessage, artifactResponseDocument, clientModel); } - + private Response emptyArtifactResponseMessage(ArtifactResolveType artifactResolveMessage, ClientModel clientModel) throws ProcessingException, ConfigurationException { return emptyArtifactResponseMessage(artifactResolveMessage, clientModel, JBossSAMLURIConstants.STATUS_SUCCESS.getUri()); } @@ -1271,7 +1269,7 @@ public class SamlService extends AuthorizationEndpointBase { return artifactResponseMessage(artifactResolveMessage, artifactResponseDocument, clientModel); } - + private Response artifactResponseMessage(ArtifactResolveType artifactResolveMessage, Document artifactResponseDocument, ClientModel clientModel) throws ProcessingException, ConfigurationException { // Add "inResponseTo" to artifactResponse if (artifactResolveMessage.getID() != null && !artifactResolveMessage.getID().trim().isEmpty()){ @@ -1279,7 +1277,7 @@ public class SamlService extends AuthorizationEndpointBase { artifactResponseElement.setAttribute("InResponseTo", artifactResolveMessage.getID()); } JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(session); - + if (clientModel != null) { SamlClient samlClient = new SamlClient(clientModel); @@ -1360,36 +1358,28 @@ public class SamlService extends AuthorizationEndpointBase { private URI clientArtifactBindingURI; private String relayState; private Document doc; - private UriInfo uri; private String realmId; private ClientConnection connection; private String bindingType; - public ArtifactResolutionRunnable(String bindingType, AsyncResponse asyncResponse, Document doc, URI clientArtifactBindingURI, String relayState, ClientConnection connection){ + public ArtifactResolutionRunnable(String bindingType, AsyncResponse asyncResponse, Document doc, URI clientArtifactBindingURI, String relayState){ this.asyncResponse = asyncResponse; this.doc = doc; this.clientArtifactBindingURI = clientArtifactBindingURI; this.relayState = relayState; - this.uri = session.getContext().getUri(); this.realmId = realm.getId(); - this.connection = connection; + this.connection = session.getContext().getConnection(); this.bindingType = bindingType; this.request = session.getContext().getHttpRequest(); this.response = session.getContext().getHttpResponse(); } - + @Override public void run(KeycloakSession session){ // Initialize context - Resteasy.pushContext(UriInfo.class, uri); - - KeycloakTransaction tx = session.getTransactionManager(); - Resteasy.pushContext(KeycloakTransaction.class, tx); - - Resteasy.pushContext(KeycloakSession.class, session); - Resteasy.pushContext(HttpRequest.class, request); - Resteasy.pushContext(HttpResponse.class, response); - Resteasy.pushContext(ClientConnection.class, connection); + session.getContext().setHttpRequest(request); + session.getContext().setHttpResponse(response); + session.getContext().setConnection(connection); RealmManager realmManager = new RealmManager(session); RealmModel realm = realmManager.getRealm(realmId); @@ -1424,7 +1414,7 @@ public class SamlService extends AuthorizationEndpointBase { if (logger.isTraceEnabled()) { logger.tracef("Resolved object: %s" + DocumentUtil.asString(samlDoc.getSamlDocument())); } - + ArtifactResponseType art = (ArtifactResponseType) samlDoc.getSamlObject(); if (art.getAny() == null) { diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index a41ae60fb5..628aab4fea 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -19,7 +19,6 @@ package org.keycloak.services; import jakarta.ws.rs.core.HttpHeaders; import org.keycloak.common.ClientConnection; -import org.keycloak.common.util.Resteasy; import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpResponse; import org.keycloak.locale.LocaleSelectorProvider; @@ -53,6 +52,7 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { private AuthenticationSessionModel authenticationSession; private HttpRequest request; private HttpResponse response; + private ClientConnection clientConnection; public DefaultKeycloakContext(KeycloakSession session) { this.session = session; @@ -96,11 +96,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { return getHttpRequest().getHttpHeaders(); } - @Override - public T getContextObject(Class clazz) { - return Resteasy.getContextData(clazz); - } - @Override public RealmModel getRealm() { return realm; @@ -124,7 +119,15 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { @Override public ClientConnection getConnection() { - return getContextObject(ClientConnection.class); + if (clientConnection == null) { + synchronized (this) { + if (clientConnection == null) { + clientConnection = createClientConnection(); + } + } + } + + return clientConnection; } @Override @@ -146,7 +149,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { public HttpRequest getHttpRequest() { if (request == null) { synchronized (this) { - request = getContextObject(HttpRequest.class); if (request == null) { request = createHttpRequest(); } @@ -160,7 +162,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { public HttpResponse getHttpResponse() { if (response == null) { synchronized (this) { - response = getContextObject(HttpResponse.class); if (response == null) { response = createHttpResponse(); } @@ -170,6 +171,10 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { return response; } + protected ClientConnection createClientConnection() { + return null; + } + protected abstract HttpRequest createHttpRequest(); protected abstract HttpResponse createHttpResponse(); @@ -177,4 +182,20 @@ public abstract class DefaultKeycloakContext implements KeycloakContext { protected KeycloakSession getSession() { return session; } + + @Override + public void setConnection(ClientConnection clientConnection) { + this.clientConnection = clientConnection; + } + + @Override + public void setHttpRequest(HttpRequest httpRequest) { + this.request = httpRequest; + } + + @Override + public void setHttpResponse(HttpResponse httpResponse) { + this.response = httpResponse; + } + } diff --git a/services/src/main/java/org/keycloak/services/filters/AbstractRequestFilter.java b/services/src/main/java/org/keycloak/services/filters/AbstractRequestFilter.java deleted file mode 100644 index 0de439e403..0000000000 --- a/services/src/main/java/org/keycloak/services/filters/AbstractRequestFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -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 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 AbstractRequestFilter { - - protected void filter(ClientConnection clientConnection, Consumer next) { - KeycloakSessionFactory sessionFactory = 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 { - if (isAutoClose()) { - close(session); - } - } - } - - protected KeycloakSessionFactory getSessionFactory() { - return KeycloakApplication.getSessionFactory(); - } - - protected void close(KeycloakSession session) { - session.close(); - } - - /** - *

Indicates whether or not resources should be close as part of the execution of the {@link #filter(ClientConnection, Consumer)} - * method. - * - * @return true if resources should be close automatically. Otherwise, false. - */ - protected boolean isAutoClose() { - return true; - } -} \ No newline at end of file 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 e06b86b70e..ffd7a72e95 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -19,7 +19,6 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.common.crypto.CryptoIntegration; -import org.keycloak.common.util.Resteasy; import org.keycloak.config.ConfigProviderFactory; import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.exportimport.ExportImportManager; @@ -76,7 +75,6 @@ public abstract class KeycloakApplication extends Application { try { logger.debugv("PlatformProvider: {0}", platform.getClass().getName()); - logger.debugv("RestEasy provider: {0}", Resteasy.getProvider().getClass().getName()); loadConfig(); diff --git a/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java b/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java index ec37ab871d..d518750369 100644 --- a/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java +++ b/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java @@ -20,7 +20,6 @@ import java.net.URI; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jboss.resteasy.core.ResteasyContext; import org.jboss.resteasy.mock.MockHttpRequest; import org.junit.Assert; import org.junit.BeforeClass; @@ -46,12 +45,12 @@ public class RedirectUtilsTest { @BeforeClass public static void beforeClass() { HttpRequest httpRequest = new HttpRequestImpl(MockHttpRequest.create("GET", URI.create("https://keycloak.org/"), URI.create("https://keycloak.org"))); - ResteasyContext.getContextDataMap().put(HttpRequest.class, httpRequest); Profile.defaults(); CryptoIntegration.init(CryptoProvider.class.getClassLoader()); ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory(); sessionFactory.init(); session = new ResteasyKeycloakSession(sessionFactory); + session.getContext().setHttpRequest(httpRequest); } @Test diff --git a/services/src/test/java/org/keycloak/protocol/oidc/utils/ResteasyTestProvider.java b/services/src/test/java/org/keycloak/protocol/oidc/utils/ResteasyTestProvider.java deleted file mode 100644 index 5d4f5d705e..0000000000 --- a/services/src/test/java/org/keycloak/protocol/oidc/utils/ResteasyTestProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 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.protocol.oidc.utils; - -import org.jboss.resteasy.core.ResteasyContext; -import org.keycloak.common.util.ResteasyProvider; - -/** - *

Resteasy provider to be used for the utils class.

- * @author rmartinc - */ -public class ResteasyTestProvider implements ResteasyProvider { - - @Override - public R getContextData(Class type) { - return ResteasyContext.getContextData(type); - } - - @Override - public void pushDefaultContextObject(Class type, Object instance) { - } - - @Override - public void pushContext(Class type, Object instance) { - } - - @Override - public void clearContextData() { - } -} diff --git a/services/src/test/java/org/keycloak/services/resteasy/HttpRequestImpl.java b/services/src/test/java/org/keycloak/services/resteasy/HttpRequestImpl.java index f968abeab4..04310c924f 100644 --- a/services/src/test/java/org/keycloak/services/resteasy/HttpRequestImpl.java +++ b/services/src/test/java/org/keycloak/services/resteasy/HttpRequestImpl.java @@ -31,9 +31,10 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.Providers; + +import org.jboss.resteasy.core.ResteasyContext; import org.jboss.resteasy.reactive.server.multipart.FormValue; import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput; -import org.keycloak.common.util.Resteasy; import org.keycloak.http.FormPartValue; import org.keycloak.http.HttpRequest; import org.keycloak.services.FormPartValueImpl; @@ -75,7 +76,7 @@ public class HttpRequestImpl implements HttpRequest { return new MultivaluedHashMap<>(); } - Providers providers = Resteasy.getContextData(Providers.class); + Providers providers = ResteasyContext.getContextData(Providers.class); MessageBodyReader multiPartProvider = providers.getMessageBodyReader( MultipartFormDataInput.class, null, null, MULTIPART_FORM_DATA_TYPE); MultipartFormDataInput inputs = multiPartProvider diff --git a/services/src/test/java/org/keycloak/services/resteasy/ResteasyKeycloakContext.java b/services/src/test/java/org/keycloak/services/resteasy/ResteasyKeycloakContext.java index 1117a2b9a9..13923801b0 100644 --- a/services/src/test/java/org/keycloak/services/resteasy/ResteasyKeycloakContext.java +++ b/services/src/test/java/org/keycloak/services/resteasy/ResteasyKeycloakContext.java @@ -17,6 +17,7 @@ package org.keycloak.services.resteasy; +import org.jboss.resteasy.core.ResteasyContext; import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpResponse; import org.keycloak.models.KeycloakSession; @@ -30,12 +31,12 @@ public class ResteasyKeycloakContext extends DefaultKeycloakContext { @Override protected HttpRequest createHttpRequest() { - return new HttpRequestImpl(getContextObject(org.jboss.resteasy.spi.HttpRequest.class)); + return new HttpRequestImpl(ResteasyContext.getContextData(org.jboss.resteasy.spi.HttpRequest.class)); } @Override protected HttpResponse createHttpResponse() { - return new HttpResponseImpl(getContextObject(org.jboss.resteasy.spi.HttpResponse.class)); + return new HttpResponseImpl(ResteasyContext.getContextData(org.jboss.resteasy.spi.HttpResponse.class)); } } diff --git a/services/src/test/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider b/services/src/test/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider deleted file mode 100644 index d8ffb8a92f..0000000000 --- a/services/src/test/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2023 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. - -org.keycloak.protocol.oidc.utils.ResteasyTestProvider diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java index be32cecc65..0d8f85e43f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java @@ -44,7 +44,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; */ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest { public static final String TEST_REALM_NAME = "test"; - + protected RealmResource testRealm() { return adminClient.realm(TEST_REALM_NAME); } @@ -128,7 +128,7 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest /** KEYCLOAK-12065 Inherit Client Connection from parent session **/ public static KeycloakSession inheritClientConnection(KeycloakSession parentSession, KeycloakSession currentSession) { - Resteasy.pushContext(ClientConnection.class, parentSession.getContext().getConnection()); + currentSession.getContext().setConnection(parentSession.getContext().getConnection()); return currentSession; } } diff --git a/testsuite/model/src/main/java/org/keycloak/testsuite/model/ResteasyNullProvider.java b/testsuite/model/src/main/java/org/keycloak/testsuite/model/ResteasyNullProvider.java deleted file mode 100644 index 86d15f22a7..0000000000 --- a/testsuite/model/src/main/java/org/keycloak/testsuite/model/ResteasyNullProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2021 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.model; - -import org.keycloak.common.util.ResteasyProvider; - -/** - * @author Martin Kanis - */ -public class ResteasyNullProvider implements ResteasyProvider { - - @Override - public R getContextData(Class type) { - return null; - } - - @Override - public void pushDefaultContextObject(Class type, Object instance) { - - } - - @Override - public void pushContext(Class type, Object instance) { - - } - - @Override - public void clearContextData() { - - } -} diff --git a/testsuite/model/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider b/testsuite/model/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider deleted file mode 100644 index 5c7f0b9fbe..0000000000 --- a/testsuite/model/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2021 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. - -org.keycloak.testsuite.model.ResteasyNullProvider \ No newline at end of file diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/JsonConfigProviderFactory.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/JsonConfigProviderFactory.java index fb560af674..25d6328cf8 100644 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/JsonConfigProviderFactory.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/JsonConfigProviderFactory.java @@ -23,7 +23,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; import jakarta.servlet.ServletContext; -import org.keycloak.common.util.Resteasy; + +import org.jboss.resteasy.core.ResteasyContext; import org.keycloak.common.util.SystemEnvProperties; import org.keycloak.util.JsonSerialization; @@ -38,7 +39,7 @@ public class JsonConfigProviderFactory extends org.keycloak.services.util.JsonCo private Map getPropertyOverrides() { - ServletContext context = Resteasy.getContextData(ServletContext.class); + ServletContext context = ResteasyContext.getContextData(ServletContext.class); Map propertyOverridesMap = new HashMap<>(); String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES); diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/Resteasy4Provider.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/Resteasy4Provider.java deleted file mode 100644 index ec26504bfb..0000000000 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/Resteasy4Provider.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.keycloak.testsuite; - -import org.jboss.resteasy.core.ResteasyContext; -import org.jboss.resteasy.spi.ResteasyProviderFactory; -import org.keycloak.common.util.ResteasyProvider; - -public class Resteasy4Provider implements ResteasyProvider { - - @Override - public R getContextData(Class type) { - return ResteasyProviderFactory.getInstance().getContextData(type); - } - - @Override - public void pushDefaultContextObject(Class type, Object instance) { - ResteasyProviderFactory.getInstance().getContextData(org.jboss.resteasy.spi.Dispatcher.class).getDefaultContextObjects() - .put(type, instance); - } - - @Override - public void pushContext(Class type, Object instance) { - ResteasyContext.pushContext(type, instance); - } - - @Override - public void clearContextData() { - ResteasyContext.clearContextData(); - } - -} diff --git a/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowRequestFilter.java b/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowRequestFilter.java index f50919c4fc..f394c7d078 100755 --- a/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowRequestFilter.java +++ b/testsuite/utils/src/main/java/org/keycloak/testsuite/UndertowRequestFilter.java @@ -26,11 +26,13 @@ import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; +import org.jboss.resteasy.core.ResteasyContext; import org.keycloak.common.ClientConnection; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.services.filters.AbstractRequestFilter; +import org.keycloak.models.utils.KeycloakModelUtils; -public class UndertowRequestFilter extends AbstractRequestFilter implements Filter { +public class UndertowRequestFilter implements Filter { private final KeycloakSessionFactory factory; @@ -44,8 +46,11 @@ public class UndertowRequestFilter extends AbstractRequestFilter implements Filt servletRequest.setCharacterEncoding("UTF-8"); final HttpServletRequest request = (HttpServletRequest) servletRequest; - filter(createClientConnection(request), (session) -> { + ClientConnection connection = createClientConnection(request); + KeycloakModelUtils.runJobInTransaction(factory, session -> { try { + ResteasyContext.pushContext(KeycloakSession.class, session); + session.getContext().setConnection(connection); filterChain.doFilter(servletRequest, servletResponse); } catch (Exception e) { throw new RuntimeException(e); @@ -88,11 +93,6 @@ public class UndertowRequestFilter extends AbstractRequestFilter implements Filt }; } - @Override - protected KeycloakSessionFactory getSessionFactory() { - return this.factory; - } - @Override public void init(FilterConfig filterConfig) { } diff --git a/testsuite/utils/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider b/testsuite/utils/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider deleted file mode 100644 index 333127ea74..0000000000 --- a/testsuite/utils/src/main/resources/META-INF/services/org.keycloak.common.util.ResteasyProvider +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.testsuite.Resteasy4Provider \ No newline at end of file