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:
parent
fa1571f231
commit
be32f8b1bf
24 changed files with 147 additions and 374 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.testsuite.Resteasy4Provider
|
Loading…
Reference in a new issue