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;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
/**
* <p>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.
*
* <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.
* <p>Provides a layer of indirection to abstract invocations to Resteasy internal APIs for obtaining the KeycloakSession
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
@ -35,38 +32,55 @@ public final class Resteasy {
private static ResteasyProvider provider;
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() {
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
* <br>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> R getContextData(Class<R> type) {
return provider.getContextData(type);
public static <R> R pushContext(Class<R> type, R instance) {
return (R) contextualData.get().put(type, instance);
}
/**
* Clear the Resteasy context associated with the current thread.
* <br>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.
* <br> 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> R getContextData(Class<R> 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);
}
}

View file

@ -4,10 +4,4 @@ public interface ResteasyProvider {
<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.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);
}
}

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.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() {

View file

@ -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<Map<Class<?>, Object>> contextualData = new ThreadLocal<Map<Class<?>, Object>>() {
@Override
protected Map<Class<?>, Object> initialValue() {
return new HashMap<>();
};
};
@Override
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.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;

View file

@ -55,7 +55,15 @@ public interface KeycloakContext {
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();
@ -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);
}

View file

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

View file

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

View file

@ -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> T getContextObject(Class<T> 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;
}
}

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.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();

View file

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

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.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<MultipartFormDataInput> multiPartProvider = providers.getMessageBodyReader(
MultipartFormDataInput.class, null, null, MULTIPART_FORM_DATA_TYPE);
MultipartFormDataInput inputs = multiPartProvider

View file

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

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

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

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.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<String, String> getPropertyOverrides() {
ServletContext context = Resteasy.getContextData(ServletContext.class);
ServletContext context = ResteasyContext.getContextData(ServletContext.class);
Map<String, String> propertyOverridesMap = new HashMap<>();
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.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) {
}

View file

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