diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 9c2e7b39be..4ed2986a9f 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -102,7 +102,10 @@ import org.keycloak.representations.provider.ScriptProviderDescriptor; import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.services.ServicesLogger; +import org.keycloak.services.resources.JsResource; import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.resources.LoadBalancerResource; +import org.keycloak.services.resources.admin.AdminRoot; import org.keycloak.theme.ClasspathThemeProviderFactory; import org.keycloak.theme.ClasspathThemeResourceProviderFactory; import org.keycloak.theme.FolderThemeProviderFactory; @@ -640,6 +643,21 @@ class KeycloakProcessor { buildTimeConditionBuildItemBuildProducer.produce(new BuildTimeConditionBuildItem(index.getIndex().getClassByName(DotName.createSimple( KeycloakApplication.class.getName())), false)); + if (!Profile.isFeatureEnabled(Profile.Feature.ADMIN_API)) { + buildTimeConditionBuildItemBuildProducer.produce(new BuildTimeConditionBuildItem(index.getIndex().getClassByName(DotName.createSimple( + AdminRoot.class.getName())), false)); + } + + if (!Profile.isFeatureEnabled(Profile.Feature.JS_ADAPTER)) { + buildTimeConditionBuildItemBuildProducer.produce(new BuildTimeConditionBuildItem(index.getIndex().getClassByName(DotName.createSimple( + JsResource.class.getName())), false)); + } + + if (!Profile.isFeatureEnabled(Profile.Feature.MULTI_SITE)) { + buildTimeConditionBuildItemBuildProducer.produce(new BuildTimeConditionBuildItem(index.getIndex().getClassByName(DotName.createSimple( + LoadBalancerResource.class.getName())), false)); + } + KeycloakHandlerChainCustomizer chainCustomizer = new KeycloakHandlerChainCustomizer(); scanner.produce(new MethodScannerBuildItem(new MethodScanner() { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java index 18840350d7..65fac38480 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java @@ -17,23 +17,20 @@ package org.keycloak.quarkus.runtime.integration.jaxrs; -import java.util.HashSet; -import java.util.Set; +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; +import io.smallrye.common.annotation.Blocking; -import jakarta.enterprise.event.Observes; -import jakarta.ws.rs.ApplicationPath; - -import org.keycloak.config.HostnameOptions; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.platform.Platform; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; import org.keycloak.quarkus.runtime.integration.QuarkusPlatform; -import org.keycloak.quarkus.runtime.services.resources.DebugHostnameSettingsResource; import org.keycloak.services.resources.KeycloakApplication; -import io.quarkus.runtime.ShutdownEvent; -import io.quarkus.runtime.StartupEvent; -import io.smallrye.common.annotation.Blocking; +import java.util.Set; + +import jakarta.enterprise.event.Observes; +import jakarta.ws.rs.ApplicationPath; @ApplicationPath("/") @Blocking @@ -69,13 +66,6 @@ public class QuarkusKeycloakApplication extends KeycloakApplication { @Override public Set> getClasses() { - Set> classes = new HashSet<>(super.getClasses()); - - classes.add(QuarkusObjectMapperResolver.class); - classes.add(CloseSessionHandler.class); - - classes.add(DebugHostnameSettingsResource.class); - - return classes; + return Set.of(); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java index e5ba299919..65034a6d10 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/DebugHostnameSettingsResource.java @@ -37,6 +37,8 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + import java.io.IOException; import java.net.URI; import java.util.HashMap; @@ -44,6 +46,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; +@Provider @Path("/realms") @EndpointDisabled(name = "kc.hostname-debug", stringValue = "false", disableIfMissing = true) public class DebugHostnameSettingsResource { diff --git a/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilter.java b/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilter.java new file mode 100644 index 0000000000..10a42c1fbd --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilter.java @@ -0,0 +1,68 @@ +/* + * 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.it.jaxrs.filter; + +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.net.SocketAddress; + +import org.jboss.logging.Logger; +import org.keycloak.models.KeycloakSession; + +import java.io.IOException; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; + +@Provider +@PreMatching +public class TestFilter implements ContainerRequestFilter { + + private static final Logger LOG = Logger.getLogger(TestFilter.class); + + @Context + UriInfo info; + + @Context + HttpServerRequest request; + + @Context + KeycloakSession session; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + final String method = requestContext.getMethod(); + final String path = info.getPath(); + final SocketAddress address = request.remoteAddress(); + + KeycloakSession s = null; + try { + if (session != null) { + session.getContext(); + } + s = session; + } catch (RuntimeException e) { + // should say something like Normal scoped producer method may not return null: org.keycloak.quarkus.runtime.integration.cdi.KeycloakBeanProducer.getKeycloakSession() + } + + LOG.infof("Request %s %s has context request %s has keycloaksession %s", method, path, address != null, s != null); + } +} diff --git a/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilterTestProvider.java b/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilterTestProvider.java new file mode 100644 index 0000000000..21843b3d1a --- /dev/null +++ b/quarkus/tests/integration/src/test-providers/java/org/keycloak/it/jaxrs/filter/TestFilterTestProvider.java @@ -0,0 +1,29 @@ +/* + * 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.it.jaxrs.filter; + +import org.keycloak.it.TestProvider; + +public class TestFilterTestProvider implements TestProvider { + + @Override + public Class[] getClasses() { + return new Class[] {TestFilter.class}; + } + +} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/JaxRsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/JaxRsDistTest.java new file mode 100644 index 0000000000..66d19614c6 --- /dev/null +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/JaxRsDistTest.java @@ -0,0 +1,50 @@ +/* + * 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.it.cli.dist; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.keycloak.it.jaxrs.filter.TestFilterTestProvider; +import org.keycloak.it.junit5.extension.CLIResult; +import org.keycloak.it.junit5.extension.DistributionTest; +import org.keycloak.it.junit5.extension.RawDistOnly; +import org.keycloak.it.junit5.extension.TestProvider; +import org.keycloak.it.utils.KeycloakDistribution; + +import java.util.concurrent.TimeUnit; + +import static io.restassured.RestAssured.when; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DistributionTest(keepAlive = true) +@RawDistOnly(reason = "Containers are immutable") +public class JaxRsDistTest { + + @Test + @TestProvider(TestFilterTestProvider.class) + public void requestFilterTest(KeycloakDistribution dist) { + CLIResult cliResult = dist.run("start-dev"); + + cliResult.assertStartedDevMode(); + + assertEquals(200, when().get("/").getStatusCode()); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAsserted( + () -> cliResult.assertMessage("Request GET / has context request true has keycloaksession false")); + } +} diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/TestProvider.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/TestProvider.java index d4872179e2..08b9e6c004 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/TestProvider.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/TestProvider.java @@ -45,5 +45,7 @@ public interface TestProvider { * name of the manifest resource that should be created in the provider JAR file. * @return */ - Map getManifestResources(); + default Map getManifestResources() { + return Map.of(); + } } diff --git a/services/src/main/java/org/keycloak/services/error/KcUnrecognizedPropertyExceptionHandler.java b/services/src/main/java/org/keycloak/services/error/KcUnrecognizedPropertyExceptionHandler.java index 11c86f9a22..948f8940a6 100644 --- a/services/src/main/java/org/keycloak/services/error/KcUnrecognizedPropertyExceptionHandler.java +++ b/services/src/main/java/org/keycloak/services/error/KcUnrecognizedPropertyExceptionHandler.java @@ -24,6 +24,8 @@ import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + import org.keycloak.models.KeycloakSession; /** @@ -31,6 +33,7 @@ import org.keycloak.models.KeycloakSession; * * org.jboss.resteasy.plugins.providers.jackson.UnrecognizedPropertyExceptionHandler */ +@Provider public class KcUnrecognizedPropertyExceptionHandler implements ExceptionMapper { @Context diff --git a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java index 975f26da3f..3bbfc50ddd 100644 --- a/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java +++ b/services/src/main/java/org/keycloak/services/error/KeycloakErrorHandler.java @@ -32,6 +32,8 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + import java.io.IOException; import java.util.HashMap; import java.util.Locale; @@ -40,6 +42,7 @@ import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; +@Provider public class KeycloakErrorHandler implements ExceptionMapper { private static final Logger logger = Logger.getLogger(KeycloakErrorHandler.class); diff --git a/services/src/main/java/org/keycloak/services/error/KeycloakMismatchedInputExceptionHandler.java b/services/src/main/java/org/keycloak/services/error/KeycloakMismatchedInputExceptionHandler.java index 029da13f61..b7497db104 100644 --- a/services/src/main/java/org/keycloak/services/error/KeycloakMismatchedInputExceptionHandler.java +++ b/services/src/main/java/org/keycloak/services/error/KeycloakMismatchedInputExceptionHandler.java @@ -20,16 +20,17 @@ package org.keycloak.services.error; import com.fasterxml.jackson.databind.exc.MismatchedInputException; -import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; -import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + import org.keycloak.models.KeycloakSession; /** * Override explicitly added ExceptionMapper for handling {@link MismatchedInputException} in RestEasy Jackson */ +@Provider public class KeycloakMismatchedInputExceptionHandler implements ExceptionMapper { @Context diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java index 50c455b56a..effa08c51e 100644 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSecurityHeadersFilter.java @@ -26,10 +26,12 @@ import jakarta.annotation.Priority; import jakarta.ws.rs.container.ContainerResponseContext; import jakarta.ws.rs.container.ContainerResponseFilter; import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.ext.Provider; /** * @author Stian Thorgersen */ +@Provider @PreMatching @Priority(10) public class KeycloakSecurityHeadersFilter implements ContainerResponseFilter { diff --git a/services/src/main/java/org/keycloak/services/resources/JsResource.java b/services/src/main/java/org/keycloak/services/resources/JsResource.java index 6ed836693f..0ead07a1a2 100755 --- a/services/src/main/java/org/keycloak/services/resources/JsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/JsResource.java @@ -33,6 +33,8 @@ import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.CacheControl; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + import java.io.InputStream; /** @@ -40,6 +42,7 @@ import java.io.InputStream; * * @author Stian Thorgersen */ +@Provider @Path("/js") public class JsResource { diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 195ba5d846..879d2a5441 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -71,6 +71,8 @@ import java.util.Set; /** * @author Bill Burke * @version $Revision: 1 $ + * + * Note: the classes and singletons are not used by Quarkus - see the KeycloakProcessor to do exclusions */ public class KeycloakApplication extends Application { diff --git a/services/src/main/java/org/keycloak/services/resources/LoadBalancerResource.java b/services/src/main/java/org/keycloak/services/resources/LoadBalancerResource.java index 3b714a4988..4812ecbf4b 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoadBalancerResource.java +++ b/services/src/main/java/org/keycloak/services/resources/LoadBalancerResource.java @@ -22,6 +22,8 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + import org.jboss.logging.Logger; import org.keycloak.health.LoadBalancerCheckProvider; import org.keycloak.models.KeycloakSession; @@ -37,6 +39,7 @@ import java.util.Set; * * @author Alexander Schwartz */ +@Provider @Path("/lb-check") @NonBlocking public class LoadBalancerResource { diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index c86a1bc616..99fc6d6f3c 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -53,6 +53,8 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.ResponseBuilder; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; + import java.net.URI; import java.util.Comparator; @@ -60,6 +62,7 @@ import java.util.Comparator; * @author Bill Burke * @version $Revision: 1 $ */ +@Provider @Path("/realms") public class RealmsResource { protected static final Logger logger = Logger.getLogger(RealmsResource.class); diff --git a/services/src/main/java/org/keycloak/services/resources/RobotsResource.java b/services/src/main/java/org/keycloak/services/resources/RobotsResource.java index d732d41f17..2ae831897d 100755 --- a/services/src/main/java/org/keycloak/services/resources/RobotsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RobotsResource.java @@ -22,7 +22,9 @@ import org.keycloak.utils.MediaType; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; +import jakarta.ws.rs.ext.Provider; +@Provider @Path("/robots.txt") public class RobotsResource { diff --git a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java index 43cafad8b7..e676aeb9f3 100644 --- a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java @@ -37,6 +37,7 @@ import org.keycloak.theme.Theme; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; import java.io.File; import java.io.IOException; @@ -56,6 +57,7 @@ import static java.util.stream.Collectors.toSet; * * @author Stian Thorgersen */ +@Provider @Path("/resources") public class ThemeResource { diff --git a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java index e7e81907a1..47ea38f054 100755 --- a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java @@ -29,6 +29,8 @@ import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.ResponseBuilder; import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.ext.Provider; + import org.jboss.logging.Logger; import org.keycloak.common.ClientConnection; import org.keycloak.common.Profile; @@ -64,6 +66,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * @author Stian Thorgersen */ +@Provider @Path("/") public class WelcomeResource { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index 5c31296fed..f1d4bb5d44 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java @@ -49,6 +49,8 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; + import java.io.IOException; import java.util.Locale; import java.util.Properties; @@ -59,6 +61,7 @@ import java.util.Properties; * @author Bill Burke * @version $Revision: 1 $ */ +@Provider @Path("/admin") public class AdminRoot { protected static final Logger logger = Logger.getLogger(AdminRoot.class); diff --git a/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java b/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java index 51f85e37e3..e1c6ccac98 100644 --- a/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java +++ b/services/src/main/java/org/keycloak/services/util/ObjectMapperResolver.java @@ -35,6 +35,7 @@ import java.util.stream.Stream; * @author Bill Burke * @version $Revision: 1 $ */ +@Provider public class ObjectMapperResolver implements ContextResolver { protected ObjectMapper mapper;