diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 87a682ec29e3a..be2308d31b6b9 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -187,7 +187,7 @@ 5.8.0 4.13.0 2.0.3.Final - 23.0.7 + 24.0.4 1.15.1 3.43.0 2.27.1 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index aaa551740eba3..8d4d5d295370e 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -107,7 +107,7 @@ - 23.0.7 + 24.0.4 19.0.3 quay.io/keycloak/keycloak:${keycloak.version} quay.io/keycloak/keycloak:${keycloak.wildfly.version}-legacy diff --git a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc index fd3d37c2468ab..32ebe11900fc0 100644 --- a/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-dev-services.adoc @@ -258,7 +258,7 @@ For more information, see xref:security-oidc-bearer-token-authentication.adoc#in [[keycloak-initialization]] === Keycloak initialization -The `quay.io/keycloak/keycloak:23.0.7` image which contains a Keycloak distribution powered by Quarkus is used to start a container by default. +The `quay.io/keycloak/keycloak:24.0.4` image which contains a Keycloak distribution powered by Quarkus is used to start a container by default. `quarkus.keycloak.devservices.image-name` can be used to change the Keycloak image name. For example, set it to `quay.io/keycloak/keycloak:19.0.3-legacy` to use a Keycloak distribution powered by WildFly. Be aware that a Quarkus-based Keycloak distribution is only available starting from Keycloak `20.0.0`. diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java index c18dd966b76fc..5a9ace88d43d5 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java @@ -34,7 +34,7 @@ public class DevServicesConfig { * ends with `-legacy`. * Override with `quarkus.keycloak.devservices.keycloak-x-image`. */ - @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:23.0.7") + @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:24.0.4") public String imageName; /** 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 c2ef04ff3d074..f7f2e1cdff083 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 @@ -109,7 +109,8 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_QUARKUS_HOSTNAME = "KC_HOSTNAME"; private static final String KEYCLOAK_QUARKUS_ADMIN_PROP = "KEYCLOAK_ADMIN"; private static final String KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP = "KEYCLOAK_ADMIN_PASSWORD"; - private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false --hostname-strict-https=false"; + private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false --hostname-strict-https=false " + + "--spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json"; private static final String JAVA_OPTS = "JAVA_OPTS"; private static final String OIDC_USERS = "oidc.users"; @@ -509,6 +510,7 @@ protected void configure() { addEnv(KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD); withCommand(startCommand.orElse(KEYCLOAK_QUARKUS_START_CMD) + (useSharedNetwork ? " --hostname-port=" + fixedExposedPort.getAsInt() : "")); + addUpConfigResource(); } else { addEnv(KEYCLOAK_WILDFLY_USER_PROP, KEYCLOAK_ADMIN_USER); addEnv(KEYCLOAK_WILDFLY_PASSWORD_PROP, KEYCLOAK_ADMIN_PASSWORD); @@ -560,6 +562,13 @@ private void mapResource(String resourcePath, String mappedResource) { } } + private void addUpConfigResource() { + if (Thread.currentThread().getContextClassLoader().getResource("/dev-service/upconfig.json") != null) { + LOG.debug("Mapping the classpath /dev-service/upconfig.json resource to /opt/keycloak/upconfig.json"); + withClasspathResourceMapping("/dev-service/upconfig.json", "/opt/keycloak/upconfig.json", BindMode.READ_ONLY); + } + } + private Integer findRandomPort() { try (ServerSocket socket = new ServerSocket(0)) { return socket.getLocalPort(); diff --git a/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json b/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json new file mode 100644 index 0000000000000..8487089bc90fd --- /dev/null +++ b/extensions/oidc/deployment/src/main/resources/dev-service/upconfig.json @@ -0,0 +1,60 @@ +{ + "attributes": [ + { + "name": "username", + "displayName": "${username}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "min": 3, "max": 255 }, + "username-prohibited-characters": {}, + "up-username-not-idn-homograph": {} + } + }, + { + "name": "email", + "displayName": "${email}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "email" : {}, + "length": { "max": 255 } + } + }, + { + "name": "firstName", + "displayName": "${firstName}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "max": 255 }, + "person-name-prohibited-characters": {} + } + }, + { + "name": "lastName", + "displayName": "${lastName}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "max": 255 }, + "person-name-prohibited-characters": {} + } + } + ], + "groups": [ + { + "name": "user-metadata", + "displayHeader": "User metadata", + "displayDescription": "Attributes, which refer to user metadata" + } + ] +} \ No newline at end of file diff --git a/integration-tests/keycloak-authorization/src/main/resources/application.properties b/integration-tests/keycloak-authorization/src/main/resources/application.properties index 18e8a230fc3cf..9f980ee1c2310 100644 --- a/integration-tests/keycloak-authorization/src/main/resources/application.properties +++ b/integration-tests/keycloak-authorization/src/main/resources/application.properties @@ -92,3 +92,5 @@ admin-url=${keycloak.url} # Configure Keycloak Admin Client quarkus.keycloak.admin-client.server-url=${admin-url} + +quarkus.log.category."com.gargoylesoftware.htmlunit".level=ERROR diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties index 0d61acc332ab5..5aef28a6fe64c 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -1,4 +1,5 @@ quarkus.keycloak.devservices.create-realm=false +quarkus.keycloak.devservices.show-logs=true # Default tenant configurationf quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret diff --git a/integration-tests/oidc-tenancy/src/main/resources/application.properties b/integration-tests/oidc-tenancy/src/main/resources/application.properties index 88bf88c41c0e1..1b5a5f2efb9a1 100644 --- a/integration-tests/oidc-tenancy/src/main/resources/application.properties +++ b/integration-tests/oidc-tenancy/src/main/resources/application.properties @@ -135,7 +135,7 @@ quarkus.http.auth.permission.authenticated.policy=authenticated smallrye.jwt.sign.key.location=/privateKey.pem smallrye.jwt.new-token.lifespan=5 -quarkus.log.category."com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet".level=FATAL +quarkus.log.category."com.gargoylesoftware.htmlunit".level=ERROR quarkus.http.auth.proactive=false quarkus.native.additional-build-args=-H:IncludeResources=.*\\.pem diff --git a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index 38a85d30756fb..f538817bdd679 100644 --- a/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc-tenancy/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -11,13 +11,17 @@ import java.io.IOException; import java.net.URI; import java.time.Duration; +import java.util.ArrayList; import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import com.gargoylesoftware.htmlunit.CookieManager; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; import com.gargoylesoftware.htmlunit.WebClient; @@ -307,7 +311,9 @@ public void testReAuthenticateWhenSwitchingTenants() throws IOException { page = loginForm.getInputByName("login").click(); assertEquals("tenant-web-app2:alice", page.getBody().asNormalizedText()); assertNull(getSessionCookie(webClient, "tenant-web-app")); - assertNotNull(getSessionCookie(webClient, "tenant-web-app2")); + List sessionCookieChunks = getSessionCookies(webClient, "tenant-web-app2"); + assertNotNull(sessionCookieChunks); + assertEquals(2, sessionCookieChunks.size()); webClient.getCookieManager().clearCookies(); } } @@ -932,4 +938,17 @@ private Cookie getSessionAtCookie(WebClient webClient, String tenantId) { private Cookie getSessionRtCookie(WebClient webClient, String tenantId) { return webClient.getCookieManager().getCookie("q_session_rt" + (tenantId == null ? "_Default_test" : "_" + tenantId)); } + + private List getSessionCookies(WebClient webClient, String tenantId) { + String sessionCookieNameChunk = "q_session" + (tenantId == null ? "" : "_" + tenantId) + "_chunk_"; + CookieManager cookieManager = webClient.getCookieManager(); + SortedMap sessionCookies = new TreeMap<>(); + for (Cookie cookie : cookieManager.getCookies()) { + if (cookie.getName().startsWith(sessionCookieNameChunk)) { + sessionCookies.put(cookie.getName(), cookie); + } + } + + return sessionCookies.isEmpty() ? null : new ArrayList(sessionCookies.values()); + } } diff --git a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java index 29a9327e6d87b..bcd717025d989 100644 --- a/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java +++ b/integration-tests/oidc-token-propagation/src/test/java/io/quarkus/it/keycloak/OidcTokenPropagationTest.java @@ -53,7 +53,7 @@ public void testGetUserNameWithAccessTokenPropagation() { //.statusCode(200) //.body(equalTo("alice")); .statusCode(500) - .body(containsString("Feature not enabled")); + .body(containsString("Client not allowed to exchange")); } @Test diff --git a/integration-tests/oidc/src/main/resources/upconfig.json b/integration-tests/oidc/src/main/resources/upconfig.json new file mode 100644 index 0000000000000..8487089bc90fd --- /dev/null +++ b/integration-tests/oidc/src/main/resources/upconfig.json @@ -0,0 +1,60 @@ +{ + "attributes": [ + { + "name": "username", + "displayName": "${username}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "min": 3, "max": 255 }, + "username-prohibited-characters": {}, + "up-username-not-idn-homograph": {} + } + }, + { + "name": "email", + "displayName": "${email}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "email" : {}, + "length": { "max": 255 } + } + }, + { + "name": "firstName", + "displayName": "${firstName}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "max": 255 }, + "person-name-prohibited-characters": {} + } + }, + { + "name": "lastName", + "displayName": "${lastName}", + "permissions": { + "view": ["admin", "user"], + "edit": ["admin", "user"] + }, + "validations": { + "length": { "max": 255 }, + "person-name-prohibited-characters": {} + } + } + ], + "groups": [ + { + "name": "user-metadata", + "displayHeader": "User metadata", + "displayDescription": "Attributes, which refer to user metadata" + } + ] +} \ No newline at end of file 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 21c76533a6334..abe4321c0789d 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 @@ -51,10 +51,12 @@ public Map start() { 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 --hostname-strict-https=false" - + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password", + + " --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());