Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start Keycloak Dev Services for standalone Keycloak Admin REST/RESTEasy clients #44389

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

public interface KeycloakDevServicesConfigurator {

record ConfigPropertiesContext(String authServerInternalUrl, String oidcClientId, String oidcClientSecret) {
record ConfigPropertiesContext(String authServerInternalUrl, String oidcClientId, String oidcClientSecret,
String authServerInternalBaseUrl) {
}

Map<String, String> createProperties(ConfigPropertiesContext context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,22 @@ private static void closeDevService() {
capturedDevServicesConfiguration = null;
}

private static String getBaseURL(String scheme, String host, Integer port) {
return scheme + host + ":" + port;
}

private static String startURL(String scheme, String host, Integer port, boolean isKeycloakX) {
return scheme + host + ":" + port + (isKeycloakX ? "" : "/auth");
return getBaseURL(scheme, host, port) + (isKeycloakX ? "" : "/auth");
}

private static String startURL(String baseUrl, boolean isKeycloakX) {
return baseUrl + (isKeycloakX ? "" : "/auth");
}

private static Map<String, String> prepareConfiguration(
BuildProducer<KeycloakDevServicesConfigBuildItem> keycloakBuildItemBuildProducer, String internalURL,
String hostURL, List<RealmRepresentation> realmReps, List<String> errors,
KeycloakDevServicesConfigurator devServicesConfigurator) {
KeycloakDevServicesConfigurator devServicesConfigurator, String internalBaseUrl) {
final String realmName = realmReps != null && !realmReps.isEmpty() ? realmReps.iterator().next().getRealm()
: getDefaultRealmName();
final String authServerInternalUrl = realmsURL(internalURL, realmName);
Expand Down Expand Up @@ -338,7 +346,8 @@ private static Map<String, String> prepareConfiguration(
}

Map<String, String> configProperties = new HashMap<>();
var configPropertiesContext = new ConfigPropertiesContext(authServerInternalUrl, oidcClientId, oidcClientSecret);
var configPropertiesContext = new ConfigPropertiesContext(authServerInternalUrl, oidcClientId, oidcClientSecret,
internalBaseUrl);
configProperties.putAll(devServicesConfigurator.createProperties(configPropertiesContext));
configProperties.put(KEYCLOAK_URL_KEY, internalURL);
configProperties.put(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl);
Expand Down Expand Up @@ -397,8 +406,9 @@ private static RunningDevService startContainer(
oidcContainer.withEnv(capturedDevServicesConfiguration.containerEnv());
oidcContainer.start();

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

Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer, internalUrl, hostUrl,
oidcContainer.realmReps, errors, devServicesConfigurator);
oidcContainer.realmReps, errors, devServicesConfigurator, internalBaseUrl);
return new RunningDevService(KEYCLOAK_CONTAINER_NAME, oidcContainer.getContainerId(),
oidcContainer::close, configs);
};

return maybeContainerAddress
.map(containerAddress -> {
// TODO: this probably needs to be addressed
Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer,
getSharedContainerUrl(containerAddress),
getSharedContainerUrl(containerAddress), null, errors, devServicesConfigurator);
String sharedContainerUrl = getSharedContainerUrl(containerAddress);
Map<String, String> configs = prepareConfiguration(keycloakBuildItemBuildProducer, sharedContainerUrl,
sharedContainerUrl, null, errors, devServicesConfigurator, sharedContainerUrl);
return new RunningDevService(KEYCLOAK_CONTAINER_NAME, containerAddress.getId(), null, configs);
})
.orElseGet(defaultKeycloakContainerSupplier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ public interface KeycloakAdminClientConfig {

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

Expand Down
9 changes: 4 additions & 5 deletions extensions/keycloak-admin-rest-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-client-common-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-keycloak</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
Expand All @@ -45,11 +49,6 @@
<artifactId>quarkus-rest-jackson-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.certs</groupId>
<artifactId>smallrye-certificate-generator-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.keycloak.admin.client.reactive.devservices;

import java.util.Map;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
import io.quarkus.devservices.keycloak.KeycloakAdminPageBuildItem;
import io.quarkus.devservices.keycloak.KeycloakDevServicesRequiredBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled;

@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class,
KeycloakAdminClientInjectionEnabled.class })
public class KeycloakDevServiceRequiredBuildStep {

private static final String SERVER_URL_CONFIG_KEY = "quarkus.keycloak.admin-client.server-url";

@BuildStep
KeycloakDevServicesRequiredBuildItem requireKeycloakDevService() {
return KeycloakDevServicesRequiredBuildItem.of(
ctx -> Map.of(SERVER_URL_CONFIG_KEY, ctx.authServerInternalBaseUrl()),
SERVER_URL_CONFIG_KEY);
}

@BuildStep(onlyIf = IsDevelopment.class)
KeycloakAdminPageBuildItem addCardWithLinkToKeycloakAdmin() {
return new KeycloakAdminPageBuildItem(new CardPageBuildItem());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RoleRepresentation;

import io.quarkus.test.QuarkusDevModeTest;
import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.restassured.response.Response;

public class KeycloakAdminClientInjectionDevServicesTest {

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

@Test
public void testGetRoles() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;

import io.quarkus.test.QuarkusDevModeTest;
import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.smallrye.certs.Format;
import io.smallrye.certs.junit5.Certificate;
Expand All @@ -29,14 +31,18 @@
public class KeycloakAdminClientMutualTlsDevServicesTest {

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

@Test
public void testCreateRealm() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.keycloak.admin.client.reactive;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RoleRepresentation;

import io.quarkus.test.QuarkusDevModeTest;
import io.restassured.RestAssured;
import io.restassured.response.Response;

public class KeycloakAdminClientZeroConfigDevServicesTest {

@RegisterExtension
final static QuarkusDevModeTest app = new QuarkusDevModeTest()
.withApplicationRoot(jar -> jar.addClasses(AdminResource.class));

@Test
public void testGetRoles() {
// use 'password' grant type
final Response getRolesReq = RestAssured.given().get("/api/admin/roles");
assertEquals(200, getRolesReq.statusCode());
final List<RoleRepresentation> roles = getRolesReq.jsonPath().getList(".", RoleRepresentation.class);
assertNotNull(roles);
// assert there are roles admin and user (among others)
assertTrue(roles.stream().anyMatch(rr -> "user".equals(rr.getName())));
assertTrue(roles.stream().anyMatch(rr -> "admin".equals(rr.getName())));
}

@Path("/api/admin")
public static class AdminResource {

@Inject
Keycloak keycloak;

@GET
@Path("/roles")
public List<RoleRepresentation> getRoles() {
return keycloak.realm("quarkus").roles().list();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
quarkus.keycloak.devservices.port=8082

# Configure Keycloak Admin Client
quarkus.keycloak.admin-client=true
quarkus.keycloak.admin-client.server-url=http://localhost:${quarkus.keycloak.devservices.port}
9 changes: 4 additions & 5 deletions extensions/keycloak-admin-resteasy-client/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-keycloak-admin-client-common-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-keycloak</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
Expand All @@ -53,11 +57,6 @@
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.smallrye.certs</groupId>
<artifactId>smallrye-certificate-generator-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.keycloak.adminclient.deployment.devservices;

import java.util.Map;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
import io.quarkus.devservices.keycloak.KeycloakAdminPageBuildItem;
import io.quarkus.devservices.keycloak.KeycloakDevServicesRequiredBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.keycloak.admin.client.common.KeycloakAdminClientInjectionEnabled;

@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class,
KeycloakAdminClientInjectionEnabled.class })
public class KeycloakDevServiceRequiredBuildStep {

private static final String SERVER_URL_CONFIG_KEY = "quarkus.keycloak.admin-client.server-url";

@BuildStep
KeycloakDevServicesRequiredBuildItem requireKeycloakDevService() {
return KeycloakDevServicesRequiredBuildItem.of(
ctx -> Map.of(SERVER_URL_CONFIG_KEY, ctx.authServerInternalBaseUrl()),
SERVER_URL_CONFIG_KEY);
}

@BuildStep(onlyIf = IsDevelopment.class)
KeycloakAdminPageBuildItem addCardWithLinkToKeycloakAdmin() {
return new KeycloakAdminPageBuildItem(new CardPageBuildItem());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RoleRepresentation;

import io.quarkus.test.QuarkusDevModeTest;
import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.restassured.response.Response;

public class KeycloakAdminClientInjectionDevServicesTest {

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

@Test
public void testGetRoles() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;

import io.quarkus.test.QuarkusDevModeTest;
import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.smallrye.certs.Format;
import io.smallrye.certs.junit5.Certificate;
Expand All @@ -29,14 +31,18 @@
public class KeycloakAdminClientMutualTlsDevServicesTest {

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

@Test
public void testCreateRealm() {
Expand Down
Loading