From dd54f90748cce12b9bce5028ada541b91f41a379 Mon Sep 17 00:00:00 2001
From: Sergey Beryozkin
Date: Wed, 27 Nov 2024 13:08:06 +0000
Subject: [PATCH 1/3] Use CertificateGenerator in OIDC Wiremock tests
(cherry picked from commit ebb2c0c3aef2a0fc6faccd24a9ef6536374b13bf)
---
integration-tests/oidc-wiremock/pom.xml | 5 ++
.../keycloak/CustomTenantConfigResolver.java | 2 +-
.../src/main/resources/application.properties | 12 ++--
.../main/resources/truststore-rootcert.p12 | Bin 1862 -> 0 bytes
.../src/main/resources/truststore.p12 | Bin 3238 -> 0 bytes
.../BearerTokenAuthorizationTest.java | 67 +++++++-----------
.../keycloak/CodeFlowAuthorizationTest.java | 2 -
.../CustomOidcWiremockTestResource.java | 59 +++++++++++++++
.../io/quarkus/it/keycloak/TestUtils.java | 29 ++++++--
.../src/test/resources/ca.cert.pem | 34 ---------
.../src/test/resources/intermediate.cert.pem | 33 ---------
.../resources/www.quarkustest.com.cert.pem | 29 --------
.../resources/www.quarkustest.com.key.pem | 28 --------
13 files changed, 120 insertions(+), 180 deletions(-)
delete mode 100644 integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12
delete mode 100644 integration-tests/oidc-wiremock/src/main/resources/truststore.p12
create mode 100644 integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CustomOidcWiremockTestResource.java
delete mode 100644 integration-tests/oidc-wiremock/src/test/resources/ca.cert.pem
delete mode 100644 integration-tests/oidc-wiremock/src/test/resources/intermediate.cert.pem
delete mode 100644 integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.cert.pem
delete mode 100644 integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.key.pem
diff --git a/integration-tests/oidc-wiremock/pom.xml b/integration-tests/oidc-wiremock/pom.xml
index dfddc45a735e1..0f6a6fb5e5fb3 100644
--- a/integration-tests/oidc-wiremock/pom.xml
+++ b/integration-tests/oidc-wiremock/pom.xml
@@ -43,6 +43,11 @@
rest-assured
test
+
+ io.smallrye.certs
+ smallrye-certificate-generator
+ test
+
org.htmlunit
htmlunit
diff --git a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
index 78ff79e9f8d16..9171e9071ba6d 100644
--- a/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
+++ b/integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomTenantConfigResolver.java
@@ -71,7 +71,7 @@ public Uni resolve(RoutingContext context,
} else if (path.endsWith("bearer-certificate-full-chain-root-only")) {
OidcTenantConfig config = new OidcTenantConfig();
config.setTenantId("bearer-certificate-full-chain-root-only");
- config.getCertificateChain().setTrustStoreFile(Path.of("truststore-rootcert.p12"));
+ config.getCertificateChain().setTrustStoreFile(Path.of("target/chain/truststore-rootcert.p12"));
config.getCertificateChain().setTrustStorePassword("storepassword");
config.getCertificateChain().setLeafCertificateName("www.quarkustest.com");
return Uni.createFrom().item(config);
diff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties
index f3d532751976e..ebf73424c3731 100644
--- a/integration-tests/oidc-wiremock/src/main/resources/application.properties
+++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties
@@ -143,7 +143,7 @@ quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.authentication.verify-
quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.client-id=quarkus-web-app
quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.credentials.client-secret.value=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.credentials.client-secret.method=query
-quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-file=truststore.p12
+quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-file=target/chain/truststore.p12
quarkus.oidc.code-flow-user-info-github-cached-in-idtoken.certificate-chain.trust-store-password=storepassword
@@ -172,7 +172,7 @@ quarkus.oidc.bearer-kid-or-chain.client-id=quarkus-app
quarkus.oidc.bearer-kid-or-chain.credentials.secret=secret
quarkus.oidc.bearer-kid-or-chain.token.audience=https://service.example.com
quarkus.oidc.bearer-kid-or-chain.allow-token-introspection-cache=false
-quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-file=truststore.p12
+quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-file=target/chain/truststore.p12
quarkus.oidc.bearer-kid-or-chain.certificate-chain.trust-store-password=storepassword
quarkus.oidc.bearer-id.auth-server-url=${keycloak.url}/realms/quarkus/
@@ -199,7 +199,7 @@ quarkus.oidc.bearer-azure.jwks-path=${keycloak.url}/azure/jwk
quarkus.oidc.bearer-azure.jwks.resolve-early=false
quarkus.oidc.bearer-azure.token.lifespan-grace=2147483647
quarkus.oidc.bearer-azure.token.customizer-name=azure-access-token-customizer
-quarkus.oidc.bearer-azure.certificate-chain.trust-store-file=truststore.p12
+quarkus.oidc.bearer-azure.certificate-chain.trust-store-file=target/chain/truststore.p12
quarkus.oidc.bearer-azure.certificate-chain.trust-store-password=storepassword
quarkus.oidc.bearer-role-claim-path.auth-server-url=${keycloak.url}/realms/quarkus/
@@ -215,14 +215,14 @@ quarkus.oidc.bearer-no-introspection.credentials.secret=secret
quarkus.oidc.bearer-no-introspection.token.audience=https://service.example.com
quarkus.oidc.bearer-no-introspection.token.allow-jwt-introspection=false
-quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-file=truststore.p12
+quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-file=target/chain/truststore.p12
quarkus.oidc.bearer-certificate-full-chain.certificate-chain.trust-store-password=storepassword
-quarkus.oidc.bearer-chain-custom-validator.certificate-chain.trust-store-file=truststore.p12
+quarkus.oidc.bearer-chain-custom-validator.certificate-chain.trust-store-file=target/chain/truststore.p12
quarkus.oidc.bearer-chain-custom-validator.certificate-chain.trust-store-password=storepassword
quarkus.oidc.bearer-chain-custom-validator.token.audience=https://service.example.com
-quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.trust-store-file=truststore-rootcert.p12
+quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.trust-store-file=target/chain/truststore-rootcert.p12
quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.trust-store-password=storepassword
quarkus.oidc.bearer-certificate-full-chain-root-only-wrongcname.certificate-chain.leaf-certificate-name=www.quarkusio.com
diff --git a/integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12 b/integration-tests/oidc-wiremock/src/main/resources/truststore-rootcert.p12
deleted file mode 100644
index e6a5a80173a456b27d3b92838280ea0e24f0e5fb..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1862
zcmV-M2f6q#f(JqZ0Ru3C2J8k2Duzgg_YDCD0ic2g-2{RL*)W0z)i8nv(FO@BhDe6@
z4FLxRpn?X%FoFiY0s#Opf(Ew+2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q3gq`{dQWc@bB20%|E54N$P+QzS#6<4_SJ8X{N
z^QIqSz3_PeGJbvEsINt?>p`bfyt(L#mvQGtN1wG*OS1`~6FT
z*+=sLB~J!X#%6@SQG$rib9=|MyseFVrIEAJrT*WtNyBd
zc+KPna<_%8YdmAO-rQi>?7JBq+1Odgx
zg2!gIcZ2(D?;;QlT#`K41vGP**m4K
zv&!3S1P6KCzyvThOKnn`kUzM*fknOk=PXaC>OSDWPpX
z2tjKugnA#<(L$WkaV@~wDkcfO6I!n2DF#N&>@3xYbok7xZQmZur=mK*%sHLEy%kCS
zYChphDX5ZlfNSdYhsp_V@LgObgUwX6*QoyTvzemWxZJO6`57ub_58O6>IByZRJ<*#!@!(E#ok#YX
zcNxACg~_DSmO;tAZ2)q|rYT;2`sKKX?Gc)b
zoKra#A*gv(nFXoDoS#wCunoTUE(w|r1(Wa+$cFV)QF3Ic%s|KQRzfkBjN=Sir_~;E
z>N3y#B7`V1RBPNz*Nhn-FygYiTE1LM0zO#NB+vY;CVo&UIL$t=+B@_sv*HiC=X9_r
z)S=UHiOPAlP0^y=XA?K9K9-&dz&Q293PM!+f>=avLlIT2lL{3dXh##?ZXV~wtrC7C@pC4m0+S9_u
zc;;>IvwrZ|#ksofds!Z%=MgRxgj)TY@12e
zbvw463b;1Ohy-uBV@7D?&O>cao#!aEEH;Y6MbR(Ac`jqfyXW`FXo0o!4iAlp0{ARh
zqp5cis&e>c;af#vM}kz;`6KTMgFZIlRPsUqY2(I%$(WL@^IoC*&Wu(%9RZ5$fE$};f)I74cVZ)JwLG?G3Sli|}C0W)3
zU^L(P*`7J~)-;R_Zr}g|30#FNC9O71OfpC00bb}ni)SqkMA-2
zKt1@jFp48R!rV!`zZ3cq6tZ&=4~0hr6gSQPH-h^YW8?LM;J@nkEt7F+?g9cQ5J6;z
AH~;_u
diff --git a/integration-tests/oidc-wiremock/src/main/resources/truststore.p12 b/integration-tests/oidc-wiremock/src/main/resources/truststore.p12
deleted file mode 100644
index b0c1f8bcb4164b99b5efca0cf4ab719bb66d7210..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 3238
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
YeayXOacNY5cYrvK>z>%
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
index d6f5bb2efbd48..83b2438ddfc11 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java
@@ -22,7 +22,6 @@
import org.awaitility.Awaitility;
import org.hamcrest.Matchers;
import org.jose4j.jwx.HeaderParameterNames;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -39,12 +38,10 @@
import io.smallrye.jwt.algorithm.SignatureAlgorithm;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;
-import io.smallrye.jwt.util.KeyUtils;
-import io.smallrye.jwt.util.ResourceUtils;
import io.vertx.core.json.JsonObject;
@QuarkusTest
-@QuarkusTestResource(OidcWiremockTestResource.class)
+@QuarkusTestResource(CustomOidcWiremockTestResource.class)
public class BearerTokenAuthorizationTest {
@OidcWireMock
@@ -62,7 +59,6 @@ public void testSecureAccessSuccessPreferredUsername() {
}
@Test
- @Disabled
public void testAccessResourceAzure() throws Exception {
String azureToken = readFile("token.txt");
String azureJwk = readFile("jwks.json");
@@ -192,16 +188,13 @@ public void testAccessAdminResourceWithWrongCertS256Thumbprint() {
}
@Test
- @Disabled
public void testCertChainWithCustomValidator() throws Exception {
- X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem"));
- X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem"));
- X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem"));
- PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem");
+ List chain = TestUtils.loadCertificateChain();
+ PrivateKey subjectPrivateKey = TestUtils.loadLeafCertificatePrivateKey();
// Send the token with the valid certificate chain and bind it to the token claim
String accessToken = getAccessTokenForCustomValidator(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey, "https://service.example.com", true, false);
RestAssured.given().auth().oauth2(accessToken)
@@ -212,7 +205,7 @@ public void testCertChainWithCustomValidator() throws Exception {
// Send the token with the valid certificate chain but do not bind it to the token claim
accessToken = getAccessTokenForCustomValidator(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey, "https://service.example.com", false, false);
RestAssured.given().auth().oauth2(accessToken)
@@ -222,7 +215,7 @@ public void testCertChainWithCustomValidator() throws Exception {
// Send the token with the valid certificate chain bound to the token claim, but expired
accessToken = getAccessTokenForCustomValidator(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey, "https://service.example.com", true, true);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-chain-custom-validator")
@@ -231,7 +224,7 @@ public void testCertChainWithCustomValidator() throws Exception {
// Send the token with the valid certificate chain but with the wrong audience
accessToken = getAccessTokenForCustomValidator(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey, "https://server.example.com", true, false);
RestAssured.given().auth().oauth2(accessToken)
@@ -242,16 +235,14 @@ public void testCertChainWithCustomValidator() throws Exception {
}
@Test
- @Disabled
public void testAccessAdminResourceWithFullCertChain() throws Exception {
- X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem"));
- X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem"));
- X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem"));
- PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem");
+ // index 2 - root, index 1 - intermediate, index 0 - leaf
+ List chain = TestUtils.loadCertificateChain();
+ PrivateKey subjectPrivateKey = TestUtils.loadLeafCertificatePrivateKey();
// Send the token with the valid certificate chain and bind it to the token claim
String accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
@@ -268,7 +259,7 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception {
// Send the token with the valid certificate chain, but with the token signed by a non-matching private key
accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate());
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain")
@@ -277,7 +268,7 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception {
// Send the token with the valid certificates but which are in the wrong order in the chain
accessToken = getAccessTokenWithCertChain(
- List.of(intermediateCert, subjectCert, rootCert),
+ List.of(chain.get(1), chain.get(0), chain.get(2)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain")
@@ -286,7 +277,7 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception {
// Send the token with the valid certificates but with the intermediate one omitted from the chain
accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, rootCert),
+ List.of(chain.get(0), chain.get(2)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain")
@@ -295,7 +286,7 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception {
// Send the token with the only the last valid certificate
accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert),
+ List.of(chain.get(0)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain")
@@ -305,16 +296,13 @@ public void testAccessAdminResourceWithFullCertChain() throws Exception {
}
@Test
- @Disabled
public void testFullCertChainWithOnlyRootInTruststore() throws Exception {
- X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem"));
- X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem"));
- X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem"));
- PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem");
+ List chain = TestUtils.loadCertificateChain();
+ PrivateKey subjectPrivateKey = TestUtils.loadLeafCertificatePrivateKey();
// Send the token with the valid certificate chain
String accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
@@ -331,7 +319,7 @@ public void testFullCertChainWithOnlyRootInTruststore() throws Exception {
// Send the token with the valid certificates but which are in the wrong order in the chain
accessToken = getAccessTokenWithCertChain(
- List.of(intermediateCert, subjectCert, rootCert),
+ List.of(chain.get(1), chain.get(0), chain.get(2)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain-root-only")
@@ -340,7 +328,7 @@ public void testFullCertChainWithOnlyRootInTruststore() throws Exception {
// Send the token with the valid certificates but with the intermediate one omitted from the chain
accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, rootCert),
+ List.of(chain.get(0), chain.get(2)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain-root-only")
@@ -349,7 +337,7 @@ public void testFullCertChainWithOnlyRootInTruststore() throws Exception {
// Send the token with the only the last valid certificate
accessToken = getAccessTokenWithCertChain(
- List.of(subjectCert),
+ List.of(chain.get(0)),
subjectPrivateKey);
RestAssured.given().auth().oauth2(accessToken)
.when().get("/api/admin/bearer-certificate-full-chain-root-only")
@@ -358,7 +346,6 @@ public void testFullCertChainWithOnlyRootInTruststore() throws Exception {
}
@Test
- @Disabled
public void testAccessAdminResourceWithKidOrChain() throws Exception {
// token with a matching kid, not x5c
String token = Jwt.preferredUserName("admin")
@@ -403,14 +390,12 @@ public void testAccessAdminResourceWithKidOrChain() throws Exception {
.then()
.statusCode(401);
- X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem"));
- X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem"));
- X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem"));
- PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem");
+ List chain = TestUtils.loadCertificateChain();
+ PrivateKey subjectPrivateKey = TestUtils.loadLeafCertificatePrivateKey();
// Send the token with the valid certificate chain
token = getAccessTokenWithCertChain(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey);
TestUtils.assertX5cOnlyIsPresent(token);
@@ -429,7 +414,7 @@ public void testAccessAdminResourceWithKidOrChain() throws Exception {
// Send the token with the valid certificate chain with certificates in the wrong order
token = getAccessTokenWithCertChain(
- List.of(intermediateCert, subjectCert, rootCert),
+ List.of(chain.get(1), chain.get(0), chain.get(2)),
subjectPrivateKey);
TestUtils.assertX5cOnlyIsPresent(token);
@@ -445,7 +430,7 @@ public void testAccessAdminResourceWithKidOrChain() throws Exception {
.groups(Set.of("admin"))
.issuer("https://server.example.com")
.audience("https://service.example.com")
- .jws().keyId("1").chain(List.of(intermediateCert, subjectCert, rootCert))
+ .jws().keyId("1").chain(List.of(chain.get(1), chain.get(0), chain.get(2)))
.sign(subjectPrivateKey);
assertBothKidAndX5cArePresent(token, "1");
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
index d5845341e9648..0741e298c5236 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java
@@ -50,7 +50,6 @@
import org.htmlunit.util.Cookie;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.github.tomakehurst.wiremock.WireMockServer;
@@ -337,7 +336,6 @@ public void testCodeFlowUserInfo() throws Exception {
}
@Test
- @Disabled
public void testCodeFlowUserInfoCachedInIdToken() throws Exception {
// Internal ID token, allow in memory cache = false, cacheUserInfoInIdtoken = true
final String refreshJwtToken = generateAlreadyExpiredRefreshToken();
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CustomOidcWiremockTestResource.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CustomOidcWiremockTestResource.java
new file mode 100644
index 0000000000000..3cd5564e7b7d6
--- /dev/null
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CustomOidcWiremockTestResource.java
@@ -0,0 +1,59 @@
+package io.quarkus.it.keycloak;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import io.quarkus.test.oidc.server.OidcWiremockTestResource;
+import io.smallrye.certs.chain.CertificateChainGenerator;
+import io.smallrye.jwt.util.KeyUtils;
+
+public class CustomOidcWiremockTestResource extends OidcWiremockTestResource {
+ @Override
+ public Map start() {
+ try {
+ generateCertificates();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ return super.start();
+ }
+
+ private void generateCertificates() throws Exception {
+ File chainDir = new File("target/chain");
+ CertificateChainGenerator chainGenerator = new CertificateChainGenerator(chainDir)
+ .withCN("www.quarkustest.com");
+ chainGenerator.generate();
+
+ Path rootCertPath = Paths.get("target/chain/root.crt");
+ X509Certificate rootCert = KeyUtils.getCertificate(Files.readString(rootCertPath));
+
+ Path leafCertPath = Paths.get("target/chain/www.quarkustest.com.crt");
+ X509Certificate leafCert = KeyUtils.getCertificate(Files.readString(leafCertPath));
+
+ File trustStore = new File(chainDir, "truststore.p12");
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keyStore.load(null, null);
+ keyStore.setCertificateEntry("root", rootCert);
+ keyStore.setCertificateEntry("leaf", leafCert);
+ var fos = new FileOutputStream(trustStore);
+ keyStore.store(fos, "storepassword".toCharArray());
+ fos.close();
+
+ File trustStoreRoot = new File(chainDir, "truststore-rootcert.p12");
+ KeyStore keyStoreRootCert = KeyStore.getInstance("PKCS12");
+ keyStoreRootCert.load(null, null);
+ keyStoreRootCert.setCertificateEntry("root", rootCert);
+ var fosRootCert = new FileOutputStream(trustStoreRoot);
+ keyStoreRootCert.store(fosRootCert, "storepassword".toCharArray());
+ fosRootCert.close();
+
+ }
+
+}
diff --git a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/TestUtils.java b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/TestUtils.java
index 591ca8c360f4d..c875b28822607 100644
--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/TestUtils.java
+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/TestUtils.java
@@ -3,6 +3,9 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -11,19 +14,33 @@
import io.quarkus.oidc.runtime.TrustStoreUtils;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.util.KeyUtils;
-import io.smallrye.jwt.util.ResourceUtils;
import io.vertx.core.json.JsonObject;
public class TestUtils {
+ public static List loadCertificateChain() throws Exception {
+ Path rootCertPath = Paths.get("target/chain/root.crt");
+ Path intermediateCertPath = Paths.get("target/chain/intermediate.crt");
+ Path leafCertPath = Paths.get("target/chain/www.quarkustest.com.crt");
+
+ X509Certificate rootCert = KeyUtils.getCertificate(Files.readString(rootCertPath));
+ X509Certificate intermediateCert = KeyUtils.getCertificate(Files.readString(intermediateCertPath));
+ X509Certificate subjectCert = KeyUtils.getCertificate(Files.readString(leafCertPath));
+
+ return List.of(subjectCert, intermediateCert, rootCert);
+ }
+
+ public static PrivateKey loadLeafCertificatePrivateKey() throws Exception {
+ Path leafKeyPath = Paths.get("target/chain/www.quarkustest.com.key");
+ return KeyUtils.decodePrivateKey(Files.readString(leafKeyPath));
+ }
+
public static String createTokenWithInlinedCertChain(String preferredUserName) throws Exception {
- X509Certificate rootCert = KeyUtils.getCertificate(ResourceUtils.readResource("/ca.cert.pem"));
- X509Certificate intermediateCert = KeyUtils.getCertificate(ResourceUtils.readResource("/intermediate.cert.pem"));
- X509Certificate subjectCert = KeyUtils.getCertificate(ResourceUtils.readResource("/www.quarkustest.com.cert.pem"));
- PrivateKey subjectPrivateKey = KeyUtils.readPrivateKey("/www.quarkustest.com.key.pem");
+ List chain = loadCertificateChain();
+ PrivateKey subjectPrivateKey = loadLeafCertificatePrivateKey();
String bearerAccessToken = getAccessTokenWithCertChain(
- List.of(subjectCert, intermediateCert, rootCert),
+ chain,
subjectPrivateKey,
preferredUserName);
diff --git a/integration-tests/oidc-wiremock/src/test/resources/ca.cert.pem b/integration-tests/oidc-wiremock/src/test/resources/ca.cert.pem
deleted file mode 100644
index b8ec4ac6c5dd1..0000000000000
--- a/integration-tests/oidc-wiremock/src/test/resources/ca.cert.pem
+++ /dev/null
@@ -1,34 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIF0TCCA7mgAwIBAgIUevkdgNus9CUyOGrDiHwuFFAzSsowDQYJKoZIhvcNAQEL
-BQAwcDELMAkGA1UEBhMCSUUxDzANBgNVBAgMBkR1YmxpbjEPMA0GA1UEBwwGRHVi
-bGluMRAwDgYDVQQKDAdRdWFya3VzMRswGQYDVQQLDBJRdWFya3VzIERlcGFydG1l
-bnQxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjMxMTE3MTIyNzI2WhcNNDMxMTEyMTIy
-NzI2WjBwMQswCQYDVQQGEwJJRTEPMA0GA1UECAwGRHVibGluMQ8wDQYDVQQHDAZE
-dWJsaW4xEDAOBgNVBAoMB1F1YXJrdXMxGzAZBgNVBAsMElF1YXJrdXMgRGVwYXJ0
-bWVudDEQMA4GA1UEAwwHUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
-AgoCggIBALQFnHt8yToRlnRKtwcf8yaKzVH53rdv2D9kVzGyuqNVwyPvnPx/Vo88
-lOVeNFY9cj9aTpuVry8wO58Xf6+eMrfiGsHkyW2Fi7PeMyTN5V+smhDonYrZIEKU
-UsGuEFwnsAdAPyboQAXG3Xy82OJD3HZfARIoA5l80GtnoeQicKex724bhSohm5ZS
-XdGTlHRhTLcG1eaidccUyBAJjMexnCsqHdLfzrKOK/Hl8wPPNXOTPZZ6GmjWub5g
-Ti6qYu/tkuC2hlu+rEFVql75cpJ9sA5P/DRF/0A7dJClWSNErG2ATcoImpaxUnpd
-jSs76LIx779nOd6zbIaSyIwzbPoTxuoiAK5Fg8dZjK2A+omwfnIHvd30/5D7NcQj
-LshRWH/G26/rdpj0c3ZwpW2md065cFVgal/m1nsEqREjHyRvm1PkacKAEw9A4gUQ
-Au0NYTX6KWE2TcTQdKbcGlBQPcNkJPKdbv+bfNs6+BreEjltcIMZ0Xl7qPVOU3Hm
-d44avBoHQRhHDg2ud7ZFxpvhjxKmUwEGTDdgt2vuXAyEkrfGCQ2AE58nlcLzAdWN
-Zaq0o0WObzW5pXjcSslEln/U5x94U7Fnql5/XD27UqvMYTkZAK0fyYnsZcghrf4Q
-qq25HipDD9j4YtDvBOYE24nxxVZWK4k5kjc5et6zWRjBt6cH99yXAgMBAAGjYzBh
-MB0GA1UdDgQWBBSjdDFDHtjprW5hclFqtSK/sz6gojAfBgNVHSMEGDAWgBSjdDFD
-HtjprW5hclFqtSK/sz6gojAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
-BjANBgkqhkiG9w0BAQsFAAOCAgEAKha7J68N/SIr/FD/X+moJF7QzCH0fPhvYHJg
-l8dOB39b901nJ0mRVxUH5pZwsoi4nxU4t52leLT5hUgyG02H0v4eHYHtkMBAJ0IE
-gdmsNRrdZPulPs5/hrLOZJG41X3X485qUouyKjZlDSjr4djFifzOWHL9VonyDSV4
-j7MtKnnTo5UzqKt3fcr0LzP5x4t3M6dZgTMjIG5C6pmar6Qp2htZh1RFx+wW+KJy
-ULhfByID5hrA99Q5gS7w27EjvD80tgDZaRbrV/gt4hI/0W0NHvP1m1HX0oe0bhWx
-soBMLaaH0F+LSo3jU3e7OakP2/i2Jpz5sIKndL6lIf80o2Ngo+LQr4aPK7lzPPYV
-U7I2Il0KfklbFUmbVYNdVtbZKaOwdEU0ADqptJY1cnH9putd5Z1ea9NWENcXFaRs
-LfgqFagEKTZZkwkX2oNHH9bwEZsfAgr2OWjzHIcfQ3NnRBPymx1CB2QMYuPbg6ql
-6eGjRBWVPpMK/tGp9BDIfPC5Kq4yuAMihKoDuikL12hKKB59VfpybBH+ziVxBcyD
-LO8Fsuu5V85TOaZ3DFqp8ZODQWnvDre8o0VxwdH+4SC01qhTZRtqSdoOXGrt+J0B
-EsIvFGOOEE5W+23Hcr6Nwl5YFm95f2ZPCkb5Iu2Wp6BtlZQBFfTXiGhP+LT008Yl
-W+0+5Lc=
------END CERTIFICATE-----
diff --git a/integration-tests/oidc-wiremock/src/test/resources/intermediate.cert.pem b/integration-tests/oidc-wiremock/src/test/resources/intermediate.cert.pem
deleted file mode 100644
index 27dc46ad3ff27..0000000000000
--- a/integration-tests/oidc-wiremock/src/test/resources/intermediate.cert.pem
+++ /dev/null
@@ -1,33 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFuTCCA6GgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCSUUx
-DzANBgNVBAgMBkR1YmxpbjEPMA0GA1UEBwwGRHVibGluMRAwDgYDVQQKDAdRdWFy
-a3VzMRswGQYDVQQLDBJRdWFya3VzIERlcGFydG1lbnQxEDAOBgNVBAMMB1Jvb3Qg
-Q0EwHhcNMjMxMTE3MTIzMDIwWhcNMzMxMTE0MTIzMDIwWjBnMQswCQYDVQQGEwJJ
-RTEPMA0GA1UECAwGRHVibGluMRAwDgYDVQQKDAdRdWFya3VzMRswGQYDVQQLDBJR
-dWFya3VzIERlcGFydG1lbnQxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTCCAiIw
-DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMFfN7Pi8dF+gQTglUDbnTsunr10
-0Ldeozyg4UK+V59NJ8FSX9k1OZeSIjqEN3kljtjNzKX7eqwIw/8UjboQeusIM9jM
-x+cu0ifWlulu/TBLzZfxO3Laq0lSJWuoPRt9tDAdA1PJK62p044zp6i6B9PDldhi
-RpZPj7PJcAoEh3NTmzh7icKVmcGfj2Xo+M/TOiGtKsIhH34w/aXi6u/03PsBs0gw
-8Lids9WTUFGIvf4jAeCzuxWL7RQVr7qDhEvlKEh1tRknSUf0W2yJCE+aFD3XL/b2
-r6qc+CbsV59n+IcARH7gFDEBkAdk9lBozmF7o7+ADc5CZAjtN2FGWDhLXoCV8fvT
-4/sGLsT/MGZPUS4vqw+Gl3+Qx0qk+DgVrwWGFPX168vXBEB/f+AsCX3O9Hn6vNjB
-uEoIi6+bZP6P2MHThARzOm705cM9xxvj82qrdpYtxhi4jLAwzZ/Aa+1wTu1Uobub
-9LXoyuNHSppuK1gi4DZYUHs9YkQwVTwGu1cyYI9oCy9tZ90YqDHHQCyNqAFEPyjw
-C9JT1B3g3gZiiPfpVtzvqxG8qNC6fYyYDSq4aNjlCfv8jYBPylByUyVMG5QnS0ai
-5lYhUF6L8v1jWrsMvkBCmua4TvP4ofa8qUNOpS5eKTNiWk8wkuGne8MhSPDAExVO
-z82E3S8s4jdpwWgHAgMBAAGjZjBkMB0GA1UdDgQWBBRM+ZXzjUoJLnQsxPsFa6CJ
-uIjsyTAfBgNVHSMEGDAWgBSjdDFDHtjprW5hclFqtSK/sz6gojASBgNVHRMBAf8E
-CDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAAa8u
-aDO6nrADunYa6ePJLmxpiMCpaJR6XcM3UkZkQ+zdHsPLPl9lbN2tbr982CWbr89Q
-QmkHMxylQQ4QLwUM6USzDohIiQ516I0LcGUatTymVXKRwSZc1xK587v4iI7LVy5P
-pQuBMeA+tteldyaaWTDL3ppa1UmNWksS3MDHOYcJ/GWqsCqD1Au0sj1E3bGW4t6b
-Wkan6gUG7z1IpXN8XOVSgbzTkWnH89LJns6YUHMnIXb+qQflLCbuj3TYa5H5JgOC
-atVJEHOtqCaDxNDH5t99zFrvLkYy/AJ2QMqMlLS0pWRBmRcaBXEQl7npenZOUgn9
-A0AKs3hoYl0aF0aVMmy7R1Rx+V0G7s3AFZ31QUuWRiy50QJmZg4qZYliZFMcFZrg
-H4T1IKcF1IddU7/tUodaaCP6DT9HRufJ8VNw8kFeFYK414TgvyuViIpHHGuUOLDl
-Ee6ONp3VkzY3sseXpmR14JRnT2JOL5yt1kaDc8VdyLe+v57NURNUB8s5s6x/oIYI
-9BDT1paHb38C/g8E72emgRMs+LwABJJm72hBiKo4eI8uDYKSTKuzxF9JANMMXPG9
-wvCgyFzf1ySsFlFueAMDVZJtqD0SPqtbilfJJ6lUmzyWEb2WYaUDpSiqmNU4Dnw0
-b+x+3T3uJ8oAbV9xBA9aba6M+fkzjtZ0VakcFpM=
------END CERTIFICATE-----
diff --git a/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.cert.pem b/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.cert.pem
deleted file mode 100644
index cb05e37ef44da..0000000000000
--- a/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.cert.pem
+++ /dev/null
@@ -1,29 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIE6zCCAtOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UEBhMCSUUx
-DzANBgNVBAgMBkR1YmxpbjEQMA4GA1UECgwHUXVhcmt1czEbMBkGA1UECwwSUXVh
-cmt1cyBEZXBhcnRtZW50MRgwFgYDVQQDDA9JbnRlcm1lZGlhdGUgQ0EwHhcNMjMx
-MTE3MTIzNjQ1WhcNMjQxMTI2MTIzNjQ1WjCBgDELMAkGA1UEBhMCSUUxDzANBgNV
-BAgMBkR1YmxpbjEPMA0GA1UEBwwGRHVibGluMRAwDgYDVQQKDAdRdWFya3VzMR8w
-HQYDVQQLDBZRdWFya3VzSW50ZWdyYXRpb25UZXN0MRwwGgYDVQQDDBN3d3cucXVh
-cmt1c3Rlc3QuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuqLr
-N3x2QK1oc5FeAiImBSq8ouaUJ5s9wZre7A/2RrM1ZTzUK/VyoynWaVxYIkjdGTpT
-H6xYtz3+T6z/l0xHO/tugXGHDGEQOgstvh0E8C1DrdvIOqdPtNYUBW6Nw0NVrVwH
-ClBDSFN5Xw89YhjydtETy11joKQ2X9SViDfCICOVpx0ml05Txc45CUJsDofEX5HQ
-C0eG32cuemvMLouAFH9fMfoVrx9Yhy5vBrzlX22s0ig9bu53qQlNuzj5AUcNKUCM
-NttRltptHmRiAnRzUIGiOhXuaz0oEIU80p82sVM78tfY4qXIu9LVWYM1qqpYielx
-BkjC7GElG7Log4lf4QIDAQABo4GGMIGDMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEB
-BAQDAgZAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNV
-HSMEGDAWgBRM+ZXzjUoJLnQsxPsFa6CJuIjsyTAdBgNVHQ4EFgQUEQ404BYvLmtz
-vfS6I3iW4vKM/4QwDQYJKoZIhvcNAQELBQADggIBAFRMZozul2oqjOhQG9todI8w
-6woJk5b5VDf0c5zPvQntWeS8vv7Bysy7yqmfdZFqZXZstIfmX+USvE1XOuQl+2X7
-BHn958lwyiKNUwNVm27Luf5yKEtjZHqmCvfCjcGAt5vuyyV1JcmP4gzEvEiTByMq
-A4VSw7+u8y0/kEJLpgoikQaBYgp5HPkqJ/EmI55QUKlIElX9cgJxz5ihdHw/EUxD
-C0AvKxH4SoMGxAlplz+ncJp6Ru6EI51dE1tIUlLwsFF39GjZ2a7AQCzG3umqM5ui
-sKI6l5DFU9HVoDbNrSJ0DWbvevC6jA9sGsQyUjwewrhrsOposR2NOS/RyMw7YdWi
-XIg50TmkWOyEScF7PQQ43qYL7JZQx9fB5k5Tscb0tV/anmTjbSQZmAeTsiHkDPeq
-hGeP5mnvIdETwS9AZyYFDam1xOPcFpnsN2MGGXIUBvI876rno3zZlNnq4ugbYYWw
-GlG/C7dseXP2dyvTsalNNUqSjZoFpwrQDPBFjTNxtKjX3E0J9ATL9QHsvO/+UkdG
-FFyKVAGsFkI0kYv6gaPoqPkJoLxK3wZJ/QXMLJPk/jz7jBz5YPwvR1huN4ZgE9A9
-UvFxgcHuDjsBaHd+DJeILv/O47ELLnVKjnmvACZt4WbxMzH4ZpcB1oN9zQ7RP/CK
-YZrxGk8DGTBADYV4cHl2
------END CERTIFICATE-----
diff --git a/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.key.pem b/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.key.pem
deleted file mode 100644
index 38080bc66d484..0000000000000
--- a/integration-tests/oidc-wiremock/src/test/resources/www.quarkustest.com.key.pem
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC6ous3fHZArWhz
-kV4CIiYFKryi5pQnmz3Bmt7sD/ZGszVlPNQr9XKjKdZpXFgiSN0ZOlMfrFi3Pf5P
-rP+XTEc7+26BcYcMYRA6Cy2+HQTwLUOt28g6p0+01hQFbo3DQ1WtXAcKUENIU3lf
-Dz1iGPJ20RPLXWOgpDZf1JWIN8IgI5WnHSaXTlPFzjkJQmwOh8RfkdALR4bfZy56
-a8wui4AUf18x+hWvH1iHLm8GvOVfbazSKD1u7nepCU27OPkBRw0pQIw221GW2m0e
-ZGICdHNQgaI6Fe5rPSgQhTzSnzaxUzvy19jipci70tVZgzWqqliJ6XEGSMLsYSUb
-suiDiV/hAgMBAAECggEAA6VZm3agt8A7dWB+WePRYtH0J+mBtOldMjpEhw+Dw9tN
-3Hms+mPb1rCjSeEeLqNGQG8pfwmmnQPGw0cxogLBNHyDqt8tIHKH9t5PiTJ3bXqw
-4wVTWsP4dGOnNfj0J3+Z/Z452/t36QKKcS8yx4cNu4D3lkYvg0yZ7FLSjfvq3KZU
-w8m8/4EPtP2+KxIvFIDU5/5W43wYUv6QctTEuIDRfikdik3oKDiNUGCtfd6OOQtE
-mINkDA6nLgOngyxj8jBAgkBIs+FfOoywPZP3cR9MeAdJYPBxEE3NN4wlZlQZCzkd
-SA10258coG7bXii8lTrik89v0WhvGOYtoWniddv7MQKBgQDge3fo06q82+QhP2wM
-ni99YMiYaW3oaotEAT86C8owDWR3z2+luk3HK34d/2uVhgzftTN3DMM70pZ7arI2
-GJqxNdzK2YGrACNB9bYCLhRj4/ITGannz6cjrHsiRH99BGpyeZQKFNmvrgY8ru+j
-GSy17JY0/8Kj7gQYohnvHdh6SQKBgQDU1ylO6ZHZykfXvPgvnFqYUHiOgBwk6Y7+
-ClozmZL48u/42PpsVNuoquzH5V1kIBDwWZjUsOnZX5rbv0YzOJ449PsVw+pM+NAb
-Dwtzwgfb9/uBryFKMgiXMToOEAyN+ENRg4PpyHRQu+shVh8/MPdMytA8AaypmZP7
-aPKhw/v42QKBgGG4PcWjxtJ54oA6rJ19iuzIYeo/EvI21zMeW9i34ycx3UdujIqX
-ZF5MZ5VFaB7qANateZ7cdmynSoylMLjt0wFLkjbXomO/JpoDDV07k/K7+tgnttfL
-hFW6MswDB4BzmKcGl9QfqXeZiOuHt5fHULhNKkIeCCv2Y+AZSLLXyjHJAoGAcmDq
-RwkII0UsVIity1A381CTaOj5tvB4spa3oLEwJW7QfSeFdEAqBztLoaTmCk+dKrzL
-f8lO8k0JeHwS6qXLiYpFgI3XVOQFWfU8z0l/VbuvQiLuPeQjb7S5oSMIzCaVbrHB
-axoZP+Ws1y5j/l5/F5qKSyUPN9lbiCj+8uXSfAkCgYAcZ8i2+4ji9Ntu83G90GWi
-hS2JOlZEUOCcE9vRu6HDkWC0qfkGbhjUk5GUHBjFp0shRTR/GnGA0ILAzgoxEK1s
-/aDel9XDeuF4DJC+HzvPDoPFYz0UH4CuOYWhAejL81a5/AAHQqm2fpQSNln097rC
-KfGyU23XuO7U8BloCy/hCA==
------END PRIVATE KEY-----
From b9278d50148097d3c3adb0fc635907cd0aaa6d1f Mon Sep 17 00:00:00 2001
From: Martin Kouba
Date: Tue, 3 Dec 2024 08:24:51 +0100
Subject: [PATCH 2/3] Qute message bundles: fix localization of enums
- support constants with underscores
- fixes #44866
(cherry picked from commit b322dbf358dd3f7356063f1e37d1ad4913c78044)
---
docs/src/main/asciidoc/qute-reference.adoc | 21 ++++++---
.../deployment/MessageBundleProcessor.java | 45 ++++++++++++++-----
.../i18n/MessageBundleEnumTest.java | 44 +++++++++++++++++-
.../MessageBundleInvalidEnumConstantTest.java | 43 ++++++++++++++++++
.../test/resources/messages/enu.properties | 11 ++++-
.../test/resources/messages/enu_cs.properties | 11 ++++-
.../resources/messages/enu_invalid.properties | 2 +
.../java/io/quarkus/qute/i18n/Message.java | 12 +++--
8 files changed, 167 insertions(+), 22 deletions(-)
create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleInvalidEnumConstantTest.java
create mode 100644 extensions/qute/deployment/src/test/resources/messages/enu_invalid.properties
diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc
index 4b62a037bd432..162103b1c31cc 100644
--- a/docs/src/main/asciidoc/qute-reference.adoc
+++ b/docs/src/main/asciidoc/qute-reference.adoc
@@ -2994,10 +2994,9 @@ If there is a message bundle method that accepts a single parameter of an enum t
@Message <1>
String methodName(MyEnum enum);
----
-<1> The value is intentionally not provided. There's also no key for the method in a localized file.
-
-Then it receives a generated template:
+<1> The value is intentionally not provided. There's also no key/value pair for this method in a localized file.
+Then it receives a generated template like:
[source,html]
----
{#when enumParamName}
@@ -3006,7 +3005,8 @@ Then it receives a generated template:
{/when}
----
-Furthermore, a special message method is generated for each enum constant. Finally, each localized file must contain keys and values for all constant message keys:
+Furthermore, a special message method is generated for each enum constant.
+Finally, each localized file must contain keys and values for all enum constants:
[source,poperties]
----
@@ -3014,7 +3014,18 @@ methodName_CONSTANT1=Value 1
methodName_CONSTANT2=Value 2
----
-In a template, an enum constant can be localized with a message bundle method like `{msg:methodName(enumConstant)}`.
+// We need to escape the first underscore
+// See https://docs.asciidoctor.org/asciidoc/latest/subs/prevent/
+[IMPORTANT]
+.Message keys for enum constants
+====
+By default, the message key consists of the method name followed by the `\_` separator and the constant name.
+If any constant name of a particular enum contains the `_` or the `$` character then the `\_$` separator must be used for all message keys for this enum instead.
+For example, `methodName_$CONSTANT_1=Value 1` or `methodName_$CONSTANT$1=Value 1`.
+A constant of a localized enum may not contain the `_$` separator.
+====
+
+In a template, the localized message for an enum constant can be obtained with a message bundle method like `{msg:methodName(enumConstant)}`.
TIP: There is also <> - a convenient annotation to access enum constants in a template.
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
index 5842bbd715abc..a9e659b47b068 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java
@@ -865,16 +865,20 @@ private Map parseKeyToTemplateFromLocalizedFile(ClassInfo bundle
* @param key
* @param bundleInterface
* @return {@code true} if the given key represents an enum constant message key, such as {@code myEnum_CONSTANT1}
- * @see #toEnumConstantKey(String, String)
*/
boolean isEnumConstantMessageKey(String key, IndexView index, ClassInfo bundleInterface) {
if (key.isBlank()) {
return false;
}
- int lastIdx = key.lastIndexOf("_");
+ return isEnumConstantMessageKey("_$", key, index, bundleInterface)
+ || isEnumConstantMessageKey("_", key, index, bundleInterface);
+ }
+
+ private boolean isEnumConstantMessageKey(String separator, String key, IndexView index, ClassInfo bundleInterface) {
+ int lastIdx = key.lastIndexOf(separator);
if (lastIdx != -1 && lastIdx != key.length()) {
String methodName = key.substring(0, lastIdx);
- String constant = key.substring(lastIdx + 1, key.length());
+ String constant = key.substring(lastIdx + separator.length(), key.length());
MethodInfo method = messageBundleMethod(bundleInterface, methodName);
if (method != null && method.parametersCount() == 1) {
Type paramType = method.parameterType(0);
@@ -1021,11 +1025,12 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
// We need some special handling for enum message bundle methods
// A message bundle method that accepts an enum and has no message template receives a generated template:
// {#when enumParamName}
- // {#is CONSTANT1}{msg:org_acme_MyEnum_CONSTANT1}
- // {#is CONSTANT2}{msg:org_acme_MyEnum_CONSTANT2}
+ // {#is CONSTANT_1}{msg:myEnum_$CONSTANT_1}
+ // {#is CONSTANT_2}{msg:myEnum_$CONSTANT_2}
// ...
// {/when}
// Furthermore, a special message method is generated for each enum constant
+ // These methods are used to handle the {msg:myEnum$CONSTANT_1} and {msg:myEnum$CONSTANT_2}
if (messageTemplate == null && method.parametersCount() == 1) {
Type paramType = method.parameterType(0);
if (paramType.kind() == org.jboss.jandex.Type.Kind.CLASS) {
@@ -1036,9 +1041,12 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
.append("}");
Set enumConstants = maybeEnum.fields().stream().filter(FieldInfo::isEnumConstant)
.map(FieldInfo::name).collect(Collectors.toSet());
+ String separator = enumConstantSeparator(enumConstants);
for (String enumConstant : enumConstants) {
- // org_acme_MyEnum_CONSTANT1
- String enumConstantKey = toEnumConstantKey(method.name(), enumConstant);
+ // myEnum_CONSTANT
+ // myEnum_$CONSTANT_1
+ // myEnum_$CONSTANT$NEXT
+ String enumConstantKey = toEnumConstantKey(method.name(), separator, enumConstant);
String enumConstantTemplate = messageTemplates.get(enumConstantKey);
if (enumConstantTemplate == null) {
throw new TemplateException(
@@ -1052,6 +1060,10 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
.append(":")
.append(enumConstantKey)
.append("}");
+ // For each constant we generate a method:
+ // myEnum_CONSTANT(MyEnum val)
+ // myEnum_$CONSTANT_1(MyEnum val)
+ // myEnum_$CONSTANT$NEXT(MyEnum val)
generateEnumConstantMessageMethod(bundleCreator, bundleName, locale, bundleInterface,
defaultBundleInterface, enumConstantKey, keyMap, enumConstantTemplate,
messageTemplateMethods);
@@ -1132,8 +1144,21 @@ private String generateImplementation(MessageBundleBuildItem bundle, ClassInfo d
return generatedName.replace('/', '.');
}
- private String toEnumConstantKey(String methodName, String enumConstant) {
- return methodName + "_" + enumConstant;
+ private String enumConstantSeparator(Set enumConstants) {
+ for (String constant : enumConstants) {
+ if (constant.contains("_$")) {
+ throw new MessageBundleException("A constant of a localized enum may not contain '_$': " + constant);
+ }
+ if (constant.contains("$") || constant.contains("_")) {
+ // If any of the constants contains "_" or "$" then "_$" is used
+ return "_$";
+ }
+ }
+ return "_";
+ }
+
+ private String toEnumConstantKey(String methodName, String separator, String enumConstant) {
+ return methodName + separator + enumConstant;
}
private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, String bundleName, String locale,
@@ -1165,7 +1190,7 @@ private void generateEnumConstantMessageMethod(ClassCreator bundleCreator, Strin
// No expression/tag - no need to use qute
enumConstantMethod.returnValue(enumConstantMethod.load(messageTemplate));
} else {
- // Obtain the template, e.g. msg_org_acme_MyEnum_CONSTANT1
+ // Obtain the template, e.g. msg_myEnum$CONSTANT_1
ResultHandle template = enumConstantMethod.invokeStaticMethod(
io.quarkus.qute.deployment.Descriptors.BUNDLES_GET_TEMPLATE,
enumConstantMethod.load(templateId));
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumTest.java
index 8ac3a9e739810..c0b4eb2e481e0 100644
--- a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumTest.java
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleEnumTest.java
@@ -9,6 +9,7 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.qute.Template;
+import io.quarkus.qute.TemplateEnum;
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.MessageBundle;
import io.quarkus.test.QuarkusUnitTest;
@@ -18,7 +19,7 @@ public class MessageBundleEnumTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
- .addClasses(Messages.class, MyEnum.class)
+ .addClasses(Messages.class, MyEnum.class, UnderscoredEnum.class, AnotherUnderscoredEnum.class)
.addAsResource("messages/enu.properties")
.addAsResource("messages/enu_cs.properties")
.addAsResource(new StringAsset(
@@ -26,16 +27,39 @@ public class MessageBundleEnumTest {
+ "{enu:shortEnum(MyEnum:ON)}::{enu:shortEnum(MyEnum:OFF)}::{enu:shortEnum(MyEnum:UNDEFINED)}::"
+ "{enu:foo(MyEnum:ON)}::{enu:foo(MyEnum:OFF)}::{enu:foo(MyEnum:UNDEFINED)}::"
+ "{enu:locFileOverride(MyEnum:ON)}::{enu:locFileOverride(MyEnum:OFF)}::{enu:locFileOverride(MyEnum:UNDEFINED)}"),
- "templates/foo.html"));
+ "templates/foo.html")
+ .addAsResource(new StringAsset(
+ "{enu:underscored(UnderscoredEnum:A_B)}::{enu:underscored(UnderscoredEnum:FOO_BAR_BAZ)}::{enu:underscored_foo(AnotherUnderscoredEnum:NEXT_B)}::{enu:underscored$foo(AnotherUnderscoredEnum:NEXT_B)}::{enu:uncommon(UncommonEnum:NEXT$B)}"),
+ "templates/bar.html"));
@Inject
Template foo;
+ @Inject
+ Template bar;
+
@Test
public void testMessages() {
assertEquals("On::Off::Undefined::1::0::U::+::-::_::on::off::undefined", foo.render());
assertEquals("Zapnuto::Vypnuto::Nedefinováno::1::0::N::+::-::_::zap::vyp::nedef",
foo.instance().setLocale("cs").render());
+ assertEquals("A/B::Foo/Bar/Baz::NEXT::NEXT::NEXT", bar.render());
+ }
+
+ @TemplateEnum
+ public enum UnderscoredEnum {
+ A_B,
+ FOO_BAR_BAZ
+ }
+
+ @TemplateEnum
+ public enum AnotherUnderscoredEnum {
+ NEXT_B
+ }
+
+ @TemplateEnum
+ public enum UncommonEnum {
+ NEXT$B
}
@MessageBundle(value = "enu", locale = "en")
@@ -69,6 +93,22 @@ public interface Messages {
@Message
String locFileOverride(MyEnum myEnum);
+ // maps to underscored_$A_B, underscored_$FOO_BAR_BAZ
+ @Message
+ String underscored(UnderscoredEnum val);
+
+ // maps to underscored_foo_$NEXT_B
+ @Message
+ String underscored_foo(AnotherUnderscoredEnum val);
+
+ // maps to underscored$foo_$NEXT_B
+ @Message
+ String underscored$foo(AnotherUnderscoredEnum val);
+
+ // maps to uncommon_$NEXT$B
+ @Message
+ String uncommon(UncommonEnum val);
+
}
}
diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleInvalidEnumConstantTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleInvalidEnumConstantTest.java
new file mode 100644
index 0000000000000..cd771a4929dfb
--- /dev/null
+++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/MessageBundleInvalidEnumConstantTest.java
@@ -0,0 +1,43 @@
+package io.quarkus.qute.deployment.i18n;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.qute.TemplateEnum;
+import io.quarkus.qute.deployment.MessageBundleException;
+import io.quarkus.qute.i18n.Message;
+import io.quarkus.qute.i18n.MessageBundle;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class MessageBundleInvalidEnumConstantTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .withApplicationRoot(root -> root
+ .addClasses(Messages.class, UnderscoredEnum.class)
+ .addAsResource("messages/enu_invalid.properties"))
+ .setExpectedException(MessageBundleException.class, true);
+
+ @Test
+ public void testMessages() {
+ fail();
+ }
+
+ @TemplateEnum
+ public enum UnderscoredEnum {
+
+ A_B,
+
+ }
+
+ @MessageBundle(value = "enu_invalid")
+ public interface Messages {
+
+ @Message
+ String underscored(UnderscoredEnum constants);
+
+ }
+
+}
diff --git a/extensions/qute/deployment/src/test/resources/messages/enu.properties b/extensions/qute/deployment/src/test/resources/messages/enu.properties
index 072f933eb0881..ac9aa52409737 100644
--- a/extensions/qute/deployment/src/test/resources/messages/enu.properties
+++ b/extensions/qute/deployment/src/test/resources/messages/enu.properties
@@ -10,4 +10,13 @@ locFileOverride={#when myEnum}\
{#is ON}on\
{#is OFF}off\
{#else}undefined\
- {/when}
\ No newline at end of file
+ {/when}
+
+underscored_$A_B=A/B
+underscored_$FOO_BAR_BAZ=Foo/Bar/Baz
+
+underscored_foo_$NEXT_B=NEXT
+
+underscored$foo_$NEXT_B=NEXT
+
+uncommon_$NEXT$B=NEXT
\ No newline at end of file
diff --git a/extensions/qute/deployment/src/test/resources/messages/enu_cs.properties b/extensions/qute/deployment/src/test/resources/messages/enu_cs.properties
index e3f5c0a2ae6de..8f15c483934fc 100644
--- a/extensions/qute/deployment/src/test/resources/messages/enu_cs.properties
+++ b/extensions/qute/deployment/src/test/resources/messages/enu_cs.properties
@@ -10,4 +10,13 @@ locFileOverride={#when myEnum}\
{#is ON}zap\
{#is OFF}vyp\
{#else}nedef\
- {/when}
\ No newline at end of file
+ {/when}
+
+underscored_$A_B=A/B
+underscored_$FOO_BAR_BAZ=Foo/Bar/Baz
+
+underscored_foo_$NEXT_B=NEXT
+
+underscored$foo_$NEXT_B=NEXT
+
+uncommon_$NEXT$B=NEXT
\ No newline at end of file
diff --git a/extensions/qute/deployment/src/test/resources/messages/enu_invalid.properties b/extensions/qute/deployment/src/test/resources/messages/enu_invalid.properties
new file mode 100644
index 0000000000000..79e8a9324eb4d
--- /dev/null
+++ b/extensions/qute/deployment/src/test/resources/messages/enu_invalid.properties
@@ -0,0 +1,2 @@
+underscored_$A_B=A/B
+underscored_$FOO_BAR_BAZ=Foo/Bar/Baz
\ No newline at end of file
diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java
index b8b8a43ae5955..6f858af9e6a91 100644
--- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java
+++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/Message.java
@@ -27,7 +27,7 @@
* There is a convenient way to localize enums.
*
* If there is a message bundle method that accepts a single parameter of an enum type and has no message template defined then
- * it receives a generated template:
+ * it receives a generated template like:
*
*
* {#when enumParamName}
@@ -37,14 +37,20 @@
*
*
* Furthermore, a special message method is generated for each enum constant. Finally, each localized file must contain keys and
- * values for all constant message keys:
+ * values for all enum constants.
*
*
* methodName_CONSTANT1=Value 1
* methodName_CONSTANT2=Value 2
*
*
- * In a template, an enum constant can be localized with a message bundle method {@code msg:methodName(enumConstant)}.
+ * By default, the message key consists of the method name followed by the {@code _} separator and the constant name. If any
+ * constant name of a particular enum contains the {@code _} or the {@code $} character then the {@code _$} separator must be
+ * used for all message keys for this enum instead. For example, {@code methodName_$CONSTANT_1=Value 1} or
+ * {@code methodName_$CONSTANT$1=Value 1}.
+ *
+ * In a template, the localized message for an enum constant can be obtained with a message bundle method like
+ * {@code msg:methodName(enumConstant)}.
*
* @see MessageBundle
*/
From cd842d3c52c6a6d9bb6f3f92ae751777c9c32ee4 Mon Sep 17 00:00:00 2001
From: Michael Edgar
Date: Tue, 3 Dec 2024 06:30:06 -0500
Subject: [PATCH 3/3] Bump smallrye-open-api.version from 4.0.3 to 4.0.4
Signed-off-by: Michael Edgar
(cherry picked from commit bdf48fb1b0a75bba154ea6f3269d54f5c54763f6)
---
bom/application/pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index ca6e24b5fcfc0..00427268f969b 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -51,7 +51,7 @@
3.10.2
4.1.0
4.0.0
- 4.0.3
+ 4.0.4
2.11.0
6.6.3
4.6.1