Skip to content

Commit

Permalink
Start Keycloak Dev Svc for standalone OIDC Client
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed Sep 30, 2024
1 parent 4cab5df commit 06da6fe
Show file tree
Hide file tree
Showing 20 changed files with 253 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<KeycloakDevServicesConfigBuildItem> configProps,
BuildProducer<CardPageBuildItem> 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:
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions extensions/oidc-common/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,31 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-common</artifactId>
</dependency>
<!-- Dev Service dependencies -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand All @@ -57,6 +82,9 @@
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-AlegacyConfigRoot=true</arg>
</compilerArgs>
</configuration>
</execution>
</executions>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -191,54 +194,6 @@ public class DevServicesConfig {
@ConfigDocMapKey("role-name")
public Map<String, List<String>> 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.
* <p>
Expand All @@ -254,6 +209,14 @@ public String getGrantType() {
@ConfigDocMapKey("environment-variable-name")
public Map<String, String> 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.oidc.deployment.devservices.keycloak;
package io.quarkus.oidc.common.deployment.devservices.keycloak;

import java.util.Map;

Expand All @@ -25,7 +25,7 @@ public Map<String, String> getConfig() {
return config;
}

boolean isContainerRestarted() {
public boolean isContainerRestarted() {
return containerRestarted;
}
}
Loading

0 comments on commit 06da6fe

Please sign in to comment.