further refinement of context handling (#28182)
* fully removing providers and moving the keycloaksession creation / final cleanup also deprecated Resteasy utility methods closes: #29223 Signed-off-by: Steve Hawkins <shawkins@redhat.com> Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
3b1ca46be2
commit
4697cc956b
22 changed files with 163 additions and 289 deletions
|
@ -18,26 +18,18 @@
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ServiceLoader;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Provides a layer of indirection to abstract invocations to Resteasy internal APIs for obtaining the KeycloakSession
|
* <p>Provides a way for obtaining the KeycloakSession
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*
|
||||||
|
* @deprecated use org.keycloak.util.KeycloakSessionUtil instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class Resteasy {
|
public final class Resteasy {
|
||||||
|
|
||||||
private static ResteasyProvider provider;
|
|
||||||
|
|
||||||
static {
|
|
||||||
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>>() {
|
private static final ThreadLocal<Map<Class<?>, Object>> contextualData = new ThreadLocal<Map<Class<?>, Object>>() {
|
||||||
@Override
|
@Override
|
||||||
protected Map<Class<?>, Object> initialValue() {
|
protected Map<Class<?>, Object> initialValue() {
|
||||||
|
@ -45,12 +37,8 @@ public final class Resteasy {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
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.
|
* Push the given {@code instance} with type/key {@code type} to the context associated with the current thread.
|
||||||
* <br>Should not be called directly
|
* <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
|
||||||
|
@ -61,7 +49,7 @@ public final class Resteasy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the Resteasy context associated with the current thread.
|
* Clear the context associated with the current thread.
|
||||||
* <br>Should not be called directly
|
* <br>Should not be called directly
|
||||||
*/
|
*/
|
||||||
public static void clearContextData() {
|
public static void clearContextData() {
|
||||||
|
@ -69,18 +57,14 @@ public final class Resteasy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup the instance associated with the given type/key {@code type} from the Resteasy context associated with the current thread, or from the provider.
|
* Lookup the instance associated with the given type/key {@code type} from the context associated with the current thread.
|
||||||
* <br> Should only be used to obtain the KeycloakSession
|
* <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) {
|
||||||
R result = (R) contextualData.get().get(type);
|
return (R) contextualData.get().get(type);
|
||||||
if (result != null) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return provider.getContextData(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +72,6 @@ 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
|
@Deprecated
|
||||||
public static void pushDefaultContextObject(Class type, Object instance) {
|
public static void pushDefaultContextObject(Class type, Object instance) {
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package org.keycloak.common.util;
|
|
||||||
|
|
||||||
public interface ResteasyProvider {
|
|
||||||
|
|
||||||
<R> R getContextData(Class<R> type);
|
|
||||||
|
|
||||||
}
|
|
|
@ -301,3 +301,9 @@ the `SingleUseObjectKeyModel` also changed to keep consistency with the method n
|
||||||
|
|
||||||
The previous `getExpiration` method is now deprecated and you should prefer using new newly introduced `getExp` method
|
The previous `getExpiration` method is now deprecated and you should prefer using new newly introduced `getExp` method
|
||||||
to avoid overflow after 2038.
|
to avoid overflow after 2038.
|
||||||
|
|
||||||
|
= Resteasy util class is deprecated
|
||||||
|
|
||||||
|
`org.keycloak.common.util.Resteasy` has been deprecated. You should use the `org.keycloak.util.KeycloakSessionUtil` to obtain the `KeycloakSession` instead.
|
||||||
|
|
||||||
|
It is highly recommended to avoid obtaining the `KeycloakSession` by means other than when creating your custom provider.
|
|
@ -40,7 +40,6 @@ import org.keycloak.Config;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.Profile.Feature;
|
import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.Resteasy;
|
|
||||||
import org.keycloak.config.HostnameV1Options;
|
import org.keycloak.config.HostnameV1Options;
|
||||||
import org.keycloak.config.ProxyOptions;
|
import org.keycloak.config.ProxyOptions;
|
||||||
import org.keycloak.config.ProxyOptions.Mode;
|
import org.keycloak.config.ProxyOptions.Mode;
|
||||||
|
@ -50,6 +49,7 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
import org.keycloak.urls.HostnameProvider;
|
import org.keycloak.urls.HostnameProvider;
|
||||||
import org.keycloak.urls.HostnameProviderFactory;
|
import org.keycloak.urls.HostnameProviderFactory;
|
||||||
import org.keycloak.urls.UrlType;
|
import org.keycloak.urls.UrlType;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
public final class DefaultHostnameProvider implements HostnameProvider, HostnameProviderFactory, EnvironmentDependentProviderFactory {
|
public final class DefaultHostnameProvider implements HostnameProvider, HostnameProviderFactory, EnvironmentDependentProviderFactory {
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ public final class DefaultHostnameProvider implements HostnameProvider, Hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
protected URI getRealmFrontEndUrl() {
|
protected URI getRealmFrontEndUrl() {
|
||||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -17,21 +17,27 @@
|
||||||
|
|
||||||
package org.keycloak.quarkus.runtime.integration.cdi;
|
package org.keycloak.quarkus.runtime.integration.cdi;
|
||||||
|
|
||||||
|
import io.quarkus.arc.Unremovable;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.enterprise.context.RequestScoped;
|
import jakarta.enterprise.context.RequestScoped;
|
||||||
import jakarta.enterprise.inject.Produces;
|
import jakarta.enterprise.inject.Disposes;
|
||||||
import org.keycloak.common.util.Resteasy;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
|
|
||||||
import io.quarkus.arc.Unremovable;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
@Unremovable
|
@Unremovable
|
||||||
public class KeycloakBeanProducer {
|
public class KeycloakBeanProducer implements TransactionalSessionHandler {
|
||||||
|
|
||||||
@Produces
|
|
||||||
@RequestScoped
|
@RequestScoped
|
||||||
public KeycloakSession getKeycloakSession() {
|
public KeycloakSession getKeycloakSession() {
|
||||||
return Resteasy.getContextData(KeycloakSession.class);
|
return create();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose(@Disposes KeycloakSession session) {
|
||||||
|
KeycloakSessionUtil.setKeycloakSession(null);
|
||||||
|
close(session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,14 +28,12 @@ import jakarta.ws.rs.container.ContainerResponseFilter;
|
||||||
import jakarta.ws.rs.container.PreMatching;
|
import jakarta.ws.rs.container.PreMatching;
|
||||||
import jakarta.ws.rs.core.StreamingOutput;
|
import jakarta.ws.rs.core.StreamingOutput;
|
||||||
import jakarta.ws.rs.ext.Provider;
|
import jakarta.ws.rs.ext.Provider;
|
||||||
import org.keycloak.common.util.Resteasy;
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
|
|
||||||
|
|
||||||
@Provider
|
@Provider
|
||||||
@PreMatching
|
@PreMatching
|
||||||
@Priority(1)
|
@Priority(1)
|
||||||
public class CloseSessionHandler implements ContainerResponseFilter, TransactionalSessionHandler {
|
public class CloseSessionFilter implements ContainerResponseFilter, org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
|
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
|
||||||
|
@ -66,6 +64,6 @@ public class CloseSessionHandler implements ContainerResponseFilter, Transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeSession() {
|
private void closeSession() {
|
||||||
close(Resteasy.getContextData(KeycloakSession.class));
|
close(KeycloakSessionUtil.getKeycloakSession());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,108 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.quarkus.runtime.integration.jaxrs;
|
package org.keycloak.quarkus.runtime.integration.jaxrs;
|
||||||
|
|
||||||
import static java.util.Collections.emptySet;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
|
||||||
|
|
||||||
public final class EmptyMultivaluedMap<K, V> implements MultivaluedMap<K, V> {
|
import jakarta.ws.rs.core.AbstractMultivaluedMap;
|
||||||
|
|
||||||
@Override
|
public final class EmptyMultivaluedMap<K, V> extends AbstractMultivaluedMap<K, V> {
|
||||||
public void putSingle(K key, V value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public EmptyMultivaluedMap() {
|
||||||
public void add(K key, V value) {
|
super(Map.of());
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public V getFirst(K key) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAll(K key, V... newValues) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAll(K key, List<V> valueList) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addFirst(K key, V value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equalsIgnoreValueOrder(MultivaluedMap<K, V> otherMap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsKey(Object key) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsValue(Object value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<V> get(Object key) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<V> put(K key, List<V> value) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<V> remove(Object key) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putAll(Map<? extends K, ? extends List<V>> m) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<K> keySet() {
|
|
||||||
return emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<List<V>> values() {
|
|
||||||
return emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<K, List<V>>> entrySet() {
|
|
||||||
return emptySet();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2022 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.quarkus.runtime.integration.resteasy;
|
|
||||||
|
|
||||||
import io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext;
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
|
|
||||||
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler;
|
|
||||||
|
|
||||||
import static org.keycloak.common.util.Resteasy.clearContextData;
|
|
||||||
|
|
||||||
public final class CreateSessionHandler implements ServerRestHandler, TransactionalSessionHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(ResteasyReactiveRequestContext requestContext) {
|
|
||||||
QuarkusResteasyReactiveRequestContext context = (QuarkusResteasyReactiveRequestContext) requestContext;
|
|
||||||
RoutingContext routingContext = context.getContext();
|
|
||||||
KeycloakSession currentSession = routingContext.get(KeycloakSession.class.getName());
|
|
||||||
|
|
||||||
if (currentSession == null) {
|
|
||||||
// this handler might be invoked multiple times when resolving sub-resources
|
|
||||||
// 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(ignored -> {
|
|
||||||
try {
|
|
||||||
close(session);
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
clearContextData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -32,7 +32,7 @@ import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
|
||||||
|
|
||||||
public final class KeycloakHandlerChainCustomizer implements HandlerChainCustomizer {
|
public final class KeycloakHandlerChainCustomizer implements HandlerChainCustomizer {
|
||||||
|
|
||||||
private final CreateSessionHandler TRANSACTIONAL_SESSION_HANDLER = new CreateSessionHandler();
|
private final TransactionalSessionHandler TRANSACTIONAL_SESSION_HANDLER = new TransactionalSessionHandler();
|
||||||
|
|
||||||
private final FormBodyHandler formBodyHandler = new FormBodyHandler(true, () -> Runnable::run, Set.of());
|
private final FormBodyHandler formBodyHandler = new FormBodyHandler(true, () -> Runnable::run, Set.of());
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2022 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.quarkus.runtime.integration.resteasy;
|
|
||||||
|
|
||||||
import io.quarkus.arc.Arc;
|
|
||||||
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
|
||||||
import org.keycloak.common.util.ResteasyProvider;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import jakarta.enterprise.context.ContextNotActiveException;
|
|
||||||
|
|
||||||
public class ResteasyVertxProvider implements ResteasyProvider {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <R> R getContextData(Class<R> type) {
|
|
||||||
return (R) getRoutingContext().map(c -> c.get(type.getName())).orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.quarkus.runtime.integration.resteasy;
|
||||||
|
|
||||||
|
import io.quarkus.arc.Arc;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
|
||||||
|
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
|
public final class TransactionalSessionHandler implements ServerRestHandler, org.keycloak.quarkus.runtime.transaction.TransactionalSessionHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(ResteasyReactiveRequestContext requestContext) {
|
||||||
|
requestContext.requireCDIRequestScope();
|
||||||
|
KeycloakSession currentSession = Arc.container().instance(KeycloakSession.class).get();
|
||||||
|
// this handler might be invoked multiple times when resolving sub-resources
|
||||||
|
// make sure the transaction is began once when the session is first associated with the thread
|
||||||
|
if (KeycloakSessionUtil.setKeycloakSession(currentSession) == null) {
|
||||||
|
beginTransaction(currentSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,9 +19,7 @@ package org.keycloak.quarkus.runtime.transaction;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakTransactionManager;
|
|
||||||
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
||||||
import org.keycloak.services.DefaultKeycloakSession;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A {@link TransactionalSessionHandler} is responsible for managing transaction sessions and its lifecycle. Its subtypes
|
* <p>A {@link TransactionalSessionHandler} is responsible for managing transaction sessions and its lifecycle. Its subtypes
|
||||||
|
@ -31,24 +29,31 @@ import org.keycloak.services.DefaultKeycloakSession;
|
||||||
public interface TransactionalSessionHandler {
|
public interface TransactionalSessionHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a transactional {@link KeycloakSession}.
|
* Creates a {@link KeycloakSession}.
|
||||||
*
|
*
|
||||||
* @return a transactional keycloak session
|
* @return a keycloak session
|
||||||
*/
|
*/
|
||||||
default KeycloakSession create() {
|
default KeycloakSession create() {
|
||||||
KeycloakSessionFactory sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
|
KeycloakSessionFactory sessionFactory = QuarkusKeycloakSessionFactory.getInstance();
|
||||||
KeycloakSession session = sessionFactory.create();
|
return sessionFactory.create();
|
||||||
session.getTransactionManager().begin();
|
|
||||||
return session;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes a transactional {@link KeycloakSession}.
|
* begin a transaction if possible
|
||||||
*
|
*
|
||||||
* @param session a transactional session
|
* @param session a session
|
||||||
|
*/
|
||||||
|
default void beginTransaction(KeycloakSession session) {
|
||||||
|
session.getTransactionManager().begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a {@link KeycloakSession}.
|
||||||
|
*
|
||||||
|
* @param session a session
|
||||||
*/
|
*/
|
||||||
default void close(KeycloakSession session) {
|
default void close(KeycloakSession session) {
|
||||||
if (session == null || DefaultKeycloakSession.class.cast(session).isClosed()) {
|
if (session == null || session.isClosed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
org.keycloak.quarkus.runtime.integration.resteasy.ResteasyVertxProvider
|
|
|
@ -45,6 +45,6 @@ public class JaxRsDistTest {
|
||||||
assertEquals(200, when().get("/").getStatusCode());
|
assertEquals(200, when().get("/").getStatusCode());
|
||||||
|
|
||||||
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
|
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted(
|
||||||
() -> cliResult.assertMessage("Request GET / has context request true has keycloaksession false"));
|
() -> cliResult.assertMessage("Request GET / has context request true has keycloaksession true"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ 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;
|
||||||
|
@ -53,6 +52,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
import org.keycloak.transaction.JtaTransactionManagerLookup;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import jakarta.transaction.InvalidTransactionException;
|
import jakarta.transaction.InvalidTransactionException;
|
||||||
|
@ -380,7 +380,7 @@ 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);
|
KeycloakSession old = KeycloakSessionUtil.setKeycloakSession(session);
|
||||||
try {
|
try {
|
||||||
cloneContextRealmClientToSession(context, session);
|
cloneContextRealmClientToSession(context, session);
|
||||||
result = callable.run(session);
|
result = callable.run(session);
|
||||||
|
@ -388,7 +388,7 @@ public final class KeycloakModelUtils {
|
||||||
session.getTransactionManager().setRollbackOnly();
|
session.getTransactionManager().setRollbackOnly();
|
||||||
throw t;
|
throw t;
|
||||||
} finally {
|
} finally {
|
||||||
Resteasy.pushContext(KeycloakSession.class, old);
|
KeycloakSessionUtil.setKeycloakSession(old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -23,13 +23,13 @@ import java.util.function.BiConsumer;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
import jakarta.ws.rs.core.Response.ResponseBuilder;
|
||||||
|
|
||||||
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.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -54,12 +54,12 @@ public interface Cors extends Provider {
|
||||||
public static final String INCLUDE_REDIRECTS = "+";
|
public static final String INCLUDE_REDIRECTS = "+";
|
||||||
|
|
||||||
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
||||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
return session.getProvider(Cors.class).request(request).builder(response);
|
return session.getProvider(Cors.class).request(request).builder(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cors add(HttpRequest request) {
|
public static Cors add(HttpRequest request) {
|
||||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
return session.getProvider(Cors.class).request(request);
|
return session.getProvider(Cors.class).request(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ public interface KeycloakSession extends AutoCloseable {
|
||||||
* @return
|
* @return
|
||||||
* @deprecated Deprecated in favor of {@link #getComponentProvider)
|
* @deprecated Deprecated in favor of {@link #getComponentProvider)
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
<T extends Provider> T getProvider(Class<T> clazz, ComponentModel componentModel);
|
<T extends Provider> T getProvider(Class<T> clazz, ComponentModel componentModel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -203,6 +204,7 @@ public interface KeycloakSession extends AutoCloseable {
|
||||||
|
|
||||||
SingleUseObjectProvider singleUseObjects();
|
SingleUseObjectProvider singleUseObjects();
|
||||||
|
|
||||||
|
@Override
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -243,4 +245,6 @@ public interface KeycloakSession extends AutoCloseable {
|
||||||
*/
|
*/
|
||||||
ClientPolicyManager clientPolicy();
|
ClientPolicyManager clientPolicy();
|
||||||
|
|
||||||
|
boolean isClosed();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 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.utils;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Resteasy;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
public class KeycloakSessionUtil {
|
||||||
|
|
||||||
|
private KeycloakSessionUtil() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link KeycloakSession} currently associated with the thread.
|
||||||
|
*
|
||||||
|
* @return the current session
|
||||||
|
*/
|
||||||
|
public static KeycloakSession getKeycloakSession() {
|
||||||
|
return Resteasy.getContextData(KeycloakSession.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate the {@link KeycloakSession} with the current thread.
|
||||||
|
* <br>Warning: should not be called directly. Keycloak will manage this.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* @return the existing {@link KeycloakSession} or null
|
||||||
|
*/
|
||||||
|
public static KeycloakSession setKeycloakSession(KeycloakSession session) {
|
||||||
|
return Resteasy.pushContext(KeycloakSession.class, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -119,13 +119,9 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientConnection getConnection() {
|
public ClientConnection getConnection() {
|
||||||
if (clientConnection == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (clientConnection == null) {
|
if (clientConnection == null) {
|
||||||
clientConnection = createClientConnection();
|
clientConnection = createClientConnection();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientConnection;
|
return clientConnection;
|
||||||
}
|
}
|
||||||
|
@ -147,26 +143,18 @@ public abstract class DefaultKeycloakContext implements KeycloakContext {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest getHttpRequest() {
|
public HttpRequest getHttpRequest() {
|
||||||
if (request == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
request = createHttpRequest();
|
request = createHttpRequest();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpResponse getHttpResponse() {
|
public HttpResponse getHttpResponse() {
|
||||||
if (response == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
response = createHttpResponse();
|
response = createHttpResponse();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.services;
|
package org.keycloak.services;
|
||||||
|
|
||||||
import org.keycloak.common.util.Resteasy;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
import jakarta.ws.rs.WebApplicationException;
|
import jakarta.ws.rs.WebApplicationException;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
@ -55,7 +55,7 @@ public class ErrorResponseException extends WebApplicationException {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response getResponse() {
|
public Response getResponse() {
|
||||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
// This has to happen, since calling getResponse() with non-null result leads to
|
// This has to happen, since calling getResponse() with non-null result leads to
|
||||||
// directly returning the result instead of
|
// directly returning the result instead of
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.keycloak.services.error;
|
package org.keycloak.services.error;
|
||||||
|
|
||||||
import static org.keycloak.common.util.Resteasy.getContextData;
|
|
||||||
import static org.keycloak.services.resources.KeycloakApplication.getSessionFactory;
|
import static org.keycloak.services.resources.KeycloakApplication.getSessionFactory;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParseException;
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
|
@ -26,6 +25,7 @@ import org.keycloak.theme.beans.MessageBean;
|
||||||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||||
import org.keycloak.forms.login.MessageType;
|
import org.keycloak.forms.login.MessageType;
|
||||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
import org.keycloak.utils.MediaTypeMatcher;
|
import org.keycloak.utils.MediaTypeMatcher;
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class KeycloakErrorHandler implements ExceptionMapper<Throwable> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response toResponse(Throwable throwable) {
|
public Response toResponse(Throwable throwable) {
|
||||||
KeycloakSession session = getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
// errors might be thrown when handling errors from JAX-RS before the session is available
|
// errors might be thrown when handling errors from JAX-RS before the session is available
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.filters;
|
package org.keycloak.services.filters;
|
||||||
|
|
||||||
import org.keycloak.common.util.Resteasy;
|
|
||||||
import org.keycloak.headers.SecurityHeadersProvider;
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.utils.KeycloakSessionUtil;
|
||||||
|
|
||||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) {
|
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) {
|
||||||
KeycloakSession session = Resteasy.getContextData(KeycloakSession.class);
|
KeycloakSession session = KeycloakSessionUtil.getKeycloakSession();
|
||||||
|
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);
|
SecurityHeadersProvider securityHeadersProvider = session.getProvider(SecurityHeadersProvider.class);
|
||||||
|
|
Loading…
Reference in a new issue