From 91ffec071b752bf632ba0f741cc2880849af6f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 1 Oct 2024 00:33:43 +0200 Subject: [PATCH] Start Keycloak Dev Svc for standalone OIDC Client --- .../deployment/OidcClientBuildStep.java | 30 ++++++++ .../OidcClientKeycloakDevServiceTest.java | 63 +++++++++++++++++ ...tTooManyJwtCredentialKeyPropsTestCase.java | 3 +- .../oidc-client-dev-service-test.properties | 15 ++++ .../resources/META-INF/quarkus-extension.yaml | 1 + extensions/oidc-common/deployment/pom.xml | 28 ++++++++ .../keycloak/DevServicesConfig.java | 69 +++++-------------- .../keycloak/KeycloakBuildTimeConfig.java | 2 +- .../KeycloakDevServicesConfigBuildItem.java | 4 +- .../KeycloakDevServicesProcessor.java | 64 ++++++++++------- .../KeycloakDevServicesRequiredBuildItem.java | 40 +++++++++++ .../main/resources/dev-service/upconfig.json | 0 .../devservices}/OidcDevServicesUtils.java | 2 +- extensions/oidc/deployment/pom.xml | 20 ------ .../quarkus/oidc/deployment/DevUiConfig.java | 1 - .../oidc/deployment/OidcBuildStep.java | 9 +++ .../devservices/OidcDevServicesBuildItem.java | 11 --- .../devservices/OidcDevUIProcessor.java | 2 +- .../keycloak/KeycloakDevUIProcessor.java | 6 +- .../runtime/devui/OidcDevJsonRpcService.java | 3 +- .../keycloak/client/KeycloakTestClient.java | 2 +- 21 files changed, 254 insertions(+), 121 deletions(-) create mode 100644 extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java create mode 100644 extensions/oidc-client/deployment/src/test/resources/oidc-client-dev-service-test.properties rename extensions/{oidc/deployment/src/main/java/io/quarkus/oidc => oidc-common/deployment/src/main/java/io/quarkus/oidc/common}/deployment/devservices/keycloak/DevServicesConfig.java (86%) rename extensions/{oidc/deployment/src/main/java/io/quarkus/oidc => oidc-common/deployment/src/main/java/io/quarkus/oidc/common}/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java (85%) rename extensions/{oidc/deployment/src/main/java/io/quarkus/oidc => oidc-common/deployment/src/main/java/io/quarkus/oidc/common}/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java (87%) rename extensions/{oidc/deployment/src/main/java/io/quarkus/oidc => oidc-common/deployment/src/main/java/io/quarkus/oidc/common}/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java (94%) create mode 100644 extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java rename extensions/{oidc => oidc-common}/deployment/src/main/resources/dev-service/upconfig.json (100%) rename extensions/{oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui => oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/devservices}/OidcDevServicesUtils.java (99%) delete mode 100644 extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevServicesBuildItem.java diff --git a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java index fe6e9fae6f74f..1e5ee54efa9f4 100644 --- a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java +++ b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java @@ -24,7 +24,11 @@ import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.ApplicationArchive; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsDevelopment; +import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -35,6 +39,9 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; +import io.quarkus.devui.spi.page.CardPageBuildItem; +import io.quarkus.devui.spi.page.Page; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.MethodCreator; @@ -51,6 +58,8 @@ import io.quarkus.oidc.client.runtime.TokenProviderProducer; import io.quarkus.oidc.client.runtime.TokensHelper; import io.quarkus.oidc.client.runtime.TokensProducer; +import io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesConfigBuildItem; +import io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesRequiredBuildItem; import io.quarkus.oidc.token.propagation.AccessToken; import io.quarkus.tls.TlsRegistryBuildItem; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; @@ -184,6 +193,27 @@ private AccessTokenInstanceBuildItem build() { return index.getIndex().getAnnotations(ACCESS_TOKEN).stream().map(ItemBuilder::new).map(ItemBuilder::build).toList(); } + @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) + KeycloakDevServicesRequiredBuildItem requireKeycloakDevService() { + // this needs to be done as OIDC Common doesn't know if the OIDC Client is enabled + return KeycloakDevServicesRequiredBuildItem.requireDevServiceForOidcClient(); + } + + @BuildStep(onlyIf = IsDevelopment.class) + void produceProviderComponent(Optional configProps, + BuildProducer cardPageProducer, Capabilities capabilities) { + final String keycloakAdminUrl = configProps.map(item -> item.getConfig().get("keycloak.url")).orElse(null); + if (capabilities.isMissing(Capability.OIDC) && keycloakAdminUrl != null) { + // Add Admin page + final CardPageBuildItem cardPage = new CardPageBuildItem(); + cardPage.addPage(Page.externalPageBuilder("Keycloak Admin") + .icon("font-awesome-solid:key") + .doNotEmbed(true) + .url(keycloakAdminUrl)); + cardPageProducer.produce(cardPage); + } + } + /** * Creates a Tokens producer class like follows: * diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java new file mode 100644 index 0000000000000..b5f64ce0343ad --- /dev/null +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientKeycloakDevServiceTest.java @@ -0,0 +1,63 @@ +package io.quarkus.oidc.client; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.oidc.runtime.OidcUtils; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +/** + * Test Keycloak Dev Service is started when OIDC extension is disabled (or not present, though indirectly). + * OIDC client auth server URL and client id and secret must be automatically configured for this test to pass. + */ +public class OidcClientKeycloakDevServiceTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(NamedOidcClientResource.class) + .addAsResource("oidc-client-dev-service-test.properties", "application.properties")); + + @Test + public void testInjectedNamedOidcClients() { + String token1 = doTestGetTokenByNamedClient("client1"); + String token2 = doTestGetTokenByNamedClient("client2"); + validateTokens(token1, token2); + } + + @Test + public void testInjectedNamedTokens() { + String token1 = doTestGetTokenByNamedTokensProvider("client1"); + String token2 = doTestGetTokenByNamedTokensProvider("client2"); + validateTokens(token1, token2); + } + + private void validateTokens(String token1, String token2) { + assertThat(token1, is(not(equalTo(token2)))); + assertThat(upn(token1), is("alice")); + assertThat(upn(token2), is("bob")); + } + + private String upn(String token) { + return OidcUtils.decodeJwtContent(token).getString("upn"); + } + + private String doTestGetTokenByNamedClient(String clientId) { + String token = RestAssured.given().get("/" + clientId + "/token").body().asString(); + assertThat(token, is(notNullValue())); + return token; + } + + private String doTestGetTokenByNamedTokensProvider(String clientId) { + String token = RestAssured.given().get("/" + clientId + "/token/singleton").body().asString(); + assertThat(token, is(notNullValue())); + return token; + } +} diff --git a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientTooManyJwtCredentialKeyPropsTestCase.java b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientTooManyJwtCredentialKeyPropsTestCase.java index a4eae56be1217..32e1bb628a6e4 100644 --- a/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientTooManyJwtCredentialKeyPropsTestCase.java +++ b/extensions/oidc-client/deployment/src/test/java/io/quarkus/oidc/client/OidcClientTooManyJwtCredentialKeyPropsTestCase.java @@ -17,7 +17,8 @@ public class OidcClientTooManyJwtCredentialKeyPropsTestCase { static final QuarkusUnitTest test = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addAsResource(new StringAsset( - "quarkus.oidc-client.token-path=http://localhost:8180/oidc/tokens\n" + "quarkus.keycloak.devservices.enabled=false\n" + + "quarkus.oidc-client.token-path=http://localhost:8180/oidc/tokens\n" + "quarkus.oidc-client.client-id=quarkus\n" + "quarkus.oidc-client.credentials.jwt.secret=secret\n" + "quarkus.oidc-client.credentials.jwt.key=base64encPrivateKey\n" diff --git a/extensions/oidc-client/deployment/src/test/resources/oidc-client-dev-service-test.properties b/extensions/oidc-client/deployment/src/test/resources/oidc-client-dev-service-test.properties new file mode 100644 index 0000000000000..51063eea9940c --- /dev/null +++ b/extensions/oidc-client/deployment/src/test/resources/oidc-client-dev-service-test.properties @@ -0,0 +1,15 @@ +quarkus.oidc.enabled=false + +quarkus.oidc-client.client1.auth-server-url=${quarkus.oidc-client.auth-server-url} +quarkus.oidc-client.client1.client-id=${quarkus.oidc-client.client-id} +quarkus.oidc-client.client1.credentials.secret=${quarkus.oidc-client.credentials.secret} +quarkus.oidc-client.client1.grant.type=password +quarkus.oidc-client.client1.grant-options.password.username=alice +quarkus.oidc-client.client1.grant-options.password.password=alice + +quarkus.oidc-client.client2.auth-server-url=${quarkus.oidc-client.auth-server-url} +quarkus.oidc-client.client2.client-id=${quarkus.oidc-client.client-id} +quarkus.oidc-client.client2.credentials.secret=${quarkus.oidc-client.credentials.secret} +quarkus.oidc-client.client2.grant.type=password +quarkus.oidc-client.client2.grant-options.password.username=bob +quarkus.oidc-client.client2.grant-options.password.password=bob diff --git a/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml index 434ccf025932b..3282e98c0a163 100644 --- a/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/oidc-client/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -14,3 +14,4 @@ metadata: config: - "quarkus.oidc-client." - "quarkus.oidc." + - "quarkus.keycloak.devservices." diff --git a/extensions/oidc-common/deployment/pom.xml b/extensions/oidc-common/deployment/pom.xml index 6b8e565f4e77c..8c307423d78e7 100644 --- a/extensions/oidc-common/deployment/pom.xml +++ b/extensions/oidc-common/deployment/pom.xml @@ -41,6 +41,31 @@ io.quarkus quarkus-oidc-common + + + org.testcontainers + testcontainers + + + junit + junit + + + + + org.keycloak + keycloak-core + + + com.sun.activation + jakarta.activation + + + + + io.quarkus + quarkus-devservices-deployment + @@ -57,6 +82,9 @@ ${project.version} + + -AlegacyConfigRoot=true + diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/DevServicesConfig.java similarity index 86% rename from extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java rename to extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/DevServicesConfig.java index 55584da107f38..d1a8d2d19b3be 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java +++ b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/DevServicesConfig.java @@ -1,12 +1,12 @@ -package io.quarkus.oidc.deployment.devservices.keycloak; +package io.quarkus.oidc.common.deployment.devservices.keycloak; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; -import io.quarkus.oidc.deployment.DevUiConfig; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -151,9 +151,12 @@ public class DevServicesConfig { public boolean createRealm; /** - * Specifies whether to create the default client id `quarkus-app` with a secret `secret`and register them as - * `quarkus.oidc.client.id` and `quarkus.oidc.credentials.secret` properties, if the {@link #createRealm} property is set to - * true. + * Specifies whether to create the default client id `quarkus-app` with a secret `secret`and register them + * if the {@link #createRealm} property is set to true. + * For OIDC extension configuration properties `quarkus.oidc.client.id` and `quarkus.oidc.credentials.secret` will + * be configured. + * For OIDC Client extension configuration properties `quarkus.oidc-client.client.id` + * and `quarkus.oidc-client.credentials.secret` will be configured. * * Set to `false` if clients have to be created using either the Keycloak Administration Console or * the Keycloak Admin API provided by {@linkplain io.quarkus.test.common.QuarkusTestResourceLifecycleManager} @@ -191,54 +194,6 @@ public class DevServicesConfig { @ConfigDocMapKey("role-name") public Map> roles; - /** - * Specifies the grant type. - * - * @deprecated This field is deprecated. Use {@link DevUiConfig#grant} instead. - */ - @Deprecated - public Grant grant = new Grant(); - - @ConfigGroup - public static class Grant { - public static enum Type { - /** - * `client_credentials` grant - */ - CLIENT("client_credentials"), - /** - * `password` grant - */ - PASSWORD("password"), - - /** - * `authorization_code` grant - */ - CODE("code"), - - /** - * `implicit` grant - */ - IMPLICIT("implicit"); - - private String grantType; - - private Type(String grantType) { - this.grantType = grantType; - } - - public String getGrantType() { - return grantType; - } - } - - /** - * Defines the grant type for aquiring tokens for testing OIDC `service` applications. - */ - @ConfigItem(defaultValue = "code") - public Type type = Type.CODE; - } - /** * The specific port for the dev service to listen on. *

@@ -254,6 +209,14 @@ public String getGrantType() { @ConfigDocMapKey("environment-variable-name") public Map containerEnv; + /** + * The WebClient timeout. + * Use this property to configure how long an HTTP client used by OIDC dev service admin client will wait + * for a response from OpenId Connect Provider when acquiring admin token and creating realm. + */ + @ConfigItem(defaultValue = "4S") + public Duration webClientTimeout; + @Override public boolean equals(Object o) { if (this == o) diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java similarity index 85% rename from extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java rename to extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java index d30df58effb70..6afc1f95e9f2c 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java +++ b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakBuildTimeConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.oidc.deployment.devservices.keycloak; +package io.quarkus.oidc.common.deployment.devservices.keycloak; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigItem; diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java similarity index 87% rename from extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java rename to extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java index efddc9b63ae8f..a706847df5fd7 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java +++ b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesConfigBuildItem.java @@ -1,4 +1,4 @@ -package io.quarkus.oidc.deployment.devservices.keycloak; +package io.quarkus.oidc.common.deployment.devservices.keycloak; import java.util.Map; @@ -25,7 +25,7 @@ public Map getConfig() { return config; } - boolean isContainerRestarted() { + public boolean isContainerRestarted() { return containerRestarted; } } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java similarity index 94% rename from extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java rename to extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index a648b03c1a0dc..fe95129741dd0 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -1,4 +1,7 @@ -package io.quarkus.oidc.deployment.devservices.keycloak; +package io.quarkus.oidc.common.deployment.devservices.keycloak; + +import static io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesRequiredBuildItem.setOidcClientConfigProperties; +import static io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesRequiredBuildItem.setOidcConfigProperties; import java.io.IOException; import java.io.InputStream; @@ -59,10 +62,7 @@ import io.quarkus.devservices.common.ConfigureUtil; import io.quarkus.devservices.common.ContainerAddress; import io.quarkus.devservices.common.ContainerLocator; -import io.quarkus.oidc.deployment.OidcBuildStep.IsEnabled; -import io.quarkus.oidc.deployment.OidcBuildTimeConfig; -import io.quarkus.oidc.deployment.devservices.OidcDevServicesBuildItem; -import io.quarkus.oidc.runtime.devui.OidcDevServicesUtils; +import io.quarkus.oidc.common.runtime.devservices.OidcDevServicesUtils; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.mutiny.TimeoutException; @@ -73,7 +73,7 @@ import io.vertx.mutiny.ext.web.client.HttpResponse; import io.vertx.mutiny.ext.web.client.WebClient; -@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { IsEnabled.class, GlobalDevServicesConfig.Enabled.class }) +@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) public class KeycloakDevServicesProcessor { static volatile Vertx vertxInstance; @@ -83,13 +83,16 @@ public class KeycloakDevServicesProcessor { private static final String TENANT_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "tenant-enabled"; private static final String AUTH_SERVER_URL_CONFIG_KEY = CONFIG_PREFIX + "auth-server-url"; private static final String PROVIDER_CONFIG_KEY = CONFIG_PREFIX + "provider"; - // avoid the Quarkus prefix in order to prevent warnings when the application starts in container integration tests - private static final String CLIENT_AUTH_SERVER_URL_CONFIG_KEY = "client." + CONFIG_PREFIX + "auth-server-url"; private static final String APPLICATION_TYPE_CONFIG_KEY = CONFIG_PREFIX + "application-type"; private static final String CLIENT_ID_CONFIG_KEY = CONFIG_PREFIX + "client-id"; private static final String CLIENT_SECRET_CONFIG_KEY = CONFIG_PREFIX + "credentials.secret"; private static final String KEYCLOAK_URL_KEY = "keycloak.url"; + // OIDC Client config properties + private static final String OIDC_CLIENT_AUTH_SERVER_URL_CONFIG_KEY = "quarkus.oidc-client.auth-server-url"; + private static final String OIDC_CLIENT_SECRET_CONFIG_KEY = "quarkus.oidc-client.credentials.secret"; + private static final String OIDC_CLIENT_ID_CONFIG_KEY = "quarkus.oidc-client.client-id"; + private static final String KEYCLOAK_CONTAINER_NAME = "keycloak"; private static final int KEYCLOAK_PORT = 8080; private static final int KEYCLOAK_HTTPS_PORT = 8443; @@ -129,15 +132,15 @@ public class KeycloakDevServicesProcessor { static volatile DevServicesConfig capturedDevServicesConfiguration; private static volatile boolean first = true; private static volatile Set capturedRealmFileLastModifiedDate; - - OidcBuildTimeConfig oidcConfig; + private static boolean setOidcConfigProperties = true; + private static boolean setOidcClientConfigProperties = true; @BuildStep public DevServicesResultBuildItem startKeycloakContainer( + List devSvcRequiredMarkerItems, DockerStatusBuildItem dockerStatusBuildItem, BuildProducer keycloakBuildItemBuildProducer, List devServicesSharedNetworkBuildItem, - Optional oidcProviderBuildItem, KeycloakBuildTimeConfig config, CuratedApplicationShutdownBuildItem closeBuildItem, LaunchModeBuildItem launchMode, @@ -145,11 +148,13 @@ public DevServicesResultBuildItem startKeycloakContainer( LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) { - if (oidcProviderBuildItem.isPresent()) { - // Dev Services for the alternative OIDC provider are enabled + if (devSvcRequiredMarkerItems.isEmpty()) { return null; } + setOidcConfigProperties = setOidcConfigProperties(devSvcRequiredMarkerItems); + setOidcClientConfigProperties = setOidcClientConfigProperties(devSvcRequiredMarkerItems); + DevServicesConfig currentDevServicesConfiguration = config.devservices; // Figure out if we need to shut down and restart any existing Keycloak container // if not and the Keycloak container has already started we just return @@ -260,8 +265,7 @@ private String startURL(String scheme, String host, Integer port, boolean isKeyc private Map prepareConfiguration( BuildProducer keycloakBuildItemBuildProducer, String internalURL, - String hostURL, List realmReps, - boolean keycloakX, List errors) { + String hostURL, List realmReps, List errors) { final String realmName = realmReps != null && !realmReps.isEmpty() ? realmReps.iterator().next().getRealm() : getDefaultRealmName(); final String authServerInternalUrl = realmsURL(internalURL, realmName); @@ -305,12 +309,22 @@ private Map prepareConfiguration( Map configProperties = new HashMap<>(); configProperties.put(KEYCLOAK_URL_KEY, internalURL); - configProperties.put(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl); - configProperties.put(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl); - configProperties.put(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType); + if (setOidcClientConfigProperties) { + configProperties.put(OIDC_CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl); + } + if (setOidcConfigProperties) { + configProperties.put(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl); + configProperties.put(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType); + } if (capturedDevServicesConfiguration.createClient) { - configProperties.put(CLIENT_ID_CONFIG_KEY, oidcClientId); - configProperties.put(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret); + if (setOidcConfigProperties) { + configProperties.put(CLIENT_ID_CONFIG_KEY, oidcClientId); + configProperties.put(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret); + } + if (setOidcClientConfigProperties) { + configProperties.put(OIDC_CLIENT_ID_CONFIG_KEY, oidcClientId); + configProperties.put(OIDC_CLIENT_SECRET_CONFIG_KEY, oidcClientSecret); + } } configProperties.put(OIDC_USERS, users.entrySet().stream() .map(e -> e.toString()).collect(Collectors.joining(","))); @@ -394,9 +408,7 @@ private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuild : null; Map configs = prepareConfiguration(keycloakBuildItemBuildProducer, internalUrl, hostUrl, - oidcContainer.realmReps, - oidcContainer.keycloakX, - errors); + oidcContainer.realmReps, errors); return new RunningDevService(KEYCLOAK_CONTAINER_NAME, oidcContainer.getContainerId(), oidcContainer::close, configs); }; @@ -406,7 +418,7 @@ private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuild // TODO: this probably needs to be addressed Map configs = prepareConfiguration(keycloakBuildItemBuildProducer, getSharedContainerUrl(containerAddress), - getSharedContainerUrl(containerAddress), null, false, errors); + getSharedContainerUrl(containerAddress), null, errors); return new RunningDevService(KEYCLOAK_CONTAINER_NAME, containerAddress.getId(), null, configs); }) .orElseGet(defaultKeycloakContainerSupplier); @@ -694,7 +706,7 @@ private String getAdminToken(WebClient client, String keycloakUrl) { return OidcDevServicesUtils.getPasswordAccessToken(client, keycloakUrl + "/realms/master/protocol/openid-connect/token", "admin-cli", null, "admin", "admin", null) - .await().atMost(oidcConfig.devui.webClientTimeout); + .await().atMost(capturedDevServicesConfiguration.webClientTimeout); } catch (TimeoutException e) { LOG.error("Admin token can not be acquired due to a client connection timeout. " + "You may try increasing the `quarkus.oidc.devui.web-client-timeout` property."); @@ -712,7 +724,7 @@ private void createRealm(WebClient client, String token, String keycloakUrl, Rea .putHeader(HttpHeaders.CONTENT_TYPE.toString(), "application/json") .putHeader(HttpHeaders.AUTHORIZATION.toString(), "Bearer " + token) .sendBuffer(Buffer.buffer().appendString(JsonSerialization.writeValueAsString(realm))) - .await().atMost(oidcConfig.devui.webClientTimeout); + .await().atMost(capturedDevServicesConfiguration.webClientTimeout); if (createRealmResponse.statusCode() > 299) { errors.add(String.format("Realm %s can not be created %d - %s ", realm.getRealm(), diff --git a/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java new file mode 100644 index 0000000000000..d0bbf4f651627 --- /dev/null +++ b/extensions/oidc-common/deployment/src/main/java/io/quarkus/oidc/common/deployment/devservices/keycloak/KeycloakDevServicesRequiredBuildItem.java @@ -0,0 +1,40 @@ +package io.quarkus.oidc.common.deployment.devservices.keycloak; + +import java.util.List; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A marker build item signifying that integrating extensions (like OIDC and OIDC client) + * are enabled. The Keycloak Dev Service will be started in DEV mode if at least one item is produced + * and the Dev Service is not disabled in other fashion. + */ +public final class KeycloakDevServicesRequiredBuildItem extends MultiBuildItem { + + enum Capability { + OIDC, + OIDC_CLIENT + } + + private final Capability capability; + + private KeycloakDevServicesRequiredBuildItem(Capability capability) { + this.capability = capability; + } + + static boolean setOidcConfigProperties(List items) { + return items.stream().anyMatch(i -> i.capability == Capability.OIDC); + } + + static boolean setOidcClientConfigProperties(List items) { + return items.stream().anyMatch(i -> i.capability == Capability.OIDC_CLIENT); + } + + public static KeycloakDevServicesRequiredBuildItem requireDevServiceForOidc() { + return new KeycloakDevServicesRequiredBuildItem(Capability.OIDC); + } + + public static KeycloakDevServicesRequiredBuildItem requireDevServiceForOidcClient() { + return new KeycloakDevServicesRequiredBuildItem(Capability.OIDC_CLIENT); + } +} diff --git a/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json b/extensions/oidc-common/deployment/src/main/resources/dev-service/upconfig.json similarity index 100% rename from extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json rename to extensions/oidc-common/deployment/src/main/resources/dev-service/upconfig.json diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/devservices/OidcDevServicesUtils.java similarity index 99% rename from extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java rename to extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/devservices/OidcDevServicesUtils.java index 5d9fc9335c0a8..ccaac2b087607 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevServicesUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/devservices/OidcDevServicesUtils.java @@ -1,4 +1,4 @@ -package io.quarkus.oidc.runtime.devui; +package io.quarkus.oidc.common.runtime.devservices; import java.time.Duration; import java.util.Map; diff --git a/extensions/oidc/deployment/pom.xml b/extensions/oidc/deployment/pom.xml index d7d217b5cb6c6..37a675695f74b 100644 --- a/extensions/oidc/deployment/pom.xml +++ b/extensions/oidc/deployment/pom.xml @@ -45,30 +45,10 @@ io.quarkus quarkus-jsonp-deployment - - org.keycloak - keycloak-core - - - com.sun.activation - jakarta.activation - - - org.eclipse.angus angus-activation - - org.testcontainers - testcontainers - - - junit - junit - - - io.quarkus quarkus-junit4-mock diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/DevUiConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/DevUiConfig.java index b16c3aebaa324..aca96f6e25a7d 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/DevUiConfig.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/DevUiConfig.java @@ -72,7 +72,6 @@ public String getGrantType() { * The WebClient timeout. * Use this property to configure how long an HTTP client used by Dev UI handlers will wait for a response when requesting * tokens from OpenId Connect Provider and sending them to the service endpoint. - * This timeout is also used by the OIDC dev service admin client. */ @ConfigItem(defaultValue = "4S") public Duration webClientTimeout; diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java index c5b4ccea69d6b..d454a59c0edb5 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java @@ -46,6 +46,7 @@ import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; +import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -57,6 +58,7 @@ import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; import io.quarkus.oidc.AuthorizationCodeFlow; import io.quarkus.oidc.BearerTokenAuthentication; import io.quarkus.oidc.IdToken; @@ -66,6 +68,7 @@ import io.quarkus.oidc.TokenIntrospectionCache; import io.quarkus.oidc.UserInfo; import io.quarkus.oidc.UserInfoCache; +import io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesRequiredBuildItem; import io.quarkus.oidc.runtime.BackChannelLogoutHandler; import io.quarkus.oidc.runtime.DefaultTenantConfigResolver; import io.quarkus.oidc.runtime.DefaultTokenIntrospectionUserInfoCache; @@ -395,6 +398,12 @@ List registerHttpAuthMechanismAnnotation() new HttpAuthMechanismAnnotationBuildItem(DotName.createSimple(BearerTokenAuthentication.class), BEARER_SCHEME)); } + @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) + KeycloakDevServicesRequiredBuildItem requireKeycloakDevService() { + // this needs to be done as OIDC Common doesn't know if the OIDC is enabled + return KeycloakDevServicesRequiredBuildItem.requireDevServiceForOidc(); + } + private static boolean isInjected(BeanRegistrationPhaseBuildItem beanRegistrationPhaseBuildItem, DotName requiredType, DotName withoutQualifier) { for (InjectionPointInfo injectionPoint : beanRegistrationPhaseBuildItem.getInjectionPoints()) { diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevServicesBuildItem.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevServicesBuildItem.java deleted file mode 100644 index 6fafbb817a38c..0000000000000 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevServicesBuildItem.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.quarkus.oidc.deployment.devservices; - -import io.quarkus.builder.item.SimpleBuildItem; - -/** - * Marker build item which indicates that Dev Services for OIDC are provided by another extension. - * Dev Services for Keycloak will be disabled if this item is detected. - */ -public class OidcDevServicesBuildItem extends SimpleBuildItem { - -} diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java index 7e094aeccf6c6..2288277b81099 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java @@ -23,9 +23,9 @@ import io.quarkus.oidc.OidcTenantConfig.ApplicationType; import io.quarkus.oidc.OidcTenantConfig.Provider; import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.common.runtime.devservices.OidcDevServicesUtils; import io.quarkus.oidc.deployment.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.devui.OidcDevJsonRpcService; -import io.quarkus.oidc.runtime.devui.OidcDevServicesUtils; import io.quarkus.oidc.runtime.devui.OidcDevUiRecorder; import io.quarkus.oidc.runtime.providers.KnownOidcProviders; import io.quarkus.runtime.configuration.ConfigUtils; diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java index 1127d02c099a6..df318895df580 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java @@ -17,6 +17,9 @@ import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.devui.spi.page.CardPageBuildItem; import io.quarkus.devui.spi.page.Page; +import io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakBuildTimeConfig; +import io.quarkus.oidc.common.deployment.devservices.keycloak.KeycloakDevServicesConfigBuildItem; +import io.quarkus.oidc.deployment.DevUiConfig; import io.quarkus.oidc.deployment.OidcBuildTimeConfig; import io.quarkus.oidc.deployment.devservices.AbstractDevUIProcessor; import io.quarkus.oidc.runtime.devui.OidcDevJsonRpcService; @@ -53,8 +56,7 @@ void produceProviderComponent(Optional confi capabilities, "Keycloak", configProps.get().getConfig().get("quarkus.oidc.application-type"), - oidcConfig.devui.grant.type.isPresent() ? oidcConfig.devui.grant.type.get().getGrantType() - : keycloakConfig.devservices.grant.type.getGrantType(), + oidcConfig.devui.grant.type.orElse(DevUiConfig.Grant.Type.CODE).getGrantType(), realmUrl + "/protocol/openid-connect/auth", realmUrl + "/protocol/openid-connect/token", realmUrl + "/protocol/openid-connect/logout", diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java index 0c26e16a76d05..cad90e96ddf06 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java @@ -1,12 +1,13 @@ package io.quarkus.oidc.runtime.devui; -import static io.quarkus.oidc.runtime.devui.OidcDevServicesUtils.getTokens; +import static io.quarkus.oidc.common.runtime.devservices.OidcDevServicesUtils.getTokens; import java.time.Duration; import java.util.List; import java.util.Map; import io.quarkus.arc.Arc; +import io.quarkus.oidc.common.runtime.devservices.OidcDevServicesUtils; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.common.annotation.NonBlocking; import io.smallrye.config.SmallRyeConfig; diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java index 55d2b70561e22..590bb62900adb 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/client/KeycloakTestClient.java @@ -23,7 +23,7 @@ public class KeycloakTestClient implements DevServicesContext.ContextAware { - private final static String CLIENT_AUTH_SERVER_URL_PROP = "client.quarkus.oidc.auth-server-url"; + private final static String CLIENT_AUTH_SERVER_URL_PROP = "quarkus.oidc-client.auth-server-url"; private final static String AUTH_SERVER_URL_PROP = "quarkus.oidc.auth-server-url"; private final static String CLIENT_ID_PROP = "quarkus.oidc.client-id"; private final static String CLIENT_SECRET_PROP = "quarkus.oidc.credentials.secret";