From 38f253f5dfcdffdfa1ebe0198119f1f9a84d6959 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 1 Apr 2021 18:24:42 +0100 Subject: [PATCH] Update Keycloak Test Module to support code flow tests --- docs/src/main/asciidoc/security-jwt.adoc | 15 +- .../security-openid-connect-client.adoc | 33 ++-- ...ity-openid-connect-web-authentication.adoc | 81 ++++++++-- .../asciidoc/security-openid-connect.adoc | 116 +++++++++++++- .../oidc-client-filter/deployment/pom.xml | 100 +------------ .../filter/KeycloakRealmResourceManager.java | 141 ------------------ .../filter/OidcClientFilterDevModeTest.java | 3 +- .../application-oidc-client-filter.properties | 4 +- extensions/oidc/deployment/pom.xml | 107 +------------ .../CodeFlowDevModeDefaultTenantTestCase.java | 11 +- .../oidc/test/CodeFlowDevModeTestCase.java | 21 +-- .../oidc/test/CustomTenantConfigResolver.java | 7 +- ...ication-dev-mode-default-tenant.properties | 5 +- .../resources/application-dev-mode.properties | 5 +- integration-tests/oidc-wiremock/pom.xml | 14 -- integration-tests/oidc/pom.xml | 16 -- .../src/main/resources/application.properties | 2 +- .../BearerTokenAuthorizationTest.java | 1 - .../KeycloakTestResourceLifecycleManager.java | 106 +++++++------ .../oidc/server/OidcWiremockTestResource.java | 4 +- 20 files changed, 316 insertions(+), 476 deletions(-) delete mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/KeycloakRealmResourceManager.java diff --git a/docs/src/main/asciidoc/security-jwt.adoc b/docs/src/main/asciidoc/security-jwt.adoc index b1d3768dd4b3b..4053db87c8155 100644 --- a/docs/src/main/asciidoc/security-jwt.adoc +++ b/docs/src/main/asciidoc/security-jwt.adoc @@ -823,7 +823,16 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus smallrye.jwt.sign.key.location=privateKey.jwk ---- -It will ensure that the `smallrye-jwt` remote key resolution code also works as expected. +=== Keycloak + +If you work with Keycloak and configure `mp.jwt.verify.publickey.location` to point to HTTPS or HTTP based JsonWebKey (JWK) set then you can use the same approach as described in the link:security-openid-connect#integration-testing-keycloak[OpenId Connect Bearer Token Integration testing] `Keycloak` section but only change the `application.properties` to use MP JWT configuration properties instead: + +[source, properties] +---- +# keycloak.url is set by OidcWiremockTestResource +mp.jwt.verify.publickey.location=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs +mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus +---- === Local Public Key @@ -839,6 +848,10 @@ mp.jwt.verify.issuer=${keycloak.url}/realms/quarkus smallrye.jwt.sign.key.location=privateKey.pem ---- +=== TestSecurity annotation + +Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation. + [[generate-jwt-tokens]] == Generate JWT tokens with SmallRye JWT diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index 1aeb33bfc8d59..9be17691e36a0 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -330,7 +330,7 @@ Using `private_key_jwt` or `private_key_jwt` authentication methods ensures that [[integration-testing-oidc-client]] === Testing -Add the following dependencies to your test project: +Start by adding the following dependencies to your test project: [source,xml] ---- @@ -340,18 +340,22 @@ Add the following dependencies to your test project: test - com.github.tomakehurst - wiremock-jre8 - test - - - io.rest-assured - rest-assured + org.awaitility + awaitility test +---- + +[[integration-testing-wiremock]] +==== Wiremock + +Add the following dependencies to your test project: + +[source,xml] +---- - org.awaitility - awaitility + com.github.tomakehurst + wiremock-jre8 test ---- @@ -400,8 +404,7 @@ public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycl Map conf = new HashMap<>(); - conf.put("quarkus.oidc-client.auth-server-url", server.baseUrl()); - conf.put("keycloak-url", server.baseUrl()); + conf.put("keycloak.url", server.baseUrl()); return conf; } @@ -425,7 +428,7 @@ Set `application.properties`, for example: quarkus.oidc-client.auth-server-url=${keycloak.url} quarkus.oidc-client.discovery-enabled=false quarkus.oidc-client.token-path=/tokens -quarkus.oidc-client.client-id=quarkus-app +quarkus.oidc-client.client-id=quarkus-service-app quarkus.oidc-client.credentials.secret=secret quarkus.oidc-client.grant.type=password quarkus.oidc-client.grant-options.password.username=alice @@ -434,6 +437,10 @@ quarkus.oidc-client.grant-options.password.password=alice and finally write the test code. Given the Wiremock-based resource above, the first test invocation should return `access_token_1` access token which will expire in 4 seconds. Use `awaitility` to wait for about 5 seconds, and now the next test invocation should return `access_token_2` access token which confirms the expired `access_token_1` access token has been refreshed. +=== Keycloak + +If you work with Keycloak then you can use the same approach as described in the link:security-openid-connect#integration-testing-keycloak[OpenId Connect Bearer Token Integration testing] `Keycloak` section. + == Token endpoint configuration By default the token endpoint address is discovered by adding a `/.well-known/openid-configuration` path to the configured `quarkus.oidc-client.auth-server-url`. diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index 7ada5b816e0fc..6120b6eb52497 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -576,15 +576,10 @@ Please see link:security-openid-connect-client#token-propagation[Token Propagati [[integration-testing]] == Testing -Add the following dependencies to your test project: +Start by adding the following dependencies to your test project: [source,xml] ---- - - io.quarkus - quarkus-test-oidc-server - test - net.sourceforge.htmlunit htmlunit @@ -601,10 +596,19 @@ Add the following dependencies to your test project: quarkus-junit5 test +---- + +[[integration-testing-wiremock]] +=== Wiremock + +Add the following dependency: + +[source,xml] +---- - io.rest-assured - rest-assured - test + io.quarkus + quarkus-test-oidc-server + test ---- @@ -666,9 +670,66 @@ public class CodeFlowAuthorizationTest { ---- `OidcWiremockTestResource` recognizes `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `quarkus.test.oidc.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `quarkus.test.oidc.token.admin-roles` system property. + Additionally, `OidcWiremockTestResource` set token issuer and audience to `https://service.example.com` which can be customized with `quarkus.test.oidc.token.issuer` and `quarkus.test.oidc.token.audience` system properties. -`OidcWiremockTestResource` will be enhanced going forward to support more complex authorization code flow test scenarios. +`OidcWiremockTestResource` can be used to emulate all OpenId Connect providers. + +=== Keycloak + +If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency: + +[source,xml] +---- + + io.quarkus + quarkus-test-keycloak-server + test + +---- + +and configure `maven.surefire.plugin` as follows: + +[source,xml] +---- + + maven-surefire-plugin + + + + ${keycloak.docker.image} + + + + +---- + +(and similarly `maven.failsafe.plugin` when testing in native image). + +And now set the configuration and write the test code the same way as it is described in the <> section above. +The only difference is the name of `QuarkusTestResource`: + +[source, java] +---- +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; + +@QuarkusTest +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class CodeFlowAuthorizationTest { +} +---- + +`KeycloakTestResourceLifecycleManager` registers `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `keycloak.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `keycloak.token.admin-roles` system property. + +By default, `KeycloakTestResourceLifecycleManager` uses HTTPS to initialize a Keycloak instance which can be disabled with `keycloak.use.https=false`. +Default realm name is `quarkus` and client id - `quarkus-web-app` - set `keycloak.realm` and `keycloak.web-app.client` system properties to customize the values if needed. + +=== TestSecurity annotation + +Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation. == Configuration Reference diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index a47bb89e7fdea..e5b8f531e6d2d 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -434,15 +434,19 @@ Please see link:security-openid-connect-client#token-propagation[Token Propagati [[integration-testing]] == Testing -=== Wiremock - -Add the following dependencies to your test project: +Start by adding the following dependencies to your test project: [source,xml] ---- - io.quarkus - quarkus-test-oidc-server + net.sourceforge.htmlunit + htmlunit + + + org.eclipse.jetty + * + + test @@ -450,10 +454,19 @@ Add the following dependencies to your test project: quarkus-junit5 test +---- + +[[integration-testing-wiremock]] +=== Wiremock + +Add the following dependencies to your test project: + +[source,xml] +---- - io.rest-assured - rest-assured - test + io.quarkus + quarkus-test-oidc-server + test ---- @@ -516,6 +529,89 @@ public class BearerTokenAuthorizationTest { Testing your `quarkus-oidc` `service` application with `OidcWiremockTestResource` provides the best coverage as even the communication channel is tested against the Wiremock HTTP stubs. `OidcWiremockTestResource` will be enhanced going forward to support more complex Bearer token test scenarios. +[[integration-testing-keycloak]] +=== Keycloak + +If you work with Keycloak then you can test against a live Keycloak instance by adding the following dependency: + +[source,xml] +---- + + io.quarkus + quarkus-test-keycloak-server + test + +---- + +and configure `maven.surefire.plugin` as follows: + +[source,xml] +---- + + maven-surefire-plugin + + + + ${keycloak.docker.image} + + + + +---- + +(and similarly `maven.failsafe.plugin` when testing in native image). + +Prepare the REST test endpoint, set `application.properties`, for example: + +[source, properties] +---- +# keycloak.url is set by KeycloakTestResourceLifecycleManager +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.client-id=quarkus-service-app +quarkus.oidc.credentials=secret +quarkus.oidc.application-type=service +---- + +and finally write the test code, for example: + +[source, java] +---- +import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken; +import static org.hamcrest.Matchers.equalTo; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; +import io.restassured.RestAssured; + +@QuarkusTest +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class BearerTokenAuthorizationTest { + + @Test + public void testBearerToken() { + RestAssured.given().auth().oauth2(getAccessToken("alice")))) + .when().get("/api/users/preferredUserName") + .then() + .statusCode(200) + // the test endpoint returns the name extracted from the injected SecurityIdentity Principal + .body("userName", equalTo("alice")); + } + +} +---- + +`KeycloakTestResourceLifecycleManager` registers `alice` and `admin` users. The user `alice` has the `user` role only by default - it can be customized with a `keycloak.token.user-roles` system property. The user `admin` has the `user` and `admin` roles by default - it can be customized with a `keycloak.token.admin-roles` system property. + +By default, `KeycloakTestResourceLifecycleManager` uses HTTPS to initialize a Keycloak instance which can be disabled with `keycloak.use.https=false`. +Default realm name is `quarkus` and client id - `quarkus-service-app` - set `keycloak.realm` and `keycloak.service.client` system properties to customize the values if needed. + === Local Public Key You can also use a local inlined public key for testing your `quarkus-oidc` `service` applications: @@ -532,6 +628,10 @@ copy `privateKey.pem` from the `integration-tests/oidc-tenancy` in the `main` Qu This approach provides a more limited coverage compared to the Wiremock approach - for example, the remote communication code is not covered. +=== TestSecurity annotation + +Please see link:security-testing#testing-security[TestingSecurity Annotation] section how to do simple tests with the `TestSecurity` annotation. + == References * https://www.keycloak.org/documentation.html[Keycloak Documentation] diff --git a/extensions/oidc-client-filter/deployment/pom.xml b/extensions/oidc-client-filter/deployment/pom.xml index c6049c6a6fc4f..ca5e2ec5dfca7 100644 --- a/extensions/oidc-client-filter/deployment/pom.xml +++ b/extensions/oidc-client-filter/deployment/pom.xml @@ -13,10 +13,6 @@ quarkus-oidc-client-filter-deployment Quarkus - OpenID Connect Client Filter - Deployment - - http://localhost:8180/auth - - io.quarkus @@ -47,13 +43,8 @@ test - org.keycloak - keycloak-adapter-core - test - - - org.keycloak - keycloak-core + io.quarkus + quarkus-test-keycloak-server test @@ -111,96 +102,13 @@ false - ${keycloak.url} + ${keycloak.docker.image} + false - - - docker-keycloak - - - start-containers - - - - http://localhost:8180/auth - - - - - io.fabric8 - docker-maven-plugin - - - - ${keycloak.docker.image} - quarkus-test-keycloak - - - 8180:8080 - - - admin - admin - - - Keycloak: - default - cyan - - - - - http://localhost:8180 - - - - - - - true - - - - docker-start - compile - - stop - start - - - - docker-stop - post-integration-test - - stop - - - - - - org.codehaus.mojo - exec-maven-plugin - - - docker-prune - generate-resources - - exec - - - ${basedir}/../../../.github/docker-prune.sh - - - - - - - - diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/KeycloakRealmResourceManager.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/KeycloakRealmResourceManager.java deleted file mode 100644 index fa3c7d92adb05..0000000000000 --- a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/KeycloakRealmResourceManager.java +++ /dev/null @@ -1,141 +0,0 @@ -package io.quarkus.oidc.client.filter; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -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 io.quarkus.test.common.QuarkusTestResourceLifecycleManager; -import io.restassured.RestAssured; - -public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager { - - private static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth"); - private static final String KEYCLOAK_REALM = "quarkus"; - - @Override - public Map start() { - - RealmRepresentation realm = createRealm(KEYCLOAK_REALM); - realm.setRevokeRefreshToken(true); - realm.setRefreshTokenMaxReuse(0); - realm.setAccessTokenLifespan(3); - - realm.getClients().add(createClient("quarkus-app")); - realm.getUsers().add(createUser("alice", "user")); - - try { - RestAssured - .given() - .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 Collections.emptyMap(); - } - - private static String getAdminAccessToken() { - return RestAssured - .given() - .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 RealmRepresentation createRealm(String name) { - RealmRepresentation realm = new RealmRepresentation(); - - realm.setRealm(name); - realm.setEnabled(true); - realm.setUsers(new ArrayList<>()); - realm.setClients(new ArrayList<>()); - realm.setAccessTokenLifespan(3); - - RolesRepresentation roles = new RolesRepresentation(); - List realmRoles = new ArrayList<>(); - - roles.setRealm(realmRoles); - realm.setRoles(roles); - - realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); - realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); - - return realm; - } - - private static ClientRepresentation createClient(String clientId) { - ClientRepresentation client = new ClientRepresentation(); - - client.setClientId(clientId); - client.setPublicClient(false); - client.setSecret("secret"); - client.setDirectAccessGrantsEnabled(true); - client.setServiceAccountsEnabled(true); - client.setEnabled(true); - - return client; - } - - private static UserRepresentation createUser(String username, String... realmRoles) { - UserRepresentation user = new UserRepresentation(); - - user.setUsername(username); - user.setEnabled(true); - user.setCredentials(new ArrayList<>()); - user.setRealmRoles(Arrays.asList(realmRoles)); - user.setEmail(username + "@gmail.com"); - - CredentialRepresentation credential = new CredentialRepresentation(); - - credential.setType(CredentialRepresentation.PASSWORD); - credential.setValue(username); - credential.setTemporary(false); - - user.getCredentials().add(credential); - - return user; - } - - @Override - public void stop() { - - RestAssured - .given() - .auth().oauth2(getAdminAccessToken()) - .when() - .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); - } - - public static String getAccessToken(String userName) { - return RestAssured - .given() - .param("grant_type", "password") - .param("username", userName) - .param("password", userName) - .param("client_id", "quarkus-app") - .param("client_secret", "secret") - .when() - .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") - .as(AccessTokenResponse.class).getToken(); - } -} diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java index 061201df5c834..9e3f2fcff95df 100644 --- a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java @@ -22,9 +22,10 @@ import io.quarkus.test.QuarkusDevModeTest; import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; import io.restassured.RestAssured; -@QuarkusTestResource(KeycloakRealmResourceManager.class) +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) public class OidcClientFilterDevModeTest { private static Class[] testClasses = { diff --git a/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties b/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties index 0bce3b0c4c593..830a0ef048dfd 100644 --- a/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties +++ b/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties @@ -1,5 +1,5 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ -quarkus.oidc.client-id=quarkus-app +quarkus.oidc.client-id=quarkus-service-app quarkus.oidc.credentials.secret=secret #quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} @@ -17,4 +17,4 @@ io.quarkus.oidc.client.filter.ProtectedResourceService/mp-rest/url=http://localh quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE quarkus.log.file.enable=true -quarkus.log.file.format=%C - %s%n \ No newline at end of file +quarkus.log.file.format=%C - %s%n diff --git a/extensions/oidc/deployment/pom.xml b/extensions/oidc/deployment/pom.xml index 951d1b8aa13d0..7dbb6f76456ab 100644 --- a/extensions/oidc/deployment/pom.xml +++ b/extensions/oidc/deployment/pom.xml @@ -12,10 +12,6 @@ quarkus-oidc-deployment Quarkus - OpenID Connect Adapter - Deployment - - http://localhost:8180/auth - - io.quarkus @@ -52,24 +48,20 @@ io.quarkus - quarkus-junit5-internal - test - - - io.rest-assured - rest-assured + quarkus-test-keycloak-server test - org.keycloak - keycloak-adapter-core + io.quarkus + quarkus-junit5-internal test - org.keycloak - keycloak-core + io.rest-assured + rest-assured test + net.sourceforge.htmlunit htmlunit @@ -125,96 +117,13 @@ false - ${keycloak.url} + ${keycloak.docker.image} + false - - - docker-keycloak - - - start-containers - - - - http://localhost:8180/auth - - - - - io.fabric8 - docker-maven-plugin - - - - ${keycloak.docker.image} - quarkus-test-keycloak - - - 8180:8080 - - - admin - admin - - - Keycloak: - default - cyan - - - - - http://localhost:8180 - - - - - - - true - - - - docker-start - compile - - stop - start - - - - docker-stop - post-integration-test - - stop - - - - - - org.codehaus.mojo - exec-maven-plugin - - - docker-prune - generate-resources - - exec - - - ${docker-prune.location} - - - - - - - - diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java index 31ce8eb5a88d9..fe82852eafb16 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeDefaultTenantTestCase.java @@ -18,8 +18,9 @@ import io.quarkus.test.QuarkusDevModeTest; import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; -@QuarkusTestResource(KeycloakDevModeRealmResourceManager.class) +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) public class CodeFlowDevModeDefaultTenantTestCase { private static Class[] testClasses = { @@ -51,16 +52,16 @@ public void testAccessAndRefreshTokenInjectionDevMode() throws IOException, Inte HtmlPage page = webClient.getPage("http://localhost:8080/protected"); - assertEquals("Sign in to devmode", page.getTitleText()); + assertEquals("Sign in to quarkus", page.getTitleText()); HtmlForm loginForm = page.getForms().get(0); - loginForm.getInputByName("username").setValueAttribute("alice-dev-mode"); - loginForm.getInputByName("password").setValueAttribute("alice-dev-mode"); + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertEquals("alice-dev-mode", page.getBody().asText()); + assertEquals("alice", page.getBody().asText()); webClient.getCookieManager().clearCookies(); } diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java index 84cafdd50671c..f81ee0540ba3f 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CodeFlowDevModeTestCase.java @@ -19,8 +19,9 @@ import io.quarkus.test.QuarkusDevModeTest; import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; -@QuarkusTestResource(KeycloakDevModeRealmResourceManager.class) +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) public class CodeFlowDevModeTestCase { private static Class[] testClasses = { @@ -68,20 +69,20 @@ public void testAccessAndRefreshTokenInjectionDevMode() throws IOException, Inte } // Now set the correct client-id - test.modifyResourceFile("application.properties", s -> s.replace("client-dev", "client-dev-mode")); + test.modifyResourceFile("application.properties", s -> s.replace("client-dev", "quarkus-web-app")); page = webClient.getPage("http://localhost:8080/protected"); - assertEquals("Sign in to devmode", page.getTitleText()); + assertEquals("Sign in to quarkus", page.getTitleText()); HtmlForm loginForm = page.getForms().get(0); - loginForm.getInputByName("username").setValueAttribute("alice-dev-mode"); - loginForm.getInputByName("password").setValueAttribute("alice-dev-mode"); + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertEquals("alice-dev-mode", page.getBody().asText()); + assertEquals("alice", page.getBody().asText()); assertEquals("custom", page.getWebClient().getCookieManager().getCookie("q_session").getValue().split("\\|")[3]); @@ -93,16 +94,16 @@ private void useTenantConfigResolver() throws IOException, InterruptedException try (final WebClient webClient = createWebClient()) { HtmlPage page = webClient.getPage("http://localhost:8080/protected/tenant/tenant-config-resolver"); - assertEquals("Sign in to devmode", page.getTitleText()); + assertEquals("Sign in to quarkus", page.getTitleText()); HtmlForm loginForm = page.getForms().get(0); - loginForm.getInputByName("username").setValueAttribute("alice-dev-mode"); - loginForm.getInputByName("password").setValueAttribute("alice-dev-mode"); + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertEquals("tenant-config-resolver:alice-dev-mode", page.getBody().asText()); + assertEquals("tenant-config-resolver:alice", page.getBody().asText()); webClient.getCookieManager().clearCookies(); } } diff --git a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTenantConfigResolver.java b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTenantConfigResolver.java index 35a6d8d85c639..3a3dac4a470ff 100644 --- a/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTenantConfigResolver.java +++ b/extensions/oidc/deployment/src/test/java/io/quarkus/oidc/test/CustomTenantConfigResolver.java @@ -14,8 +14,9 @@ public OidcTenantConfig resolve(RoutingContext context) { if (context.request().path().endsWith("/tenant-config-resolver")) { OidcTenantConfig config = new OidcTenantConfig(); config.setTenantId("tenant-config-resolver"); - config.setAuthServerUrl(getIssuerUrl() + "/realms/devmode"); - config.setClientId("client-dev-mode"); + config.setAuthServerUrl(getIssuerUrl() + "/realms/quarkus"); + config.setClientId("quarkus-web-app"); + config.getCredentials().setSecret("secret"); config.applicationType = ApplicationType.WEB_APP; return config; } @@ -23,6 +24,6 @@ public OidcTenantConfig resolve(RoutingContext context) { } private String getIssuerUrl() { - return System.getProperty("keycloak.url", "http://localhost:8180/auth"); + return System.getProperty("keycloak.url"); } } diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties index 66215af62a581..244e251ce510a 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode-default-tenant.properties @@ -1,5 +1,6 @@ -#quarkus.oidc.auth-server-url=${keycloak.url}/realms/devmode -quarkus.oidc.client-id=client-dev-mode +#quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus +quarkus.oidc.client-id=quarkus-web-app +quarkus.oidc.credentials.secret=secret quarkus.oidc.application-type=web-app quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL diff --git a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties index 21891144d7155..347397a43066b 100644 --- a/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties +++ b/extensions/oidc/deployment/src/test/resources/application-dev-mode.properties @@ -1,7 +1,8 @@ -quarkus.oidc.auth-server-url=${keycloak.url}/realms/devmode +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus quarkus.oidc.tenant-enabled=false -# This is a wrong client-id, will be updated to 'client-dev-mode' in the dev mode test +# This is a wrong client-id, will be updated to 'quarkus-web-app' in the dev mode test quarkus.oidc.client-id=client-dev +quarkus.oidc.credentials.secret=secret quarkus.oidc.application-type=web-app quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL diff --git a/integration-tests/oidc-wiremock/pom.xml b/integration-tests/oidc-wiremock/pom.xml index e2d6aceb89eb1..3eb96fedbef00 100644 --- a/integration-tests/oidc-wiremock/pom.xml +++ b/integration-tests/oidc-wiremock/pom.xml @@ -13,10 +13,6 @@ Quarkus - Integration Tests - OpenID Connect Adapter WireMock Module that contains OpenID Connect related tests using WireMock - - http://localhost:8180/auth - - io.quarkus @@ -93,19 +89,9 @@ maven-surefire-plugin - - - ${keycloak.url} - - maven-failsafe-plugin - - - ${keycloak.url} - - io.quarkus diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index 66065311383b2..29c875afc0ae0 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -13,11 +13,6 @@ Quarkus - Integration Tests - OpenID Connect Adapter Module that contains OpenID Connect related tests - - http://localhost:8180/auth - https://localhost:8543/auth - - io.quarkus @@ -27,15 +22,6 @@ io.quarkus quarkus-resteasy-jackson - - org.keycloak - keycloak-adapter-core - - - org.keycloak - keycloak-core - - io.quarkus @@ -140,7 +126,6 @@ false ${keycloak.docker.image} - ${keycloak.ssl.url} @@ -150,7 +135,6 @@ false ${keycloak.docker.image} - ${keycloak.ssl.url} diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index bbeaa8b34ef8b..7229843c206cb 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -1,5 +1,5 @@ # Configuration file -quarkus.oidc.auth-server-url=${keycloak.ssl.url}/realms/quarkus/ +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.client-id=quarkus-service-app quarkus.oidc.credentials.secret=secret quarkus.oidc.token.principal-claim=email diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index af69812984c1a..a0a186fe96e58 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -148,7 +148,6 @@ public void testVerificationFailedInvalidToken() { } //see https://github.com/quarkusio/quarkus/issues/5809 - @Test @RepeatedTest(20) public void testOidcAndVertxHandler() { RestAssured.given().auth().oauth2(getAccessToken("alice")) diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java index 04c9de75e9b5c..f19c9a87b1933 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java @@ -18,25 +18,40 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.restassured.RestAssured; public class KeycloakTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager { - - private GenericContainer keycloak; + private GenericContainer keycloak; private static String KEYCLOAK_SERVER_URL; - private static final String KEYCLOAK_REALM = "quarkus-service-realm"; - private static final String KEYCLOAK_DOCKER_IMAGE = System.getProperty("keycloak.docker.image", - "quay.io/keycloak/keycloak:12.0.4"); + private static final String KEYCLOAK_REALM = System.getProperty("keycloak.realm", "quarkus"); + private static final String KEYCLOAK_SERVICE_CLIENT = System.getProperty("keycloak.service.client", "quarkus-service-app"); + private static final String KEYCLOAK_WEB_APP_CLIENT = System.getProperty("keycloak.web-app.client", "quarkus-web-app"); + private static final Boolean KEYCLOAK_USE_HTTPS = Boolean.valueOf(System.getProperty("keycloak.use.https", "true")); + private static final String KEYCLOAK_VERSION = System.getProperty("keycloak.version"); + private static final String KEYCLOAK_DOCKER_IMAGE = System.getProperty("keycloak.docker.image"); + + private static final String TOKEN_USER_ROLES = System.getProperty("keycloak.token.user-roles", "user"); + private static final String TOKEN_ADMIN_ROLES = System.getProperty("keycloak.token.admin-roles", "user,admin"); static { RestAssured.useRelaxedHTTPSValidation(); } + @SuppressWarnings("resource") @Override public Map start() { - keycloak = new GenericContainer(KEYCLOAK_DOCKER_IMAGE) + String keycloakDockerImage; + if (KEYCLOAK_DOCKER_IMAGE != null) { + keycloakDockerImage = KEYCLOAK_DOCKER_IMAGE; + } else if (KEYCLOAK_VERSION != null) { + keycloakDockerImage = "quay.io/keycloak/keycloak:" + KEYCLOAK_VERSION; + } else { + throw new ConfigurationException("Please set either 'keycloak.docker.image' or 'keycloak.version' system property"); + } + keycloak = new GenericContainer<>(keycloakDockerImage) .withExposedPorts(8080, 8443) .withEnv("DB_VENDOR", "H2") .withEnv("KEYCLOAK_USER", "admin") @@ -45,16 +60,17 @@ public Map start() { keycloak.start(); - KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443) + "/auth"; + if (KEYCLOAK_USE_HTTPS) { + KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443) + "/auth"; + } else { + KEYCLOAK_SERVER_URL = "http://localhost:" + keycloak.getMappedPort(8080) + "/auth"; + } RealmRepresentation realm = createRealm(KEYCLOAK_REALM); postRealm(realm); - RealmRepresentation webAppRealm = createWebAppRealm("quarkus-webapp-realm"); - postRealm(webAppRealm); - Map conf = new HashMap<>(); - conf.put("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); + conf.put("keycloak.url", KEYCLOAK_SERVER_URL); return conf; } @@ -82,6 +98,7 @@ private static RealmRepresentation createRealm(String name) { realm.setUsers(new ArrayList<>()); realm.setClients(new ArrayList<>()); realm.setAccessTokenLifespan(3); + realm.setSsoSessionMaxLifespan(3); RolesRepresentation roles = new RolesRepresentation(); List realmRoles = new ArrayList<>(); @@ -93,38 +110,12 @@ private static RealmRepresentation createRealm(String name) { realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); realm.getRoles().getRealm().add(new RoleRepresentation("confidential", null, false)); - realm.getClients().add(createClient("quarkus-service-app")); - realm.getUsers().add(createUser("alice", "user")); - realm.getUsers().add(createUser("admin", "user", "admin")); - realm.getUsers().add(createUser("jdoe", "user", "confidential")); - - return realm; - } - - private static RealmRepresentation createWebAppRealm(String name) { - RealmRepresentation realm = new RealmRepresentation(); - - realm.setRealm(name); - realm.setEnabled(true); - realm.setUsers(new ArrayList<>()); - realm.setClients(new ArrayList<>()); - realm.setSsoSessionMaxLifespan(3); // sec - realm.setAccessTokenLifespan(4); // 3 seconds - - RolesRepresentation roles = new RolesRepresentation(); - List realmRoles = new ArrayList<>(); + realm.getClients().add(createServiceClient(KEYCLOAK_SERVICE_CLIENT)); + realm.getClients().add(createWebAppClient(KEYCLOAK_WEB_APP_CLIENT)); - roles.setRealm(realmRoles); - realm.setRoles(roles); - - realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); - realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); - realm.getRoles().getRealm().add(new RoleRepresentation("confidential", null, false)); - - realm.getClients().add(createClient("quarkus-app")); - realm.getUsers().add(createUser("alice", "user")); - realm.getUsers().add(createUser("admin", "user", "admin")); - realm.getUsers().add(createUser("jdoe", "user", "confidential")); + realm.getUsers().add(createUser("alice", getUserRoles())); + realm.getUsers().add(createUser("admin", getAdminRoles())); + realm.getUsers().add(createUser("jdoe", Arrays.asList("user", "confidential"))); return realm; } @@ -141,25 +132,38 @@ private static String getAdminAccessToken() { .as(AccessTokenResponse.class).getToken(); } - private static ClientRepresentation createClient(String clientId) { + private static ClientRepresentation createServiceClient(String clientId) { ClientRepresentation client = new ClientRepresentation(); client.setClientId(clientId); client.setPublicClient(false); client.setSecret("secret"); client.setDirectAccessGrantsEnabled(true); + client.setServiceAccountsEnabled(true); client.setEnabled(true); return client; } - private static UserRepresentation createUser(String username, String... realmRoles) { + private static ClientRepresentation createWebAppClient(String clientId) { + ClientRepresentation client = new ClientRepresentation(); + + client.setClientId(clientId); + client.setPublicClient(false); + client.setSecret("secret"); + client.setRedirectUris(Arrays.asList("*")); + client.setEnabled(true); + + return client; + } + + private static UserRepresentation createUser(String username, List realmRoles) { UserRepresentation user = new UserRepresentation(); user.setUsername(username); user.setEnabled(true); user.setCredentials(new ArrayList<>()); - user.setRealmRoles(Arrays.asList(realmRoles)); + user.setRealmRoles(realmRoles); user.setEmail(username + "@gmail.com"); CredentialRepresentation credential = new CredentialRepresentation(); @@ -179,7 +183,7 @@ public static String getAccessToken(String userName) { .param("grant_type", "password") .param("username", userName) .param("password", userName) - .param("client_id", "quarkus-service-app") + .param("client_id", KEYCLOAK_SERVICE_CLIENT) .param("client_secret", "secret") .when() .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") @@ -192,7 +196,7 @@ public static String getRefreshToken(String userName) { .param("grant_type", "password") .param("username", userName) .param("password", userName) - .param("client_id", "quarkus-service-app") + .param("client_id", KEYCLOAK_SERVICE_CLIENT) .param("client_secret", "secret") .when() .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") @@ -201,7 +205,6 @@ public static String getRefreshToken(String userName) { @Override public void stop() { - RestAssured .given() .auth().oauth2(getAdminAccessToken()) @@ -211,4 +214,11 @@ public void stop() { keycloak.stop(); } + private static List getAdminRoles() { + return Arrays.asList(TOKEN_ADMIN_ROLES.split(",")); + } + + private static List getUserRoles() { + return Arrays.asList(TOKEN_USER_ROLES.split(",")); + } } diff --git a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java index 6fc652539954f..dc6f082745f1e 100644 --- a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java +++ b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java @@ -123,9 +123,7 @@ public Map start() { LOG.infof("Keycloak started in mock mode: %s", server.baseUrl()); Map conf = new HashMap<>(); - conf.put("quarkus.oidc.auth-server-url", server.baseUrl() + "/auth/realms/quarkus"); - conf.put("quarkus.oidc.code-flow.auth-server-url", server.baseUrl() + "/auth/realms/quarkus"); - conf.put("keycloak-url", server.baseUrl()); + conf.put("keycloak.url", server.baseUrl() + "/auth"); return conf; }