From ec6e5be7d27515ba4dbddac03216bf4d6633db96 Mon Sep 17 00:00:00 2001 From: Alexander Haslam Date: Thu, 21 Aug 2025 17:15:46 -0700 Subject: [PATCH] Feature/open id connect (#1) Fixes: https://github.com/quarkiverse/quarkus-openapi-generator/issues/1227 Add to the template and codegen to look for and use the openIdConnectMethods Add Integration tests to verify support --- .../wrapper/QuarkusJavaClientCodegen.java | 3 ++- .../auth/compositeAuthenticationProvider.qute | 3 +++ .../it/auth/TokenServerResource.java | 10 ++++++++++ .../main/openapi/token-external-service6.yaml | 20 +++++++++++++++++++ .../src/main/resources/application.properties | 13 ++++++++++++ .../it/auth/TokenExternalServicesMock.java | 5 +++++ ...TokenWithCustomCredentialProviderTest.java | 2 +- .../it/security/TokenPropagationResource.java | 9 +++++++++ .../token-propagation-external-service6.yaml | 20 +++++++++++++++++++ .../src/main/resources/application.properties | 12 +++++++++++ .../TokenPropagationExternalServicesMock.java | 5 +++++ 11 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 client/integration-tests/auth-provider/src/main/openapi/token-external-service6.yaml create mode 100644 client/integration-tests/security/src/main/openapi/token-propagation-external-service6.yaml diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java index b7daaa74a..24808c323 100644 --- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java +++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java @@ -77,7 +77,8 @@ private void replaceWithQuarkusTemplateFiles() { if (ProcessUtils.hasHttpBasicMethods(this.openAPI) || ProcessUtils.hasApiKeyMethods(this.openAPI) || ProcessUtils.hasHttpBearerMethods(this.openAPI) || - ProcessUtils.hasOAuthMethods(this.openAPI)) { + ProcessUtils.hasOAuthMethods(this.openAPI) || + ProcessUtils.hasOpenIdConnectMethods(this.openAPI)) { supportingFiles.add( new SupportingFile(AUTH_PACKAGE + "/compositeAuthenticationProvider.qute", authFileFolder(), diff --git a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute index 297ce5f34..29b73b8cc 100644 --- a/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute +++ b/client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute @@ -4,6 +4,9 @@ package {apiPackage}.auth; {#for auth in openapi:getUniqueOAuthOperations(oauthMethods.orEmpty)} @io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}") {/for} +{#for auth in openapi:getUniqueOAuthOperations(openIdConnectMethods.orEmpty)} +@io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}") +{/for} {#for auth in httpBasicMethods.orEmpty} @io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}") {/for} diff --git a/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/TokenServerResource.java b/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/TokenServerResource.java index 9c3ae7e57..99d2a4a87 100644 --- a/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/TokenServerResource.java +++ b/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/TokenServerResource.java @@ -20,6 +20,9 @@ public class TokenServerResource { @RestClient org.acme.externalservice5.api.DefaultApi defaultApi5; + @RestClient + org.acme.externalservice6.api.DefaultApi defaultApi6; + @POST @Path("service1") public String service1() { @@ -47,4 +50,11 @@ public String service5() { defaultApi5.executeQuery5(); return "hello"; } + + @POST + @Path("service6") + public String service6() { + defaultApi6.executeQuery6(); + return "hello"; + } } diff --git a/client/integration-tests/auth-provider/src/main/openapi/token-external-service6.yaml b/client/integration-tests/auth-provider/src/main/openapi/token-external-service6.yaml new file mode 100644 index 000000000..464861e36 --- /dev/null +++ b/client/integration-tests/auth-provider/src/main/openapi/token-external-service6.yaml @@ -0,0 +1,20 @@ +--- +openapi: 3.0.3 +info: + title: token-external-service6 API + version: 3.0.0-SNAPSHOT +paths: + /token-external-service6/executeQuery6: + post: + operationId: executeQuery6 + responses: + "200": + description: OK + security: + - service6-oidc: [] +components: + securitySchemes: + service6-oidc: + type: openIdConnect + description: Authentication for service6 + openIdConnectUrl: https://example.com/realms/master/.well-known/openid-configuration \ No newline at end of file diff --git a/client/integration-tests/auth-provider/src/main/resources/application.properties b/client/integration-tests/auth-provider/src/main/resources/application.properties index eeed16b8a..42d594673 100644 --- a/client/integration-tests/auth-provider/src/main/resources/application.properties +++ b/client/integration-tests/auth-provider/src/main/resources/application.properties @@ -6,11 +6,13 @@ quarkus.openapi-generator.codegen.spec.token_external_service1_yaml.base-package quarkus.openapi-generator.codegen.spec.token_external_service2_yaml.base-package=org.acme.externalservice2 quarkus.openapi-generator.codegen.spec.token_external_service3_yaml.base-package=org.acme.externalservice3 quarkus.openapi-generator.codegen.spec.token_external_service5_yaml.base-package=org.acme.externalservice5 +quarkus.openapi-generator.codegen.spec.token_external_service6_yaml.base-package=org.acme.externalservice6 quarkus.rest-client.token_external_service1_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_external_service2_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_external_service3_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_external_service5_yaml.url=${propagation-external-service-mock.url} +quarkus.rest-client.token_external_service6_yaml.url=${propagation-external-service-mock.url} # default propagation for token_external_service1 invocation quarkus.openapi-generator.token_external_service1_yaml.auth.service1_http_bearer.token-propagation=true @@ -39,4 +41,15 @@ quarkus.oidc-client.service5_oauth2.grant.type=client quarkus.oidc-client.service5_oauth2.credentials.client-secret.method=basic quarkus.oidc-client.service5_oauth2.credentials.client-secret.value=secret + +# Oidc client used by the token_external_service6 +quarkus.oidc-client.service6_oidc.auth-server-url=${keycloak.mock.service.url} +quarkus.oidc-client.service6_oidc.token-path=${keycloak.mock.service.token-path} +quarkus.oidc-client.service6_oidc.discovery-enabled=false +quarkus.oidc-client.service6_oidc.client-id=kogito-app +quarkus.oidc-client.service6_oidc.grant.type=client +quarkus.oidc-client.service6_oidc.credentials.client-secret.method=basic +quarkus.oidc-client.service6_oidc.credentials.client-secret.value=secret + + quarkus.keycloak.devservices.enabled=false \ No newline at end of file diff --git a/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenExternalServicesMock.java b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenExternalServicesMock.java index 7a4545218..48f62372d 100644 --- a/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenExternalServicesMock.java +++ b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenExternalServicesMock.java @@ -60,6 +60,11 @@ public Map start() { // configured. The token will be overridden by the custom credential provider stubForExternalService("/token-external-service5/executeQuery5", KEYCLOAK_ACCESS_TOKEN + "_TEST"); + // stub the token-external-service6 invocation with the expected token, no propagation is produced + // in this case but the service must receive the token provided by Keycloak since it has oidc security + // configured. The token will be overridden by the custom credential provider + stubForExternalService("/token-external-service6/executeQuery6", KEYCLOAK_ACCESS_TOKEN + "_TEST"); + return Map.of(TOKEN_EXTERNAL_SERVICE_MOCK_URL, wireMockServer.baseUrl()); } diff --git a/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenWithCustomCredentialProviderTest.java b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenWithCustomCredentialProviderTest.java index 9fd525475..bbd9ebb97 100644 --- a/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenWithCustomCredentialProviderTest.java +++ b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenWithCustomCredentialProviderTest.java @@ -22,7 +22,7 @@ class TokenWithCustomCredentialProviderTest { @ParameterizedTest - @ValueSource(strings = { "service1", "service2", "service3", "service5" }) + @ValueSource(strings = { "service1", "service2", "service3", "service5", "service6" }) void testService(String service) { Map headers = Map.of(HttpHeaders.AUTHORIZATION, AUTHORIZATION_TOKEN); diff --git a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java index 4253357f3..0a9a1e3aa 100644 --- a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java +++ b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java @@ -24,6 +24,9 @@ public class TokenPropagationResource { @RestClient org.acme.externalservice5.api.DefaultApi defaultApi5; + @RestClient + org.acme.externalservice6.api.DefaultApi defaultApi6; + @POST @Path("service1") public Response service1() { @@ -53,4 +56,10 @@ public Response service4() { public Response service5() { return defaultApi5.executeQuery5(); } + + @POST + @Path("service6") + public Response service6() { + return defaultApi6.executeQuery6(); + } } diff --git a/client/integration-tests/security/src/main/openapi/token-propagation-external-service6.yaml b/client/integration-tests/security/src/main/openapi/token-propagation-external-service6.yaml new file mode 100644 index 000000000..cf2a83c6e --- /dev/null +++ b/client/integration-tests/security/src/main/openapi/token-propagation-external-service6.yaml @@ -0,0 +1,20 @@ +--- +openapi: 3.0.3 +info: + title: external-service6 API + version: 3.0.0-SNAPSHOT +paths: + /token-propagation-external-service6/executeQuery6: + post: + operationId: executeQuery6 + responses: + "200": + description: OK + security: + - service6-oidc: [] +components: + securitySchemes: + service6-oidc: + type: openIdConnect + description: Authentication for service6 + openIdConnectUrl: https://example.com/realms/master/.well-known/openid-configuration diff --git a/client/integration-tests/security/src/main/resources/application.properties b/client/integration-tests/security/src/main/resources/application.properties index e4bb70b7a..1e32e3b14 100644 --- a/client/integration-tests/security/src/main/resources/application.properties +++ b/client/integration-tests/security/src/main/resources/application.properties @@ -32,12 +32,15 @@ quarkus.openapi-generator.codegen.spec.token_propagation_external_service2_yaml. quarkus.openapi-generator.codegen.spec.token_propagation_external_service3_yaml.base-package=org.acme.externalservice3 quarkus.openapi-generator.codegen.spec.token_propagation_external_service4_yaml.base-package=org.acme.externalservice4 quarkus.openapi-generator.codegen.spec.token_propagation_external_service5_yaml.base-package=org.acme.externalservice5 +quarkus.openapi-generator.codegen.spec.token_propagation_external_service6_yaml.base-package=org.acme.externalservice6 quarkus.rest-client.token_propagation_external_service1_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service2_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service3_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service4_yaml.url=${propagation-external-service-mock.url} quarkus.rest-client.token_propagation_external_service5_yaml.url=${propagation-external-service-mock.url} +quarkus.rest-client.token_propagation_external_service6_yaml.url=${propagation-external-service-mock.url} + # default propagation for token_propagation_external_service1 invocation quarkus.openapi-generator.token_propagation_external_service1_yaml.auth.service1_http_bearer.token-propagation=true @@ -78,6 +81,15 @@ quarkus.oidc-client.service5_oauth2.grant.type=client quarkus.oidc-client.service5_oauth2.credentials.client-secret.method=basic quarkus.oidc-client.service5_oauth2.credentials.client-secret.value=secret +# Oidc client used by the token_propagation_external_service6 +quarkus.oidc-client.service6_oidc.auth-server-url=${keycloak.mock.service.url} +quarkus.oidc-client.service6_oidc.token-path=${keycloak.mock.service.token-path} +quarkus.oidc-client.service6_oidc.discovery-enabled=false +quarkus.oidc-client.service6_oidc.client-id=kogito-app +quarkus.oidc-client.service6_oidc.grant.type=client +quarkus.oidc-client.service6_oidc.credentials.client-secret.method=basic +quarkus.oidc-client.service6_oidc.credentials.client-secret.value=secret + quarkus.keycloak.devservices.enabled=false # Slack OpenAPI diff --git a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java index 53ecbf2ac..62e6863be 100644 --- a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java +++ b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java @@ -66,6 +66,11 @@ public Map start() { // configured. stubForExternalService("/token-propagation-external-service5/executeQuery5", KEYCLOAK_ACCESS_TOKEN); + // stub the token-propagation-external-service6 invocation with the expected token, no propagation is produced + // in this case but the service must receive the token provided by Keycloak since it has oidc security + // configured. + stubForExternalService("/token-propagation-external-service6/executeQuery6", KEYCLOAK_ACCESS_TOKEN); + return Map.of(TOKEN_PROPAGATION_EXTERNAL_SERVICE_MOCK_URL, wireMockServer.baseUrl()); }