diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java index 1bd7965cbf692..a648b03c1a0dc 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -92,6 +92,7 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_CONTAINER_NAME = "keycloak"; private static final int KEYCLOAK_PORT = 8080; + private static final int KEYCLOAK_HTTPS_PORT = 8443; private static final String KEYCLOAK_LEGACY_IMAGE_VERSION_PART = "-legacy"; @@ -253,8 +254,8 @@ public void run() { return devService.toBuildItem(); } - private String startURL(String host, Integer port, boolean isKeycloakX) { - return "http://" + host + ":" + port + (isKeycloakX ? "" : "/auth"); + private String startURL(String scheme, String host, Integer port, boolean isKeycloakX) { + return scheme + host + ":" + port + (isKeycloakX ? "" : "/auth"); } private Map prepareConfiguration( @@ -383,10 +384,12 @@ private RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuild oidcContainer.withEnv(capturedDevServicesConfiguration.containerEnv); oidcContainer.start(); - String internalUrl = startURL(oidcContainer.getHost(), oidcContainer.getPort(), oidcContainer.keycloakX); + String internalUrl = startURL((oidcContainer.isHttps() ? "https://" : "http://"), oidcContainer.getHost(), + oidcContainer.getPort(), oidcContainer.keycloakX); String hostUrl = oidcContainer.useSharedNetwork // we need to use auto-detected host and port, so it works when docker host != localhost - ? startURL(oidcContainer.getSharedNetworkExternalHost(), oidcContainer.getSharedNetworkExternalPort(), + ? startURL("http://", oidcContainer.getSharedNetworkExternalHost(), + oidcContainer.getSharedNetworkExternalPort(), oidcContainer.keycloakX) : null; @@ -518,6 +521,9 @@ protected void configure() { withCommand(startCommand.orElse(KEYCLOAK_QUARKUS_START_CMD) + (useSharedNetwork ? " --hostname-port=" + fixedExposedPort.getAsInt() : "")); addUpConfigResource(); + if (isHttps()) { + addExposedPort(KEYCLOAK_HTTPS_PORT); + } } else { addEnv(KEYCLOAK_WILDFLY_USER_PROP, KEYCLOAK_ADMIN_USER); addEnv(KEYCLOAK_WILDFLY_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD); @@ -639,7 +645,11 @@ public int getPort() { if (fixedExposedPort.isPresent()) { return fixedExposedPort.getAsInt(); } - return getFirstMappedPort(); + return super.getMappedPort(isHttps() ? KEYCLOAK_HTTPS_PORT : KEYCLOAK_PORT); + } + + public boolean isHttps() { + return startCommand.isPresent() && startCommand.get().contains("--https"); } } diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index 8e4c5c8e9dafb..ff0b6fdd56069 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -26,16 +26,10 @@ io.quarkus quarkus-websockets - - org.keycloak - keycloak-core - - - com.sun.activation - jakarta.activation - - + io.quarkus + quarkus-test-keycloak-server + test org.eclipse.angus @@ -46,10 +40,7 @@ quarkus-test-security-oidc test - - org.testcontainers - testcontainers - + io.quarkus quarkus-junit5 diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index cb8865a6636de..d8b1ec529ad7c 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -1,19 +1,22 @@ -# Configuration file -quarkus.oidc.auth-server-url=replaced-by-tests -quarkus.oidc.client-id=quarkus-service-app -quarkus.oidc.credentials.secret=secret +quarkus.keycloak.devservices.create-realm=false +quarkus.keycloak.devservices.start-command=start --https-client-auth=required --hostname-strict=false --https-key-store-file=/etc/server-keystore.p12 --https-trust-store-file=/etc/server-truststore.p12 --https-trust-store-password=password --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json +quarkus.keycloak.devservices.resource-aliases.keystore=server-keystore.p12 +quarkus.keycloak.devservices.resource-aliases.truststore=server-truststore.p12 +quarkus.keycloak.devservices.resource-mappings.keystore=/etc/server-keystore.p12 +quarkus.keycloak.devservices.resource-mappings.truststore=/etc/server-truststore.p12 + quarkus.oidc.token.principal-claim=email quarkus.oidc.tls.verification=required -quarkus.oidc.tls.trust-store-file=client-truststore.jks +quarkus.oidc.tls.trust-store-file=client-truststore.p12 quarkus.oidc.tls.trust-store-password=password -quarkus.oidc.tls.key-store-file=client-keystore.jks +quarkus.oidc.tls.key-store-file=client-keystore.p12 quarkus.oidc.tls.key-store-password=password %tls-registry.quarkus.oidc.tls.tls-configuration-name=oidc-tls -%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=client-keystore.jks +%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=client-keystore.p12 %tls-registry.quarkus.tls.oidc-tls.key-store.jks.password=password -%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=client-truststore.jks +%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=client-truststore.p12 %tls-registry.quarkus.tls.oidc-tls.trust-store.jks.password=password %tls-registry.quarkus.oidc.tls.verification= %tls-registry.quarkus.oidc.tls.trust-store-file= @@ -21,7 +24,7 @@ quarkus.oidc.tls.key-store-password=password %tls-registry.quarkus.oidc.tls.key-store-file= %tls-registry.quarkus.oidc.tls.key-store-password= -quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks +quarkus.native.additional-build-args=-H:IncludeResources=.*\\.p12 quarkus.http.cors=true quarkus.http.cors.origins=* diff --git a/integration-tests/oidc/src/main/resources/client-keystore.jks b/integration-tests/oidc/src/main/resources/client-keystore.jks deleted file mode 100644 index cf6d6ba454864..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/client-keystore.jks and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/client-keystore.p12 b/integration-tests/oidc/src/main/resources/client-keystore.p12 new file mode 100644 index 0000000000000..11df9af88cd73 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-keystore.p12 differ diff --git a/integration-tests/oidc/src/main/resources/client-truststore.jks b/integration-tests/oidc/src/main/resources/client-truststore.jks deleted file mode 100644 index d89769a593721..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/client-truststore.jks and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/client-truststore.p12 b/integration-tests/oidc/src/main/resources/client-truststore.p12 new file mode 100644 index 0000000000000..8a9cefe2f5506 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-truststore.p12 differ diff --git a/integration-tests/oidc/src/main/resources/server-keystore.jks b/integration-tests/oidc/src/main/resources/server-keystore.jks deleted file mode 100644 index 6961a6b1d0203..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/server-keystore.jks and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/server-keystore.p12 b/integration-tests/oidc/src/main/resources/server-keystore.p12 new file mode 100644 index 0000000000000..6e476f513ef30 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-keystore.p12 differ diff --git a/integration-tests/oidc/src/main/resources/server-truststore.jks b/integration-tests/oidc/src/main/resources/server-truststore.jks deleted file mode 100644 index 8ec8e126507b6..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/server-truststore.jks and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/server-truststore.p12 b/integration-tests/oidc/src/main/resources/server-truststore.p12 new file mode 100644 index 0000000000000..d006d5d2dd43e Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-truststore.p12 differ diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/AbstractBearerTokenAuthorizationTest.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/AbstractBearerTokenAuthorizationTest.java index b0fa872aaa64d..a1ec95be5d378 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/AbstractBearerTokenAuthorizationTest.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/AbstractBearerTokenAuthorizationTest.java @@ -1,7 +1,5 @@ package io.quarkus.it.keycloak; -import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken; -import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getRefreshToken; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; @@ -12,10 +10,14 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; import io.restassured.RestAssured; public abstract class AbstractBearerTokenAuthorizationTest { + KeycloakTestClient client = new KeycloakTestClient(new Tls()); + @Test public void testSecureAccessSuccessWithCors() { String origin = "http://custom.origin.quarkus"; @@ -109,7 +111,7 @@ public void testAccessAdminResourceCustomHeaderBearerScheme() { @Test public void testAccessAdminResourceWithRefreshToken() { - RestAssured.given().auth().oauth2(getRefreshToken("admin")) + RestAssured.given().auth().oauth2(client.getRefreshToken("admin")) .when().get("/api/admin") .then() .statusCode(401); @@ -225,4 +227,8 @@ public void testAuthenticationEvent() { .statusCode(200) .body(Matchers.is("true")); } + + String getAccessToken(String username) { + return client.getAccessToken(username); + } } diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java index 125145a5d84c0..94681a73185d9 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java @@ -1,6 +1,6 @@ package io.quarkus.it.keycloak; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; import org.junit.jupiter.api.Test; @@ -14,6 +14,6 @@ public class BearerTokenAuthorizationInGraalITCase extends BearerTokenAuthorizat @Test public void testDevServicesProperties() { - assertThat(context.devServicesProperties()).isEmpty(); + assertFalse(context.devServicesProperties().isEmpty()); } } diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java index 813c1d8e7cac7..57e855d482d07 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java @@ -1,85 +1,36 @@ package io.quarkus.it.keycloak; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.jboss.logging.Logger; -import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.util.JsonSerialization; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; +import io.quarkus.test.common.DevServicesContext; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; -import io.restassured.RestAssured; -import io.restassured.specification.RequestSpecification; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; -public class KeycloakXTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager { - private static final Logger LOGGER = Logger.getLogger(KeycloakXTestResourceLifecycleManager.class); - private GenericContainer keycloak; +public class KeycloakXTestResourceLifecycleManager + implements QuarkusTestResourceLifecycleManager, DevServicesContext.ContextAware { - private static String KEYCLOAK_SERVER_URL; private static final String KEYCLOAK_REALM = "quarkus"; - private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-service-app"; - private static final String KEYCLOAK_VERSION = System.getProperty("keycloak.version", "23.0.1"); + private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-app"; - private static String CLIENT_KEYSTORE = "client-keystore.jks"; - private static String CLIENT_TRUSTSTORE = "client-truststore.jks"; + final KeycloakTestClient client = new KeycloakTestClient(new Tls()); - private static String SERVER_KEYSTORE = "server-keystore.jks"; - private static String SERVER_KEYSTORE_MOUNTED_PATH = "/etc/server-keystore.jks"; - private static String SERVER_TRUSTSTORE = "server-truststore.jks"; - private static String SERVER_TRUSTSTORE_MOUNTED_PATH = "/etc/server-truststore.jks"; - - @SuppressWarnings("resource") @Override public Map start() { - keycloak = new GenericContainer<>("quay.io/keycloak/keycloak:" + KEYCLOAK_VERSION) - .withExposedPorts(8080, 8443) - .withEnv("KEYCLOAK_ADMIN", "admin") - .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin") - .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1)); - - keycloak = keycloak - .withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY) - .withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY) - .withClasspathResourceMapping("/upconfig.json", "/opt/keycloak/upconfig.json", BindMode.READ_ONLY) - .withCommand("build --https-client-auth=required") - .withCommand(String.format( - "start --https-client-auth=required --hostname-strict=false" - + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password" - + " --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json", - SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH)); - keycloak.start(); - LOGGER.info(keycloak.getLogs()); - - KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443); RealmRepresentation realm = createRealm(KEYCLOAK_REALM); - postRealm(realm); - - return Map.of("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); - } + client.createRealm(realm); - private static void postRealm(RealmRepresentation realm) { - try { - createRequestSpec().auth().oauth2(getAdminAccessToken()) - .contentType("application/json") - .body(JsonSerialization.writeValueAsBytes(realm)) - .when() - .post(KEYCLOAK_SERVER_URL + "/admin/realms").then() - .statusCode(201); - } catch (IOException e) { - throw new RuntimeException(e); - } + return Map.of(); } private static RealmRepresentation createRealm(String name) { @@ -111,17 +62,6 @@ private static RealmRepresentation createRealm(String name) { return realm; } - private static String getAdminAccessToken() { - return createRequestSpec() - .param("grant_type", "password") - .param("username", "admin") - .param("password", "admin") - .param("client_id", "admin-cli") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); - } - private static ClientRepresentation createServiceClient(String clientId) { ClientRepresentation client = new ClientRepresentation(); @@ -155,39 +95,13 @@ private static UserRepresentation createUser(String username, List realm return user; } - public static String getAccessToken(String userName) { - return createRequestSpec().param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", KEYCLOAK_SERVICE_CLIENT) - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); - } - - public static String getRefreshToken(String userName) { - return createRequestSpec().param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", KEYCLOAK_SERVICE_CLIENT) - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getRefreshToken(); + @Override + public void setIntegrationTestContext(DevServicesContext context) { + client.setIntegrationTestContext(context); } @Override public void stop() { - createRequestSpec().auth().oauth2(getAdminAccessToken()) - .when() - .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); - - keycloak.stop(); } - private static RequestSpecification createRequestSpec() { - return RestAssured.given().trustStore(CLIENT_TRUSTSTORE, "password") - .keyStore(CLIENT_KEYSTORE, "password"); - } } diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java index b998d0bf7bc3b..3a75d88294dc4 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java @@ -1,7 +1,5 @@ package io.quarkus.it.keycloak; -import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken; - import java.net.URI; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; @@ -18,6 +16,8 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; import io.quarkus.websockets.BearerTokenClientEndpointConfigurator; @QuarkusTest @@ -27,6 +27,8 @@ public class WebsocketOidcTestCase { @TestHTTPResource("secured-hello") URI wsUri; + KeycloakTestClient client = new KeycloakTestClient(new Tls()); + @Test public void websocketTest() throws Exception { @@ -42,7 +44,7 @@ public void onMessage(String s) { }); session.getAsyncRemote().sendText("hello"); } - }, new BearerTokenClientEndpointConfigurator(getAccessToken("alice")), wsUri); + }, new BearerTokenClientEndpointConfigurator(client.getAccessToken("alice")), wsUri); try { Assertions.assertEquals("hello alice@gmail.com", message.poll(20, TimeUnit.SECONDS)); 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 ba0f7171fc8f6..55d2b70561e22 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 @@ -28,20 +28,26 @@ public class KeycloakTestClient implements DevServicesContext.ContextAware { private final static String CLIENT_ID_PROP = "quarkus.oidc.client-id"; private final static String CLIENT_SECRET_PROP = "quarkus.oidc.credentials.secret"; - static { - RestAssured.useRelaxedHTTPSValidation(); - } - private DevServicesContext testContext; private final String authServerUrl; + private final Tls tls; public KeycloakTestClient() { - this(null); + this(null, null); + } + + public KeycloakTestClient(Tls tls) { + this(null, tls); } public KeycloakTestClient(String authServerUrl) { + this(authServerUrl, null); + } + + public KeycloakTestClient(String authServerUrl, Tls tls) { this.authServerUrl = authServerUrl; + this.tls = tls; } /** @@ -165,6 +171,55 @@ public String getAccessToken(String userName, String userSecret, String clientId return getAccessTokenInternal(userName, userSecret, clientId, clientSecret, scopes, getAuthServerUrl()); } + /** + * Get a refresh token from the default tenant realm using a password grant with a provided user name. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * User secret will be the same as the user name. + * Client id will be set to `quarkus-app` unless it has been configured with the `quarkus.oidc.client-id` property. + * Client secret will be to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` property. + */ + public String getRefreshToken(String userName) { + return getRefreshToken(userName, getClientId()); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name and client id. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * User secret will be the same as the user name. + * Client secret will be to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` property. + */ + public String getRefreshToken(String userName, String clientId) { + return getRefreshToken(userName, userName, clientId); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret and + * client id. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + * Client secret will be set to `secret` unless it has been configured with the `quarkus.oidc.credentials.secret` propertys. + */ + public String getRefreshToken(String userName, String userSecret, String clientId) { + return getRefreshToken(userName, userSecret, clientId, getClientSecret()); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret, client + * id and secret. + * Realm name is set to `quarkus` unless it has been configured with the `quarkus.keycloak.devservices.realm-name` property. + */ + public String getRefreshToken(String userName, String userSecret, String clientId, String clientSecret) { + return getRefreshToken(userName, userSecret, clientId, clientSecret, null); + } + + /** + * Get a refresh token from the default tenant realm using a password grant with the provided user name, user secret, client + * id and secret, and scopes. + */ + public String getRefreshToken(String userName, String userSecret, String clientId, String clientSecret, + List scopes) { + return getRefreshTokenInternal(userName, userSecret, clientId, clientSecret, scopes, getAuthServerUrl()); + } + /** * Get a realm access token using a password grant with a provided user name. * User secret will be the same as the user name. @@ -213,7 +268,17 @@ public String getRealmAccessToken(String realm, String userName, String userSecr private String getAccessTokenInternal(String userName, String userSecret, String clientId, String clientSecret, List scopes, String authServerUrl) { - RequestSpecification requestSpec = RestAssured.given().param("grant_type", "password") + return getAccessTokenResponse(userName, userSecret, clientId, clientSecret, scopes, authServerUrl).getToken(); + } + + private String getRefreshTokenInternal(String userName, String userSecret, String clientId, String clientSecret, + List scopes, String authServerUrl) { + return getAccessTokenResponse(userName, userSecret, clientId, clientSecret, scopes, authServerUrl).getRefreshToken(); + } + + private AccessTokenResponse getAccessTokenResponse(String userName, String userSecret, String clientId, String clientSecret, + List scopes, String authServerUrl) { + RequestSpecification requestSpec = getSpec().param("grant_type", "password") .param("username", userName) .param("password", userSecret) .param("client_id", clientId); @@ -224,12 +289,12 @@ private String getAccessTokenInternal(String userName, String userSecret, String requestSpec = requestSpec.param("scope", urlEncode(String.join(" ", scopes))); } return requestSpec.when().post(authServerUrl + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); + .as(AccessTokenResponse.class); } private String getClientAccessTokenInternal(String clientId, String clientSecret, List scopes, String authServerUrl) { - RequestSpecification requestSpec = RestAssured.given().param("grant_type", "client_credentials") + RequestSpecification requestSpec = getSpec().param("grant_type", "client_credentials") .param("client_id", clientId); if (clientSecret != null && !clientSecret.isBlank()) { requestSpec = requestSpec.param("client_secret", clientSecret); @@ -298,8 +363,7 @@ public String getAuthServerUrl() { */ public void createRealm(RealmRepresentation realm) { try { - RestAssured - .given() + getSpec() .auth().oauth2(getAdminAccessToken()) .contentType("application/json") .body(JsonSerialization.writeValueAsBytes(realm)) @@ -315,8 +379,7 @@ public void createRealm(RealmRepresentation realm) { * Delete a realm */ public void deleteRealm(String realm) { - RestAssured - .given() + getSpec() .auth().oauth2(getAdminAccessToken()) .when() .delete(getAuthServerBaseUrl() + "/admin/realms/" + realm).then().statusCode(204); @@ -375,4 +438,23 @@ private static String urlEncode(String value) { throw new RuntimeException(ex); } } + + private RequestSpecification getSpec() { + RequestSpecification spec = RestAssured.given(); + if (tls != null) { + spec = spec.keyStore(tls.keystore(), tls.keystorePassword()) + .trustStore(tls.truststore(), tls.truststorePassword()); + } else { + spec = spec.relaxedHTTPSValidation(); + } + return spec; + } + + public record Tls(String keystore, String keystorePassword, + String truststore, String truststorePassword) { + public Tls() { + this("client-keystore.p12", "password", "client-truststore.p12", "password"); + } + }; + }