Skip to content

Commit ee2100b

Browse files
authored
Merge pull request quarkusio#44389 from michalvavrik/feature/kc-dev-svc-for-kc-admin-client
Start Keycloak Dev Services for standalone Keycloak Admin REST/RESTEasy clients
2 parents 3321050 + f983e20 commit ee2100b

File tree

15 files changed

+239
-37
lines changed

15 files changed

+239
-37
lines changed

extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesConfigurator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
public interface KeycloakDevServicesConfigurator {
88

9-
record ConfigPropertiesContext(String authServerInternalUrl, String oidcClientId, String oidcClientSecret) {
9+
record ConfigPropertiesContext(String authServerInternalUrl, String oidcClientId, String oidcClientSecret,
10+
String authServerInternalBaseUrl) {
1011
}
1112

1213
Map<String, String> createProperties(ConfigPropertiesContext context);

extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesProcessor.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,22 @@ private static void closeDevService() {
287287
capturedDevServicesConfiguration = null;
288288
}
289289

290+
private static String getBaseURL(String scheme, String host, Integer port) {
291+
return scheme + host + ":" + port;
292+
}
293+
290294
private static String startURL(String scheme, String host, Integer port, boolean isKeycloakX) {
291-
return scheme + host + ":" + port + (isKeycloakX ? "" : "/auth");
295+
return getBaseURL(scheme, host, port) + (isKeycloakX ? "" : "/auth");
296+
}
297+
298+
private static String startURL(String baseUrl, boolean isKeycloakX) {
299+
return baseUrl + (isKeycloakX ? "" : "/auth");
292300
}
293301

294302
private static Map<String, String> prepareConfiguration(
295303
BuildProducer<KeycloakDevServicesConfigBuildItem> keycloakBuildItemBuildProducer, String internalURL,
296304
String hostURL, List<RealmRepresentation> realmReps, List<String> errors,
297-
KeycloakDevServicesConfigurator devServicesConfigurator) {
305+
KeycloakDevServicesConfigurator devServicesConfigurator, String internalBaseUrl) {
298306
final String realmName = realmReps != null && !realmReps.isEmpty() ? realmReps.iterator().next().getRealm()
299307
: getDefaultRealmName();
300308
final String authServerInternalUrl = realmsURL(internalURL, realmName);
@@ -338,7 +346,8 @@ private static Map<String, String> prepareConfiguration(
338346
}
339347

340348
Map<String, String> configProperties = new HashMap<>();
341-
var configPropertiesContext = new ConfigPropertiesContext(authServerInternalUrl, oidcClientId, oidcClientSecret);
349+
var configPropertiesContext = new ConfigPropertiesContext(authServerInternalUrl, oidcClientId, oidcClientSecret,
350+
internalBaseUrl);
342351
configProperties.putAll(devServicesConfigurator.createProperties(configPropertiesContext));
343352
configProperties.put(KEYCLOAK_URL_KEY, internalURL);
344353
configProperties.put(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl);
@@ -397,8 +406,9 @@ private static RunningDevService startContainer(
397406
oidcContainer.withEnv(capturedDevServicesConfiguration.containerEnv());
398407
oidcContainer.start();
399408

400-
String internalUrl = startURL((oidcContainer.isHttps() ? "https://" : "http://"), oidcContainer.getHost(),
401-
oidcContainer.getPort(), oidcContainer.keycloakX);
409+
String internalBaseUrl = getBaseURL((oidcContainer.isHttps() ? "https://" : "http://"), oidcContainer.getHost(),
410+
oidcContainer.getPort());
411+
String internalUrl = startURL(internalBaseUrl, oidcContainer.keycloakX);
402412
String hostUrl = oidcContainer.useSharedNetwork
403413
// we need to use auto-detected host and port, so it works when docker host != localhost
404414
? startURL("http://", oidcContainer.getSharedNetworkExternalHost(),
@@ -407,17 +417,17 @@ private static RunningDevService startContainer(
407417
: null;
408418

409419
Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer, internalUrl, hostUrl,
410-
oidcContainer.realmReps, errors, devServicesConfigurator);
420+
oidcContainer.realmReps, errors, devServicesConfigurator, internalBaseUrl);
411421
return new RunningDevService(KEYCLOAK_CONTAINER_NAME, oidcContainer.getContainerId(),
412422
oidcContainer::close, configs);
413423
};
414424

415425
return maybeContainerAddress
416426
.map(containerAddress -> {
417427
// TODO: this probably needs to be addressed
418-
Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer,
419-
getSharedContainerUrl(containerAddress),
420-
getSharedContainerUrl(containerAddress), null, errors, devServicesConfigurator);
428+
String sharedContainerUrl = getSharedContainerUrl(containerAddress);
429+
Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer, sharedContainerUrl,
430+
sharedContainerUrl, null, errors, devServicesConfigurator, sharedContainerUrl);
421431
return new RunningDevService(KEYCLOAK_CONTAINER_NAME, containerAddress.getId(), null, configs);
422432
})
423433
.orElseGet(defaultKeycloakContainerSupplier);

extensions/keycloak-admin-client-common/runtime/src/main/java/io/quarkus/keycloak/admin/client/common/KeycloakAdminClientConfig.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ public interface KeycloakAdminClientConfig {
1616

1717
/**
1818
* Keycloak server URL, for example, `https://host:port`.
19-
* If this property is not set then the Keycloak Admin Client injection will fail - use
20-
* {@linkplain org.keycloak.admin.client.KeycloakBuilder}
21-
* to create it instead.
19+
* When the Keycloak Dev Services is started and this property is not configured,
20+
* Quarkus points the 'quarkus.keycloak.admin-client.server-url' configuration property to started Keycloak container.
21+
* In other cases, when this property is not set then the Keycloak Admin Client injection will fail - use
22+
* {@linkplain org.keycloak.admin.client.KeycloakBuilder} to create the client instead.
2223
*/
2324
Optional<String> serverUrl();
2425

extensions/keycloak-admin-rest-client/deployment/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
<groupId>io.quarkus</groupId>
3030
<artifactId>quarkus-keycloak-admin-client-common-deployment</artifactId>
3131
</dependency>
32+
<dependency>
33+
<groupId>io.quarkus</groupId>
34+
<artifactId>quarkus-devservices-keycloak</artifactId>
35+
</dependency>
3236
<!-- Test dependencies -->
3337
<dependency>
3438
<groupId>io.quarkus</groupId>
@@ -45,11 +49,6 @@
4549
<artifactId>quarkus-rest-jackson-deployment</artifactId>
4650
<scope>test</scope>
4751
</dependency>
48-
<dependency>
49-
<groupId>io.quarkus</groupId>
50-
<artifactId>quarkus-oidc-deployment</artifactId>
51-
<scope>test</scope>
52-
</dependency>
5352
<dependency>
5453
<groupId>io.smallrye.certs</groupId>
5554
<artifactId>smallrye-certificate-generator-junit5</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.quarkus.keycloak.admin.client.reactive.devservices;
2+
3+
import java.util.Map;
4+
5+
import io.quarkus.deployment.IsDevelopment;
6+
import io.quarkus.deployment.IsNormal;
7+
import io.quarkus.deployment.annotations.BuildStep;
8+
import io.quarkus.deployment.annotations.BuildSteps;
9+
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
10+
import io.quarkus.devservices.keycloak.KeycloakAdminPageBuildItem;
11+
import io.quarkus.devservices.keycloak.KeycloakDevServicesRequiredBuildItem;
12+
import io.quarkus.devui.spi.page.CardPageBuildItem;
13+
import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled;
14+
15+
@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class,
16+
KeycloakAdminClientInjectionEnabled.class })
17+
public class KeycloakDevServiceRequiredBuildStep {
18+
19+
private static final String SERVER_URL_CONFIG_KEY = "quarkus.keycloak.admin-client.server-url";
20+
21+
@BuildStep
22+
KeycloakDevServicesRequiredBuildItem requireKeycloakDevService() {
23+
return KeycloakDevServicesRequiredBuildItem.of(
24+
ctx -> Map.of(SERVER_URL_CONFIG_KEY, ctx.authServerInternalBaseUrl()),
25+
SERVER_URL_CONFIG_KEY);
26+
}
27+
28+
@BuildStep(onlyIf = IsDevelopment.class)
29+
KeycloakAdminPageBuildItem addCardWithLinkToKeycloakAdmin() {
30+
return new KeycloakAdminPageBuildItem(new CardPageBuildItem());
31+
}
32+
}

extensions/keycloak-admin-rest-client/deployment/src/test/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientInjectionDevServicesTest.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
import org.keycloak.admin.client.Keycloak;
1616
import org.keycloak.representations.idm.RoleRepresentation;
1717

18-
import io.quarkus.test.QuarkusDevModeTest;
18+
import io.quarkus.builder.Version;
19+
import io.quarkus.maven.dependency.Dependency;
20+
import io.quarkus.test.QuarkusUnitTest;
1921
import io.restassured.RestAssured;
2022
import io.restassured.response.Response;
2123

2224
public class KeycloakAdminClientInjectionDevServicesTest {
2325

2426
@RegisterExtension
25-
final static QuarkusDevModeTest app = new QuarkusDevModeTest()
27+
final static QuarkusUnitTest app = new QuarkusUnitTest()
2628
.withApplicationRoot(jar -> jar
2729
.addClasses(AdminResource.class)
28-
.addAsResource("app-dev-mode-config.properties", "application.properties"));
30+
.addAsResource("app-dev-mode-config.properties", "application.properties"))
31+
// intention of this forced dependency is to test backwards compatibility
32+
// when users started Keycloak Dev Service by adding OIDC extension and configured 'server-url'
33+
.setForcedDependencies(
34+
List.of(Dependency.of("io.quarkus", "quarkus-oidc-deployment", Version.getVersion())));
2935

3036
@Test
3137
public void testGetRoles() {

extensions/keycloak-admin-rest-client/deployment/src/test/java/io/quarkus/keycloak/admin/client/reactive/KeycloakAdminClientMutualTlsDevServicesTest.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import org.keycloak.representations.idm.RoleRepresentation;
1919
import org.keycloak.representations.idm.RolesRepresentation;
2020

21-
import io.quarkus.test.QuarkusDevModeTest;
21+
import io.quarkus.builder.Version;
22+
import io.quarkus.maven.dependency.Dependency;
23+
import io.quarkus.test.QuarkusUnitTest;
2224
import io.restassured.RestAssured;
2325
import io.smallrye.certs.Format;
2426
import io.smallrye.certs.junit5.Certificate;
@@ -29,14 +31,18 @@
2931
public class KeycloakAdminClientMutualTlsDevServicesTest {
3032

3133
@RegisterExtension
32-
final static QuarkusDevModeTest app = new QuarkusDevModeTest()
34+
final static QuarkusUnitTest app = new QuarkusUnitTest()
3335
.withApplicationRoot(jar -> jar
3436
.addClasses(MtlsResource.class)
3537
.addAsResource(new File("target/certs/mtls-test-keystore.p12"), "server-keystore.p12")
3638
.addAsResource(new File("target/certs/mtls-test-server-ca.crt"), "server-ca.crt")
3739
.addAsResource(new File("target/certs/mtls-test-client-keystore.p12"), "client-keystore.p12")
3840
.addAsResource(new File("target/certs/mtls-test-client-truststore.p12"), "client-truststore.p12")
39-
.addAsResource("app-mtls-config.properties", "application.properties"));
41+
.addAsResource("app-mtls-config.properties", "application.properties"))
42+
// intention of this forced dependency is to test backwards compatibility
43+
// when users started Keycloak Dev Service by adding OIDC extension and configured 'server-url'
44+
.setForcedDependencies(
45+
List.of(Dependency.of("io.quarkus", "quarkus-oidc-deployment", Version.getVersion())));
4046

4147
@Test
4248
public void testCreateRealm() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.quarkus.keycloak.admin.client.reactive;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.util.List;
8+
9+
import jakarta.inject.Inject;
10+
import jakarta.ws.rs.GET;
11+
import jakarta.ws.rs.Path;
12+
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.RegisterExtension;
15+
import org.keycloak.admin.client.Keycloak;
16+
import org.keycloak.representations.idm.RoleRepresentation;
17+
18+
import io.quarkus.test.QuarkusDevModeTest;
19+
import io.restassured.RestAssured;
20+
import io.restassured.response.Response;
21+
22+
public class KeycloakAdminClientZeroConfigDevServicesTest {
23+
24+
@RegisterExtension
25+
final static QuarkusDevModeTest app = new QuarkusDevModeTest()
26+
.withApplicationRoot(jar -> jar.addClasses(AdminResource.class));
27+
28+
@Test
29+
public void testGetRoles() {
30+
// use 'password' grant type
31+
final Response getRolesReq = RestAssured.given().get("/api/admin/roles");
32+
assertEquals(200, getRolesReq.statusCode());
33+
final List<RoleRepresentation> roles = getRolesReq.jsonPath().getList(".", RoleRepresentation.class);
34+
assertNotNull(roles);
35+
// assert there are roles admin and user (among others)
36+
assertTrue(roles.stream().anyMatch(rr -> "user".equals(rr.getName())));
37+
assertTrue(roles.stream().anyMatch(rr -> "admin".equals(rr.getName())));
38+
}
39+
40+
@Path("/api/admin")
41+
public static class AdminResource {
42+
43+
@Inject
44+
Keycloak keycloak;
45+
46+
@GET
47+
@Path("/roles")
48+
public List<RoleRepresentation> getRoles() {
49+
return keycloak.realm("quarkus").roles().list();
50+
}
51+
52+
}
53+
}

extensions/keycloak-admin-rest-client/deployment/src/test/resources/app-dev-mode-config.properties

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22
quarkus.keycloak.devservices.port=8082
33

44
# Configure Keycloak Admin Client
5-
quarkus.keycloak.admin-client=true
65
quarkus.keycloak.admin-client.server-url=http://localhost:${quarkus.keycloak.devservices.port}

extensions/keycloak-admin-resteasy-client/deployment/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
<groupId>io.quarkus</groupId>
3838
<artifactId>quarkus-keycloak-admin-client-common-deployment</artifactId>
3939
</dependency>
40+
<dependency>
41+
<groupId>io.quarkus</groupId>
42+
<artifactId>quarkus-devservices-keycloak</artifactId>
43+
</dependency>
4044
<!-- Test dependencies -->
4145
<dependency>
4246
<groupId>io.quarkus</groupId>
@@ -53,11 +57,6 @@
5357
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
5458
<scope>test</scope>
5559
</dependency>
56-
<dependency>
57-
<groupId>io.quarkus</groupId>
58-
<artifactId>quarkus-oidc-deployment</artifactId>
59-
<scope>test</scope>
60-
</dependency>
6160
<dependency>
6261
<groupId>io.smallrye.certs</groupId>
6362
<artifactId>smallrye-certificate-generator-junit5</artifactId>

0 commit comments

Comments
 (0)