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
This commit is contained in:
Steven Hawkins 2024-03-26 13:43:41 -04:00 committed by GitHub
parent fa1571f231
commit be32f8b1bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 147 additions and 374 deletions

View file

@ -17,16 +17,13 @@
package org.keycloak.common.util; package org.keycloak.common.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader; import java.util.ServiceLoader;
/** /**
* <p>Provides a layer of indirection to abstract invocations to Resteasy internal APIs. Making also possible to use different * <p>Provides a layer of indirection to abstract invocations to Resteasy internal APIs for obtaining the KeycloakSession
* versions of Resteasy (e.g.: v3 and v4) depending on the stack that the server is running.
*
* <p>The methods herein provided are basically related with accessing context data from Resteasy, which changed in latest versions of Resteasy.
*
* <p>It is important to use this class when access to context data is necessary in order to avoid incompatibilities with future
* versions of Resteasy.
* *
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@ -35,8 +32,18 @@ public final class Resteasy {
private static ResteasyProvider provider; private static ResteasyProvider provider;
static { static {
provider = ServiceLoader.load(ResteasyProvider.class, Resteasy.class.getClassLoader()).iterator().next(); Iterator<ResteasyProvider> iter = ServiceLoader.load(ResteasyProvider.class, Resteasy.class.getClassLoader()).iterator();
if (iter.hasNext()) {
provider = iter.next();
} }
}
private static final ThreadLocal<Map<Class<?>, Object>> contextualData = new ThreadLocal<Map<Class<?>, Object>>() {
@Override
protected Map<Class<?>, Object> initialValue() {
return new HashMap<>(1);
};
};
public static ResteasyProvider getProvider() { public static ResteasyProvider getProvider() {
return provider; return provider;
@ -44,29 +51,36 @@ public final class Resteasy {
/** /**
* Push the given {@code instance} with type/key {@code type} to the Resteasy context associated with the current thread. * Push the given {@code instance} with type/key {@code type} to the Resteasy context associated with the current thread.
* <br>Should not be called directly
* *
* @param type the type/key to associate the {@code instance} with * @param type the type/key to associate the {@code instance} with
* @param instance the instance * @param instance the instance
*/ */
public static void pushContext(Class type, Object instance) { public static <R> R pushContext(Class<R> type, R instance) {
provider.pushContext(type, instance); return (R) contextualData.get().put(type, instance);
} }
/** /**
* Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread. * Clear the Resteasy context associated with the current thread.
* <br>Should not be called directly
*/
public static void 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.
* <br> Should only be used to obtain the KeycloakSession
* *
* @param type the type/key to lookup * @param type the type/key to lookup
* @return the instance associated with the given {@code type} or null if non-existent. * @return the instance associated with the given {@code type} or null if non-existent.
*/ */
public static <R> R getContextData(Class<R> type) { public static <R> R getContextData(Class<R> type) {
return provider.getContextData(type); R result = (R) contextualData.get().get(type);
if (result != null) {
return result;
} }
return provider.getContextData(type);
/**
* Clear the Resteasy context associated with the current thread.
*/
public static void clearContextData() {
provider.clearContextData();
} }
/** /**
@ -74,9 +88,11 @@ public final class Resteasy {
* *
* @param type the type/key to associate the {@code instance} with * @param type the type/key to associate the {@code instance} with
* @param instance the instance * @param instance the instance
* @deprecated use {@link #pushContext(Class, Object)}
*/ */
@Deprecated
public static void pushDefaultContextObject(Class type, Object instance) { public static void pushDefaultContextObject(Class type, Object instance) {
provider.pushDefaultContextObject(type, instance); pushContext(type, instance);
} }
} }

View file

@ -4,10 +4,4 @@ public interface ResteasyProvider {
<R> R getContextData(Class<R> type); <R> R getContextData(Class<R> type);
void pushDefaultContextObject(Class type, Object instance);
void pushContext(Class type, Object instance);
void clearContextData();
} }

View file

@ -24,6 +24,8 @@ import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.keycloak.common.util.Resteasy; import org.keycloak.common.util.Resteasy;
import org.keycloak.models.KeycloakSession; 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 org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
import io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext; 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 // make sure the session is created once
KeycloakSession session = create(); KeycloakSession session = create();
routingContext.put(KeycloakSession.class.getName(), session); 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); context.registerCompletionCallback(this);
Resteasy.pushContext(KeycloakSession.class, session);
} }
} }

View file

@ -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.CurrentRequestManager;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Resteasy;
import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpRequest;
import org.keycloak.http.HttpResponse; import org.keycloak.http.HttpResponse;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -29,8 +28,6 @@ import org.keycloak.services.DefaultKeycloakContext;
public final class QuarkusKeycloakContext extends DefaultKeycloakContext { public final class QuarkusKeycloakContext extends DefaultKeycloakContext {
private ClientConnection clientConnection;
public QuarkusKeycloakContext(KeycloakSession session) { public QuarkusKeycloakContext(KeycloakSession session) {
super(session); super(session);
} }
@ -46,22 +43,10 @@ public final class QuarkusKeycloakContext extends DefaultKeycloakContext {
} }
@Override @Override
public ClientConnection getConnection() { protected ClientConnection createClientConnection() {
if (clientConnection == null) {
ClientConnection contextualObject = Resteasy.getContextData(ClientConnection.class);
if (contextualObject == null) {
ResteasyReactiveRequestContext requestContext = getResteasyReactiveRequestContext(); ResteasyReactiveRequestContext requestContext = getResteasyReactiveRequestContext();
HttpServerRequest serverRequest = requestContext.unwrap(HttpServerRequest.class); HttpServerRequest serverRequest = requestContext.unwrap(HttpServerRequest.class);
clientConnection = new QuarkusClientConnection(serverRequest); return 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;
} }
private ResteasyReactiveRequestContext getResteasyReactiveRequestContext() { private ResteasyReactiveRequestContext getResteasyReactiveRequestContext() {

View file

@ -22,51 +22,24 @@ import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import org.keycloak.common.util.ResteasyProvider; import org.keycloak.common.util.ResteasyProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import jakarta.enterprise.context.ContextNotActiveException;
public class ResteasyVertxProvider implements ResteasyProvider { public class ResteasyVertxProvider implements ResteasyProvider {
private static final ThreadLocal<Map<Class<?>, Object>> contextualData = new ThreadLocal<Map<Class<?>, Object>>() {
@Override
protected Map<Class<?>, Object> initialValue() {
return new HashMap<>();
};
};
@Override @Override
public <R> R getContextData(Class<R> type) { public <R> R getContextData(Class<R> 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<RoutingContext> 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();
} }
} }

View file

@ -25,6 +25,7 @@ import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.Resteasy;
import org.keycloak.common.util.SecretGenerator; import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentModel;
@ -375,12 +376,15 @@ public final class KeycloakModelUtils {
V result; V result;
try (KeycloakSession session = factory.create()) { try (KeycloakSession session = factory.create()) {
session.getTransactionManager().begin(); session.getTransactionManager().begin();
KeycloakSession old = Resteasy.pushContext(KeycloakSession.class, session);
try { try {
cloneContextRealmClientToSession(context, session); cloneContextRealmClientToSession(context, session);
result = callable.run(session); result = callable.run(session);
} catch (Throwable t) { } catch (Throwable t) {
session.getTransactionManager().setRollbackOnly(); session.getTransactionManager().setRollbackOnly();
throw t; throw t;
} finally {
Resteasy.pushContext(KeycloakSession.class, old);
} }
} }
return result; return result;

View file

@ -55,7 +55,15 @@ public interface KeycloakContext {
HttpHeaders getRequestHeaders(); HttpHeaders getRequestHeaders();
<T> T getContextObject(Class<T> clazz); /**
* Will always return null. You should not need access to a general context object.
*
* @deprecated
*/
@Deprecated(forRemoval = true)
default <T> T getContextObject(Class<T> clazz) {
return null;
}
RealmModel getRealm(); RealmModel getRealm();
@ -81,4 +89,10 @@ public interface KeycloakContext {
HttpRequest getHttpRequest(); HttpRequest getHttpRequest();
HttpResponse getHttpResponse(); HttpResponse getHttpResponse();
void setConnection(ClientConnection clientConnection);
void setHttpRequest(HttpRequest httpRequest);
void setHttpResponse(HttpResponse httpResponse);
} }

View file

@ -20,7 +20,6 @@ package org.keycloak.events.email;
import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction; import static org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.util.Resteasy;
import org.keycloak.email.EmailException; import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider; import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.events.Event; import org.keycloak.events.Event;
@ -88,7 +87,7 @@ public class EmailEventListenerProvider implements EventListenerProvider {
context.setClient(client); context.setClient(client);
} }
Resteasy.pushContext(HttpRequest.class, request); context.setHttpRequest(request);
UserModel user = session.users().getUserById(realm, event.getUserId()); UserModel user = session.users().getUserById(realm, event.getUserId());

View file

@ -27,7 +27,6 @@ import org.keycloak.broker.saml.SAMLDataMarshaller;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.common.util.Resteasy;
import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus; import org.keycloak.crypto.KeyStatus;
@ -56,7 +55,6 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager; import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.KeycloakUriInfo; import org.keycloak.models.KeycloakUriInfo;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.SingleUseObjectProvider;
@ -404,7 +402,7 @@ public class SamlService extends AuthorizationEndpointBase {
ExecutorService executor = session.getProvider(ExecutorsProvider.class).getExecutor("saml-artifact-pool"); 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); ScheduledTaskRunner task = new ScheduledTaskRunner(session.getKeycloakSessionFactory(), artifactResolutionRunnable);
executor.execute(task); executor.execute(task);
@ -1360,36 +1358,28 @@ public class SamlService extends AuthorizationEndpointBase {
private URI clientArtifactBindingURI; private URI clientArtifactBindingURI;
private String relayState; private String relayState;
private Document doc; private Document doc;
private UriInfo uri;
private String realmId; private String realmId;
private ClientConnection connection; private ClientConnection connection;
private String bindingType; 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.asyncResponse = asyncResponse;
this.doc = doc; this.doc = doc;
this.clientArtifactBindingURI = clientArtifactBindingURI; this.clientArtifactBindingURI = clientArtifactBindingURI;
this.relayState = relayState; this.relayState = relayState;
this.uri = session.getContext().getUri();
this.realmId = realm.getId(); this.realmId = realm.getId();
this.connection = connection; this.connection = session.getContext().getConnection();
this.bindingType = bindingType; this.bindingType = bindingType;
this.request = session.getContext().getHttpRequest(); this.request = session.getContext().getHttpRequest();
this.response = session.getContext().getHttpResponse(); this.response = session.getContext().getHttpResponse();
} }
@Override
public void run(KeycloakSession session){ public void run(KeycloakSession session){
// Initialize context // Initialize context
Resteasy.pushContext(UriInfo.class, uri); session.getContext().setHttpRequest(request);
session.getContext().setHttpResponse(response);
KeycloakTransaction tx = session.getTransactionManager(); session.getContext().setConnection(connection);
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);
RealmManager realmManager = new RealmManager(session); RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.getRealm(realmId); RealmModel realm = realmManager.getRealm(realmId);

View file

@ -19,7 +19,6 @@ package org.keycloak.services;
import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.HttpHeaders;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Resteasy;
import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpRequest;
import org.keycloak.http.HttpResponse; import org.keycloak.http.HttpResponse;
import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.locale.LocaleSelectorProvider;
@ -53,6 +52,7 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
private AuthenticationSessionModel authenticationSession; private AuthenticationSessionModel authenticationSession;
private HttpRequest request; private HttpRequest request;
private HttpResponse response; private HttpResponse response;
private ClientConnection clientConnection;
public DefaultKeycloakContext(KeycloakSession session) { public DefaultKeycloakContext(KeycloakSession session) {
this.session = session; this.session = session;
@ -96,11 +96,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
return getHttpRequest().getHttpHeaders(); return getHttpRequest().getHttpHeaders();
} }
@Override
public <T> T getContextObject(Class<T> clazz) {
return Resteasy.getContextData(clazz);
}
@Override @Override
public RealmModel getRealm() { public RealmModel getRealm() {
return realm; return realm;
@ -124,7 +119,15 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
@Override @Override
public ClientConnection getConnection() { public ClientConnection getConnection() {
return getContextObject(ClientConnection.class); if (clientConnection == null) {
synchronized (this) {
if (clientConnection == null) {
clientConnection = createClientConnection();
}
}
}
return clientConnection;
} }
@Override @Override
@ -146,7 +149,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
public HttpRequest getHttpRequest() { public HttpRequest getHttpRequest() {
if (request == null) { if (request == null) {
synchronized (this) { synchronized (this) {
request = getContextObject(HttpRequest.class);
if (request == null) { if (request == null) {
request = createHttpRequest(); request = createHttpRequest();
} }
@ -160,7 +162,6 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
public HttpResponse getHttpResponse() { public HttpResponse getHttpResponse() {
if (response == null) { if (response == null) {
synchronized (this) { synchronized (this) {
response = getContextObject(HttpResponse.class);
if (response == null) { if (response == null) {
response = createHttpResponse(); response = createHttpResponse();
} }
@ -170,6 +171,10 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
return response; return response;
} }
protected ClientConnection createClientConnection() {
return null;
}
protected abstract HttpRequest createHttpRequest(); protected abstract HttpRequest createHttpRequest();
protected abstract HttpResponse createHttpResponse(); protected abstract HttpResponse createHttpResponse();
@ -177,4 +182,20 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
protected KeycloakSession getSession() { protected KeycloakSession getSession() {
return session; 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;
}
} }

View file

@ -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<KeycloakSession> 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();
}
/**
* <p>Indicates whether or not resources should be close as part of the execution of the {@link #filter(ClientConnection, Consumer)}
* method.
*
* @return true if resources should be close automatically. Otherwise, false.
*/
protected boolean isAutoClose() {
return true;
}
}

View file

@ -19,7 +19,6 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.Resteasy;
import org.keycloak.config.ConfigProviderFactory; import org.keycloak.config.ConfigProviderFactory;
import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.exportimport.ExportImportConfig;
import org.keycloak.exportimport.ExportImportManager; import org.keycloak.exportimport.ExportImportManager;
@ -76,7 +75,6 @@ public abstract class KeycloakApplication extends Application {
try { try {
logger.debugv("PlatformProvider: {0}", platform.getClass().getName()); logger.debugv("PlatformProvider: {0}", platform.getClass().getName());
logger.debugv("RestEasy provider: {0}", Resteasy.getProvider().getClass().getName());
loadConfig(); loadConfig();

View file

@ -20,7 +20,6 @@ import java.net.URI;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpRequest;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -46,12 +45,12 @@ public class RedirectUtilsTest {
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
HttpRequest httpRequest = new HttpRequestImpl(MockHttpRequest.create("GET", URI.create("https://keycloak.org/"), URI.create("https://keycloak.org"))); 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(); Profile.defaults();
CryptoIntegration.init(CryptoProvider.class.getClassLoader()); CryptoIntegration.init(CryptoProvider.class.getClassLoader());
ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory(); ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory();
sessionFactory.init(); sessionFactory.init();
session = new ResteasyKeycloakSession(sessionFactory); session = new ResteasyKeycloakSession(sessionFactory);
session.getContext().setHttpRequest(httpRequest);
} }
@Test @Test

View file

@ -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;
/**
* <p>Resteasy provider to be used for the utils class.</p>
* @author rmartinc
*/
public class ResteasyTestProvider implements ResteasyProvider {
@Override
public <R> R getContextData(Class<R> 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() {
}
}

View file

@ -31,9 +31,10 @@ import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.MessageBodyReader; import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Providers; 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.FormValue;
import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput; import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput;
import org.keycloak.common.util.Resteasy;
import org.keycloak.http.FormPartValue; import org.keycloak.http.FormPartValue;
import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpRequest;
import org.keycloak.services.FormPartValueImpl; import org.keycloak.services.FormPartValueImpl;
@ -75,7 +76,7 @@ public class HttpRequestImpl implements HttpRequest {
return new MultivaluedHashMap<>(); return new MultivaluedHashMap<>();
} }
Providers providers = Resteasy.getContextData(Providers.class); Providers providers = ResteasyContext.getContextData(Providers.class);
MessageBodyReader<MultipartFormDataInput> multiPartProvider = providers.getMessageBodyReader( MessageBodyReader<MultipartFormDataInput> multiPartProvider = providers.getMessageBodyReader(
MultipartFormDataInput.class, null, null, MULTIPART_FORM_DATA_TYPE); MultipartFormDataInput.class, null, null, MULTIPART_FORM_DATA_TYPE);
MultipartFormDataInput inputs = multiPartProvider MultipartFormDataInput inputs = multiPartProvider

View file

@ -17,6 +17,7 @@
package org.keycloak.services.resteasy; package org.keycloak.services.resteasy;
import org.jboss.resteasy.core.ResteasyContext;
import org.keycloak.http.HttpRequest; import org.keycloak.http.HttpRequest;
import org.keycloak.http.HttpResponse; import org.keycloak.http.HttpResponse;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
@ -30,12 +31,12 @@ public class ResteasyKeycloakContext extends DefaultKeycloakContext {
@Override @Override
protected HttpRequest createHttpRequest() { 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 @Override
protected HttpResponse createHttpResponse() { protected HttpResponse createHttpResponse() {
return new HttpResponseImpl(getContextObject(org.jboss.resteasy.spi.HttpResponse.class)); return new HttpResponseImpl(ResteasyContext.getContextData(org.jboss.resteasy.spi.HttpResponse.class));
} }
} }

View file

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

View file

@ -128,7 +128,7 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest
/** KEYCLOAK-12065 Inherit Client Connection from parent session **/ /** KEYCLOAK-12065 Inherit Client Connection from parent session **/
public static KeycloakSession inheritClientConnection(KeycloakSession parentSession, KeycloakSession currentSession) { public static KeycloakSession inheritClientConnection(KeycloakSession parentSession, KeycloakSession currentSession) {
Resteasy.pushContext(ClientConnection.class, parentSession.getContext().getConnection()); currentSession.getContext().setConnection(parentSession.getContext().getConnection());
return currentSession; return currentSession;
} }
} }

View file

@ -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 <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class ResteasyNullProvider implements ResteasyProvider {
@Override
public <R> R getContextData(Class<R> type) {
return null;
}
@Override
public void pushDefaultContextObject(Class type, Object instance) {
}
@Override
public void pushContext(Class type, Object instance) {
}
@Override
public void clearContextData() {
}
}

View file

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

View file

@ -23,7 +23,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import jakarta.servlet.ServletContext; 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.common.util.SystemEnvProperties;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
@ -38,7 +39,7 @@ public class JsonConfigProviderFactory extends org.keycloak.services.util.JsonCo
private Map<String, String> getPropertyOverrides() { private Map<String, String> getPropertyOverrides() {
ServletContext context = Resteasy.getContextData(ServletContext.class); ServletContext context = ResteasyContext.getContextData(ServletContext.class);
Map<String, String> propertyOverridesMap = new HashMap<>(); Map<String, String> propertyOverridesMap = new HashMap<>();
String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES); String propertyOverrides = context.getInitParameter(SERVER_CONTEXT_CONFIG_PROPERTY_OVERRIDES);

View file

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

View file

@ -26,11 +26,13 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.jboss.resteasy.core.ResteasyContext;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; 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; private final KeycloakSessionFactory factory;
@ -44,8 +46,11 @@ public class UndertowRequestFilter extends AbstractRequestFilter implements Filt
servletRequest.setCharacterEncoding("UTF-8"); servletRequest.setCharacterEncoding("UTF-8");
final HttpServletRequest request = (HttpServletRequest) servletRequest; final HttpServletRequest request = (HttpServletRequest) servletRequest;
filter(createClientConnection(request), (session) -> { ClientConnection connection = createClientConnection(request);
KeycloakModelUtils.runJobInTransaction(factory, session -> {
try { try {
ResteasyContext.pushContext(KeycloakSession.class, session);
session.getContext().setConnection(connection);
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -88,11 +93,6 @@ public class UndertowRequestFilter extends AbstractRequestFilter implements Filt
}; };
} }
@Override
protected KeycloakSessionFactory getSessionFactory() {
return this.factory;
}
@Override @Override
public void init(FilterConfig filterConfig) { public void init(FilterConfig filterConfig) {
} }

View file

@ -1 +0,0 @@
org.keycloak.testsuite.Resteasy4Provider