diff --git a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java
index 6538f9ce4..fa9e3faa9 100644
--- a/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java
+++ b/client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/GeneratorProcessor.java
@@ -174,6 +174,7 @@ void produceOauthAuthentication(CombinedIndexBuildItem beanArchiveBuildItem,
.annotation(OpenApiSpec.class)
.addValue("openApiSpecId", openApiSpecId)
.done()
+ .addInjectionPoint(ClassType.create(DotName.createSimple(CredentialsProvider.class)))
.addInjectionPoint(ClassType.create(OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate.class),
AnnotationInstance.builder(OidcClient.class).add("name", sanitizeAuthName(name)).build())
.addInjectionPoint(ClassType.create(DotName.createSimple(CredentialsProvider.class)))
diff --git a/client/integration-tests/auth-provider/pom.xml b/client/integration-tests/auth-provider/pom.xml
new file mode 100644
index 000000000..84a493979
--- /dev/null
+++ b/client/integration-tests/auth-provider/pom.xml
@@ -0,0 +1,124 @@
+
+
+
+ quarkus-openapi-generator-integration-tests
+ io.quarkiverse.openapi.generator
+ 3.0.0-lts-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-openapi-generator-it-auth-provider
+ Quarkus - OpenAPI Generator - Integration Tests - Client - Auth Provider
+ A few use cases that relies on authentication provider use cases with the OpenAPI Generator
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator-oidc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.wiremock
+ wiremock
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+
+
+
+ native-image
+
+
+ native
+
+
+
+
+
+ maven-surefire-plugin
+
+ ${native.surefire.skip}
+
+
+
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+ native
+
+
+
+ resteasy-reactive
+
+
+ io.quarkus
+ quarkus-rest-client-oidc-filter
+
+
+
+
+ resteasy-classic
+
+ true
+
+
+
+ io.quarkus
+ quarkus-resteasy-client-oidc-filter
+
+
+ io.quarkus
+ quarkus-resteasy-multipart
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 000000000..9c3ae7e57
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/TokenServerResource.java
@@ -0,0 +1,50 @@
+package io.quarkiverse.openapi.generator.it.auth;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@Path("/token_server")
+public class TokenServerResource {
+
+ @RestClient
+ org.acme.externalservice1.api.DefaultApi defaultApi1;
+
+ @RestClient
+ org.acme.externalservice2.api.DefaultApi defaultApi2;
+
+ @RestClient
+ org.acme.externalservice3.api.DefaultApi defaultApi3;
+
+ @RestClient
+ org.acme.externalservice5.api.DefaultApi defaultApi5;
+
+ @POST
+ @Path("service1")
+ public String service1() {
+ defaultApi1.executeQuery1();
+ return "hello";
+ }
+
+ @POST
+ @Path("service2")
+ public String service2() {
+ defaultApi2.executeQuery2();
+ return "hello";
+ }
+
+ @POST
+ @Path("service3")
+ public String service3() {
+ defaultApi3.executeQuery3();
+ return "hello";
+ }
+
+ @POST
+ @Path("service5")
+ public String service5() {
+ defaultApi5.executeQuery5();
+ return "hello";
+ }
+}
diff --git a/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/provider/CustomCredentialsProvider.java b/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/provider/CustomCredentialsProvider.java
new file mode 100644
index 000000000..b9022e801
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/java/io/quarkiverse/openapi/generator/it/auth/provider/CustomCredentialsProvider.java
@@ -0,0 +1,30 @@
+package io.quarkiverse.openapi.generator.it.auth.provider;
+
+import java.util.Optional;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.Dependent;
+import jakarta.enterprise.inject.Alternative;
+import jakarta.enterprise.inject.Specializes;
+
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkiverse.openapi.generator.providers.CredentialsContext;
+
+@Dependent
+@Alternative
+@Specializes
+@Priority(201)
+public class CustomCredentialsProvider extends ConfigCredentialsProvider {
+ public CustomCredentialsProvider() {
+ }
+
+ @Override
+ public Optional getBearerToken(CredentialsContext input) {
+ return Optional.of("BEARER_TOKEN_TEST");
+ }
+
+ @Override
+ public Optional getOauth2BearerToken(CredentialsContext input) {
+ return Optional.of("KEYCLOAK_ACCESS_TOKEN_TEST");
+ }
+}
diff --git a/client/integration-tests/auth-provider/src/main/openapi/token-external-service1.yaml b/client/integration-tests/auth-provider/src/main/openapi/token-external-service1.yaml
new file mode 100644
index 000000000..dd18964c1
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/openapi/token-external-service1.yaml
@@ -0,0 +1,19 @@
+---
+openapi: 3.0.3
+info:
+ title: token-external-service1 API
+ version: 3.0.0-SNAPSHOT
+paths:
+ /token-external-service1/executeQuery1:
+ post:
+ operationId: executeQuery1
+ responses:
+ "200":
+ description: OK
+ security:
+ - service1-http-bearer: []
+components:
+ securitySchemes:
+ service1-http-bearer:
+ type: http
+ scheme: bearer
\ No newline at end of file
diff --git a/client/integration-tests/auth-provider/src/main/openapi/token-external-service2.yaml b/client/integration-tests/auth-provider/src/main/openapi/token-external-service2.yaml
new file mode 100644
index 000000000..726200831
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/openapi/token-external-service2.yaml
@@ -0,0 +1,23 @@
+---
+openapi: 3.0.3
+info:
+ title: token-external-service2 API
+ version: 3.0.0-SNAPSHOT
+paths:
+ /token-external-service2/executeQuery2:
+ post:
+ operationId: executeQuery2
+ responses:
+ "200":
+ description: OK
+ security:
+ - service2-oauth2: []
+components:
+ securitySchemes:
+ service2-oauth2:
+ type: oauth2
+ flows:
+ clientCredentials:
+ authorizationUrl: https://example.com/oauth
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
\ No newline at end of file
diff --git a/client/integration-tests/auth-provider/src/main/openapi/token-external-service3.yaml b/client/integration-tests/auth-provider/src/main/openapi/token-external-service3.yaml
new file mode 100644
index 000000000..981daef3c
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/openapi/token-external-service3.yaml
@@ -0,0 +1,19 @@
+---
+openapi: 3.0.3
+info:
+ title: token-external-service3 API
+ version: 3.0.0-SNAPSHOT
+paths:
+ /token-external-service3/executeQuery3:
+ post:
+ operationId: executeQuery3
+ responses:
+ "200":
+ description: OK
+ security:
+ - service3-http-bearer: []
+components:
+ securitySchemes:
+ service3-http-bearer:
+ type: http
+ scheme: bearer
\ No newline at end of file
diff --git a/client/integration-tests/auth-provider/src/main/openapi/token-external-service5.yaml b/client/integration-tests/auth-provider/src/main/openapi/token-external-service5.yaml
new file mode 100644
index 000000000..4490dcf00
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/openapi/token-external-service5.yaml
@@ -0,0 +1,23 @@
+---
+openapi: 3.0.3
+info:
+ title: token-external-service5 API
+ version: 3.0.0-SNAPSHOT
+paths:
+ /token-external-service5/executeQuery5:
+ post:
+ operationId: executeQuery5
+ responses:
+ "200":
+ description: OK
+ security:
+ - service5-oauth2: []
+components:
+ securitySchemes:
+ service5-oauth2:
+ type: oauth2
+ flows:
+ clientCredentials:
+ authorizationUrl: https://example.com/oauth
+ tokenUrl: https://example.com/oauth/token
+ scopes: {}
\ 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
new file mode 100644
index 000000000..eeed16b8a
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/main/resources/application.properties
@@ -0,0 +1,42 @@
+# Note: The property value is the name of an existing securityScheme in the spec file
+quarkus.openapi-generator.codegen.default-security-scheme=app_id
+
+#Token service
+quarkus.openapi-generator.codegen.spec.token_external_service1_yaml.base-package=org.acme.externalservice1
+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.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}
+
+# default propagation for token_external_service1 invocation
+quarkus.openapi-generator.token_external_service1_yaml.auth.service1_http_bearer.token-propagation=true
+# default propagation for token_external_service2 invocation
+quarkus.openapi-generator.token_external_service2_yaml.auth.service2_oauth2.token-propagation=true
+
+quarkus.openapi-generator.token_external_service3_yaml.auth.service3_http_bearer.bearer-token=BEARER_TOKEN
+
+# Oidc clients for the services that has oauth2 security.
+# Oidc client used by the token_external_service2
+quarkus.oidc-client.service2_oauth2.auth-server-url=${keycloak.mock.service.url}
+quarkus.oidc-client.service2_oauth2.token-path=${keycloak.mock.service.token-path}
+quarkus.oidc-client.service2_oauth2.discovery-enabled=false
+quarkus.oidc-client.service2_oauth2.client-id=kogito-app
+quarkus.oidc-client.service2_oauth2.grant.type=client
+quarkus.oidc-client.service2_oauth2.credentials.client-secret.method=basic
+quarkus.oidc-client.service2_oauth2.credentials.client-secret.value=secret
+
+
+# Oidc client used by the token_external_service5
+quarkus.oidc-client.service5_oauth2.auth-server-url=${keycloak.mock.service.url}
+quarkus.oidc-client.service5_oauth2.token-path=${keycloak.mock.service.token-path}
+quarkus.oidc-client.service5_oauth2.discovery-enabled=false
+quarkus.oidc-client.service5_oauth2.client-id=kogito-app
+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
+
+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/KeycloakServiceMock.java b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/KeycloakServiceMock.java
new file mode 100644
index 000000000..477d7d253
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/KeycloakServiceMock.java
@@ -0,0 +1,82 @@
+package io.quarkiverse.openapi.generator.it.auth;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+
+/**
+ * Lightweight Keycloak mock to use when an OidcClient is required, and we don't want/need to start a full Keycloak
+ * container as part of the tests, etc. Keep the things simple.
+ */
+public class KeycloakServiceMock implements QuarkusTestResourceLifecycleManager {
+
+ public static final String KEY_CLOAK_SERVICE_URL = "keycloak.mock.service.url";
+ public static final String KEY_CLOAK_SERVICE_TOKEN_PATH = "keycloak.mock.service.token-path";
+ public static final String REALM = "kogito-tests";
+ public static final String KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE = "/realms/" + REALM + "/protocol/openid-connect/token";
+ public static final String CLIENT_ID = "kogito-app";
+ public static final String SECRET = "secret";
+ public static final String KEYCLOAK_ACCESS_TOKEN = "KEYCLOAK_ACCESS_TOKEN";
+ public static final String KEYCLOAK_REFRESH_TOKEN = "KEYCLOAK_REFRESH_TOKEN";
+ public static final String KEYCLOAK_SESSION_STATE = "KEYCLOAK_SESSION_STATE";
+
+ public static final String AUTH_REQUEST_BODY = "grant_type=client_credentials";
+
+ private static final ThreadLocal wireMockServer = new ThreadLocal<>();
+
+ @Override
+ public Map start() {
+ wireMockServer.set(new WireMockServer(options().dynamicPort()));
+ wireMockServer.get().start();
+ configureFor(wireMockServer.get().port());
+
+ stubFor(post(KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE)
+ .withHeader(CONTENT_TYPE, equalTo(APPLICATION_FORM_URLENCODED))
+ .withBasicAuth(CLIENT_ID, SECRET)
+ .withRequestBody(equalTo(AUTH_REQUEST_BODY))
+ .willReturn(aResponse()
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)
+ .withBody(getTokenResult())));
+
+ Map properties = new HashMap<>();
+ properties.put(KEY_CLOAK_SERVICE_URL, wireMockServer.get().baseUrl());
+ properties.put(KEY_CLOAK_SERVICE_TOKEN_PATH, KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE);
+ return properties;
+ }
+
+ private static String getTokenResult() {
+ return """
+ {
+ "access_token": "%s",
+ "expires_in": 300,
+ "refresh_expires_in": 1800,
+ "refresh_token": "%s",
+ "token_type": "bearer",
+ "not-before-policy": 0,
+ "session_state": "%s",
+ "scope": "email profile"
+ }
+ """.formatted(KEYCLOAK_ACCESS_TOKEN, KEYCLOAK_REFRESH_TOKEN, KEYCLOAK_SESSION_STATE);
+ }
+
+ @Override
+ public void stop() {
+ if (wireMockServer.get() != null) {
+ wireMockServer.get().stop();
+ wireMockServer.remove();
+ }
+ }
+}
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
new file mode 100644
index 000000000..7a4545218
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenExternalServicesMock.java
@@ -0,0 +1,72 @@
+package io.quarkiverse.openapi.generator.it.auth;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static io.quarkiverse.openapi.generator.it.auth.KeycloakServiceMock.KEYCLOAK_ACCESS_TOKEN;
+import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.Map;
+
+import jakarta.ws.rs.core.HttpHeaders;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+
+public class TokenExternalServicesMock implements QuarkusTestResourceLifecycleManager {
+
+ public static final String AUTHORIZATION_TOKEN = "AUTHORIZATION_TOKEN";
+ public static final String SERVICE3_AUTHORIZATION_TOKEN = "BEARER_TOKEN";
+ public static final String TOKEN_EXTERNAL_SERVICE_MOCK_URL = "propagation-external-service-mock.url";
+ private static final String BEARER = "Bearer ";
+ private static final Logger LOGGER = LoggerFactory.getLogger(TokenExternalServicesMock.class);
+ private WireMockServer wireMockServer;
+
+ private static void stubForExternalService(String tokenPropagationExternalServiceUrl, String authorizationToken) {
+ stubFor(post(tokenPropagationExternalServiceUrl)
+ .withHeader(HttpHeaders.AUTHORIZATION, equalTo(BEARER + authorizationToken))
+ .willReturn(aResponse()
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)
+ .withBody("{}")));
+ }
+
+ @Override
+ public Map start() {
+ wireMockServer = new WireMockServer(options().dynamicPort());
+ wireMockServer.start();
+ configureFor(wireMockServer.port());
+ LOGGER.info("Mocked Server started at {}", wireMockServer.baseUrl());
+
+ // stub the token-external-service1 invocation with the expected token
+ stubForExternalService("/token-external-service1/executeQuery1", AUTHORIZATION_TOKEN);
+
+ // stub the token-external-service2 invocation with the expected token
+ stubForExternalService("/token-external-service2/executeQuery2", AUTHORIZATION_TOKEN);
+
+ // stub the token-external-service3 invocation with the expected token taken from the
+ // application.properties and overridden by the custom credential provider
+ stubForExternalService("/token-external-service3/executeQuery3", SERVICE3_AUTHORIZATION_TOKEN + "_TEST");
+
+ // stub the token-external-service5 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 oauth2 security
+ // configured. The token will be overridden by the custom credential provider
+ stubForExternalService("/token-external-service5/executeQuery5", KEYCLOAK_ACCESS_TOKEN + "_TEST");
+
+ return Map.of(TOKEN_EXTERNAL_SERVICE_MOCK_URL, wireMockServer.baseUrl());
+ }
+
+ @Override
+ public void stop() {
+ if (wireMockServer != null) {
+ wireMockServer.stop();
+ }
+ }
+}
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
new file mode 100644
index 000000000..9fd525475
--- /dev/null
+++ b/client/integration-tests/auth-provider/src/test/java/io/quarkiverse/openapi/generator/it/auth/TokenWithCustomCredentialProviderTest.java
@@ -0,0 +1,35 @@
+package io.quarkiverse.openapi.generator.it.auth;
+
+import static io.quarkiverse.openapi.generator.it.auth.TokenExternalServicesMock.AUTHORIZATION_TOKEN;
+import static io.restassured.RestAssured.given;
+
+import java.util.Map;
+
+import jakarta.ws.rs.core.HttpHeaders;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTestResource(TokenExternalServicesMock.class)
+@QuarkusTestResource(KeycloakServiceMock.class)
+@QuarkusTest
+// Enabled only for RESTEasy Classic while https://github.com/quarkiverse/quarkus-openapi-generator/issues/434 is not fixed
+@Tag("resteasy-classic")
+class TokenWithCustomCredentialProviderTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = { "service1", "service2", "service3", "service5" })
+ void testService(String service) {
+ Map headers = Map.of(HttpHeaders.AUTHORIZATION, AUTHORIZATION_TOKEN);
+
+ given()
+ .headers(headers)
+ .post("/token_server/" + service)
+ .then()
+ .statusCode(200);
+ }
+}
diff --git a/client/integration-tests/override-credential-provider/src/main/java/io/quarkiverse/openapi/generator/it/creds/CustomCredentialsProvider.java b/client/integration-tests/override-credential-provider/src/main/java/io/quarkiverse/openapi/generator/it/creds/CustomCredentialsProvider.java
index 0ac9e2282..00a445982 100644
--- a/client/integration-tests/override-credential-provider/src/main/java/io/quarkiverse/openapi/generator/it/creds/CustomCredentialsProvider.java
+++ b/client/integration-tests/override-credential-provider/src/main/java/io/quarkiverse/openapi/generator/it/creds/CustomCredentialsProvider.java
@@ -1,14 +1,16 @@
package io.quarkiverse.openapi.generator.it.creds;
+import java.util.Optional;
+
import jakarta.annotation.Priority;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Alternative;
-import jakarta.ws.rs.client.ClientRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkiverse.openapi.generator.providers.CredentialsContext;
@Dependent
@Alternative
@@ -19,8 +21,8 @@ public class CustomCredentialsProvider extends ConfigCredentialsProvider {
public static String TOKEN = "FIXED_TEST_TOKEN";
@Override
- public String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName) {
+ public Optional getBearerToken(CredentialsContext input) {
LOGGER.info("========> getBearerToken from CustomCredentialsProvider");
- return TOKEN;
+ return Optional.of(TOKEN);
}
-}
\ No newline at end of file
+}
diff --git a/client/integration-tests/pom.xml b/client/integration-tests/pom.xml
index 362a3a38c..661f2ec90 100644
--- a/client/integration-tests/pom.xml
+++ b/client/integration-tests/pom.xml
@@ -12,6 +12,7 @@
pom
additional-properties
+ auth-provider
array-enum
bean-validation
beanparam
diff --git a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java
index 1096cca18..2d60e0e44 100644
--- a/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java
+++ b/client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/auth/DummyApiKeyAuthenticationProvider.java
@@ -12,6 +12,7 @@
import io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider;
import io.quarkiverse.openapi.generator.providers.ApiKeyIn;
import io.quarkiverse.openapi.generator.providers.AuthProvider;
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
@Priority(Priorities.AUTHENTICATION)
public class DummyApiKeyAuthenticationProvider implements ClientRequestFilter {
@@ -21,7 +22,7 @@ public class DummyApiKeyAuthenticationProvider implements ClientRequestFilter {
@PostConstruct
public void init() {
authProvider = new ApiKeyAuthenticationProvider("open_weather_custom_security_yaml", "app_id", ApiKeyIn.query, "appid",
- List.of());
+ List.of(), new ConfigCredentialsProvider());
}
@Override
diff --git a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/KeycloakServiceMock.java b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/KeycloakServiceMock.java
index 06fd6aa57..264b34bea 100644
--- a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/KeycloakServiceMock.java
+++ b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/KeycloakServiceMock.java
@@ -35,13 +35,13 @@ public class KeycloakServiceMock implements QuarkusTestResourceLifecycleManager
public static final String AUTH_REQUEST_BODY = "grant_type=client_credentials";
- private WireMockServer wireMockServer;
+ private static final ThreadLocal wireMockServer = new ThreadLocal<>();
@Override
public Map start() {
- wireMockServer = new WireMockServer(options().dynamicPort());
- wireMockServer.start();
- configureFor(wireMockServer.port());
+ wireMockServer.set(new WireMockServer(options().dynamicPort()));
+ wireMockServer.get().start();
+ configureFor(wireMockServer.get().port());
stubFor(post(KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE)
.withHeader(CONTENT_TYPE, equalTo(APPLICATION_FORM_URLENCODED))
@@ -52,28 +52,31 @@ public Map start() {
.withBody(getTokenResult())));
Map properties = new HashMap<>();
- properties.put(KEY_CLOAK_SERVICE_URL, wireMockServer.baseUrl());
+ properties.put(KEY_CLOAK_SERVICE_URL, wireMockServer.get().baseUrl());
properties.put(KEY_CLOAK_SERVICE_TOKEN_PATH, KEY_CLOAK_SERVICE_TOKEN_PATH_VALUE);
return properties;
}
private static String getTokenResult() {
- return "{\n" +
- " \"access_token\": \"" + KEYCLOAK_ACCESS_TOKEN + "\",\n" +
- " \"expires_in\": 300,\n" +
- " \"refresh_expires_in\": 1800,\n" +
- " \"refresh_token\": \"" + KEYCLOAK_REFRESH_TOKEN + "\",\n" +
- " \"token_type\": \"bearer\",\n" +
- " \"not-before-policy\": 0,\n" +
- " \"session_state\": \"" + KEYCLOAK_SESSION_STATE + "\",\n" +
- " \"scope\": \"email profile\"\n" +
- "}";
+ return """
+ {
+ "access_token": "%s",
+ "expires_in": 300,
+ "refresh_expires_in": 1800,
+ "refresh_token": "%s",
+ "token_type": "bearer",
+ "not-before-policy": 0,
+ "session_state": "%s",
+ "scope": "email profile"
+ }
+ """.formatted(KEYCLOAK_ACCESS_TOKEN, KEYCLOAK_REFRESH_TOKEN, KEYCLOAK_SESSION_STATE);
}
@Override
public void stop() {
- if (wireMockServer != null) {
- wireMockServer.stop();
+ if (wireMockServer.get() != null) {
+ wireMockServer.get().stop();
+ wireMockServer.remove();
}
}
}
diff --git a/client/oidc/pom.xml b/client/oidc/pom.xml
index 078a5ea01..3da32f6bb 100644
--- a/client/oidc/pom.xml
+++ b/client/oidc/pom.xml
@@ -27,6 +27,31 @@
quarkus-resteasy-client-oidc-filter
provided
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.jboss.resteasy
+ resteasy-core
+ test
+
\ No newline at end of file
diff --git a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/ClassicOidcClientRequestFilterDelegate.java b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/ClassicOidcClientRequestFilterDelegate.java
index f37895c8a..677ee5b48 100644
--- a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/ClassicOidcClientRequestFilterDelegate.java
+++ b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/ClassicOidcClientRequestFilterDelegate.java
@@ -7,6 +7,7 @@
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
+import jakarta.ws.rs.core.HttpHeaders;
import org.jboss.logging.Logger;
@@ -41,7 +42,7 @@ protected java.util.Optional clientId() {
public void filter(ClientRequestContext requestContext) throws IOException {
try {
String accessToken = this.getAccessToken();
- requestContext.getHeaders().add("Authorization", "Bearer " + accessToken);
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
} catch (DisabledOidcClientException ex) {
LOG.debug("Client is disabled, acquiring and propagating the token is not necessary");
} catch (RuntimeException ex) {
diff --git a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java
index 4b5a9ea39..676f77876 100644
--- a/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java
+++ b/client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java
@@ -4,16 +4,19 @@
import java.io.IOException;
import java.util.List;
+import java.util.Optional;
import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.core.HttpHeaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkiverse.openapi.generator.providers.AbstractAuthProvider;
+import io.quarkiverse.openapi.generator.providers.AuthUtils;
+import io.quarkiverse.openapi.generator.providers.CredentialsContext;
import io.quarkiverse.openapi.generator.providers.CredentialsProvider;
import io.quarkiverse.openapi.generator.providers.OperationAuthInfo;
-import io.quarkus.oidc.common.runtime.OidcConstants;
public class OAuth2AuthenticationProvider extends AbstractAuthProvider {
@@ -31,16 +34,38 @@ public OAuth2AuthenticationProvider(String name,
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
- if (isTokenPropagation()) {
- String bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders()));
- if (!isEmptyOrBlank(bearerToken)) {
- addAuthorizationHeader(requestContext.getHeaders(), OidcConstants.BEARER_SCHEME + " " + bearerToken);
- } else {
- LOGGER.debug("No oauth2 bearer token was found to propagate for the security scheme: {}." +
- " You must verify that the request header: {} is set.", getName(), getHeaderForPropagation());
+ String bearerToken = "";
+
+ if (this.isTokenPropagation()) {
+ bearerToken = this.getTokenForPropagation(requestContext.getHeaders());
+ if (isEmptyOrBlank(bearerToken)) {
+ LOGGER.debug(
+ "Token propagation for OAUTH2 is enabled but the configured propagation header defined by {} is not present",
+ getHeaderForPropagation(getOpenApiSpecId(), getName()));
}
} else {
- delegate.filter(requestContext);
+ Optional optionalBearerToken = this.getCredentialsProvider()
+ .getOauth2BearerToken(CredentialsContext.builder()
+ .requestContext(requestContext)
+ .openApiSpecId(getOpenApiSpecId())
+ .authName(getName())
+ .build());
+ if (optionalBearerToken.isPresent()) {
+ bearerToken = optionalBearerToken.get();
+ if (isEmptyOrBlank(bearerToken)) {
+ LOGGER.debug("The CredentialProvider implementation returned an empty OAUTH2 bearer");
+ }
+ } else {
+ LOGGER.debug(
+ "There is no custom CredentialProvider implementation, the {} header will be set using delegate's filter. ",
+ HttpHeaders.AUTHORIZATION);
+ delegate.filter(requestContext);
+ }
+ }
+
+ if (!isEmptyOrBlank(bearerToken)) {
+ addAuthorizationHeader(requestContext.getHeaders(),
+ AuthUtils.authTokenOrBearer("Bearer", AbstractAuthProvider.sanitizeBearerToken(bearerToken)));
}
}
@@ -55,4 +80,4 @@ private void validateConfig() {
public interface OidcClientRequestFilterDelegate {
void filter(ClientRequestContext requestContext) throws IOException;
}
-}
+}
\ No newline at end of file
diff --git a/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/OAuth2AuthenticationProviderTest.java b/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/OAuth2AuthenticationProviderTest.java
new file mode 100644
index 000000000..aa54b54b7
--- /dev/null
+++ b/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/OAuth2AuthenticationProviderTest.java
@@ -0,0 +1,126 @@
+package io.quarkiverse.openapi.generator.oidc;
+
+import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+import org.assertj.core.api.Assertions;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import io.quarkiverse.openapi.generator.AuthConfig;
+import io.quarkiverse.openapi.generator.oidc.providers.OAuth2AuthenticationProvider;
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkus.oidc.client.Tokens;
+
+@ExtendWith(MockitoExtension.class)
+public class OAuth2AuthenticationProviderTest {
+ private static final String OPEN_API_FILE_SPEC_ID = "open_api_file_spec_id_json";
+ private static final String AUTH_SCHEME_NAME = "auth_scheme_name";
+
+ private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
+
+ private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
+
+ private static final String HEADER_NAME = "HEADER_NAME";
+
+ @Mock
+ private ClientRequestContext requestContext;
+
+ private MultivaluedMap headers;
+
+ private ClassicOidcClientRequestFilterDelegate classicDelegate;
+
+ private static final Tokens token = new Tokens(ACCESS_TOKEN, Long.MAX_VALUE, null, "", Long.MAX_VALUE, null, "");
+
+ private OAuth2AuthenticationProvider provider;
+
+ @BeforeEach
+ void setUp() {
+ headers = new MultivaluedHashMap<>();
+ Mockito.lenient().doReturn(headers).when(requestContext).getHeaders();
+
+ classicDelegate = Mockito.mock(ClassicOidcClientRequestFilterDelegate.class);
+ Mockito.lenient().when(classicDelegate.awaitTokens()).thenReturn(token);
+ try {
+ Mockito.lenient().doCallRealMethod().when(classicDelegate).filter(requestContext);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ provider = createClassicProvider();
+
+ }
+
+ private OAuth2AuthenticationProvider createClassicProvider() {
+ return new OAuth2AuthenticationProvider(AUTH_SCHEME_NAME, OPEN_API_FILE_SPEC_ID, classicDelegate, List.of(),
+ new ConfigCredentialsProvider());
+ }
+
+ private void assertHeader(MultivaluedMap headers, String headerName, String value) {
+ Assertions.assertThat(headers.getFirst(headerName))
+ .isNotNull()
+ .isEqualTo(value);
+ }
+
+ static Stream filterWithPropagationTestValues() {
+ return Stream.of(
+ Arguments.of(null, "Bearer " + PROPAGATED_TOKEN),
+ Arguments.of(HEADER_NAME, "Bearer " + PROPAGATED_TOKEN));
+ }
+
+ @Test
+ void filterClassic() throws IOException {
+ filter(provider, "Bearer " + ACCESS_TOKEN);
+ }
+
+ private void filter(OAuth2AuthenticationProvider provider, String expectedAuthorizationHeader) throws IOException {
+ provider.filter(requestContext);
+ assertHeader(headers, HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);
+ }
+
+ @ParameterizedTest
+ @MethodSource("filterWithPropagationTestValues")
+ void filterWithPropagation(String headerName,
+ String expectedAuthorizationHeader) throws IOException {
+ String propagatedHeaderName;
+ if (headerName == null) {
+ propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
+ HttpHeaders.AUTHORIZATION);
+ } else {
+ propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
+ HEADER_NAME);
+ }
+ try (MockedStatic configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) {
+ Config mockedConfig = Mockito.mock(Config.class);
+ configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig);
+
+ when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION),
+ Boolean.class)).thenReturn(Optional.of(true));
+ when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME),
+ String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName));
+
+ headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
+ filter(provider, expectedAuthorizationHeader);
+ }
+ }
+
+}
diff --git a/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/ReactiveOAuth2AuthenticationProviderTest.java b/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/ReactiveOAuth2AuthenticationProviderTest.java
new file mode 100644
index 000000000..a450f3cb2
--- /dev/null
+++ b/client/oidc/src/test/java/io/quarkiverse/openapi/generator/oidc/ReactiveOAuth2AuthenticationProviderTest.java
@@ -0,0 +1,138 @@
+package io.quarkiverse.openapi.generator.oidc;
+
+import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+
+import org.assertj.core.api.Assertions;
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl;
+import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import io.quarkiverse.openapi.generator.AuthConfig;
+import io.quarkiverse.openapi.generator.oidc.providers.OAuth2AuthenticationProvider;
+import io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider;
+import io.quarkus.oidc.client.Tokens;
+import io.smallrye.mutiny.Uni;
+
+@ExtendWith(MockitoExtension.class)
+public class ReactiveOAuth2AuthenticationProviderTest {
+ private static final String OPEN_API_FILE_SPEC_ID = "open_api_file_spec_id_json";
+ private static final String AUTH_SCHEME_NAME = "auth_scheme_name";
+
+ private static final String ACCESS_TOKEN = "REACTIVE_ACCESS_TOKEN";
+
+ private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
+
+ private static final String HEADER_NAME = "HEADER_NAME";
+ @Mock
+ private ClientRequestContextImpl reactiveRequestContext;
+
+ @Mock
+ private RestClientRequestContext restClientRequestContext;
+ private MultivaluedMap headers;
+
+ private ReactiveOidcClientRequestFilterDelegate reactiveDelegate;
+ private static final Tokens token = new Tokens(ACCESS_TOKEN, Long.MAX_VALUE, null, "", Long.MAX_VALUE, null, "");
+ private static final Uni uniToken = Uni.createFrom().item(token);
+
+ private OAuth2AuthenticationProvider provider;
+
+ @BeforeEach
+ void setUp() {
+ headers = new MultivaluedHashMap<>();
+ Mockito.lenient().doReturn(headers).when(reactiveRequestContext).getHeaders();
+ Mockito.lenient().doReturn(restClientRequestContext).when(reactiveRequestContext).getRestClientRequestContext();
+ Mockito.lenient().doAnswer(invocationOnMock -> restClientRequestContext.setSuspended(true))
+ .when(restClientRequestContext).suspend();
+ Mockito.lenient().doAnswer(invocationOnMock -> restClientRequestContext.setSuspended(false))
+ .when(restClientRequestContext).resume();
+ reactiveDelegate = Mockito.mock(ReactiveOidcClientRequestFilterDelegate.class);
+ try {
+ Mockito.lenient().doCallRealMethod().when(reactiveDelegate).filter(Mockito.any(ClientRequestContext.class));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Mockito.lenient().doCallRealMethod().when(reactiveDelegate).filter(reactiveRequestContext);
+ Mockito.lenient().when(reactiveDelegate.getTokens()).thenReturn(uniToken);
+
+ provider = createReactiveProvider();
+ }
+
+ protected OAuth2AuthenticationProvider createReactiveProvider() {
+ return new OAuth2AuthenticationProvider(AUTH_SCHEME_NAME, OPEN_API_FILE_SPEC_ID, reactiveDelegate, List.of(),
+ new ConfigCredentialsProvider());
+ }
+
+ protected void assertHeader(MultivaluedMap headers, String headerName, String value) {
+ Assertions.assertThat(headers.getFirst(headerName))
+ .isNotNull()
+ .isEqualTo(value);
+ }
+
+ static Stream filterWithPropagationTestValues() {
+ return Stream.of(
+ Arguments.of(null, "Bearer " + PROPAGATED_TOKEN),
+ Arguments.of(HEADER_NAME, "Bearer " + PROPAGATED_TOKEN));
+ }
+
+ @Test
+ void filterReactive() throws IOException, InterruptedException {
+ filter(provider, "Bearer " + ACCESS_TOKEN);
+ }
+
+ private void filter(OAuth2AuthenticationProvider provider, String expectedAuthorizationHeader)
+ throws IOException, InterruptedException {
+ provider.filter(reactiveRequestContext);
+ while (reactiveRequestContext.getRestClientRequestContext().isSuspended()) {
+ Thread.sleep(1000);
+ }
+ assertHeader(headers, HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);
+ }
+
+ @ParameterizedTest
+ @MethodSource("filterWithPropagationTestValues")
+ void filterWithPropagation(String headerName,
+ String expectedAuthorizationHeader) throws IOException, InterruptedException {
+ String propagatedHeaderName;
+ if (headerName == null) {
+ propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
+ HttpHeaders.AUTHORIZATION);
+ } else {
+ propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
+ HEADER_NAME);
+ }
+ try (MockedStatic configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) {
+ Config mockedConfig = Mockito.mock(Config.class);
+ configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig);
+
+ when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION),
+ Boolean.class)).thenReturn(Optional.of(true));
+ when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME),
+ String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName));
+
+ headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
+ filter(provider, expectedAuthorizationHeader);
+ }
+ }
+}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java
index ff738a16e..cc4acd5d9 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java
@@ -61,18 +61,23 @@ public String getName() {
}
public boolean isTokenPropagation() {
- return ConfigProvider.getConfig()
- .getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION), Boolean.class)
- .orElse(false);
+ return isTokenPropagation(getOpenApiSpecId(), getName());
}
- public String getTokenForPropagation(MultivaluedMap httpHeaders) {
- String propagatedHeaderName = propagationHeaderName(getOpenApiSpecId(), getName(), getHeaderForPropagation());
+ public static String getTokenForPropagation(MultivaluedMap httpHeaders, String openApiSpecId,
+ String authName) {
+ String headerName = getHeaderForPropagation(openApiSpecId, authName);
+ String propagatedHeaderName = propagationHeaderName(openApiSpecId, authName, headerName);
return Objects.toString(httpHeaders.getFirst(propagatedHeaderName), null);
}
- public String getHeaderForPropagation() {
- return getHeaderName() != null ? getHeaderName() : HttpHeaders.AUTHORIZATION;
+ public String getTokenForPropagation(MultivaluedMap httpHeaders) {
+ return getTokenForPropagation(httpHeaders, getOpenApiSpecId(), getName());
+ }
+
+ public static String getHeaderForPropagation(String openApiSpecId, String authName) {
+ return getHeaderName(openApiSpecId, authName) != null ? getHeaderName(openApiSpecId, authName)
+ : HttpHeaders.AUTHORIZATION;
}
public String getHeaderName() {
@@ -80,6 +85,13 @@ public String getHeaderName() {
.getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME), String.class).orElse(null);
}
+ public static String getHeaderName(String openApiSpecId, String authName) {
+ return ConfigProvider.getConfig()
+ .getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME, openApiSpecId, authName),
+ String.class)
+ .orElse(null);
+ }
+
@Override
public List operationsToFilter() {
return applyToOperations;
@@ -93,6 +105,17 @@ public static String getCanonicalAuthConfigPropertyName(String authPropertyName,
return String.format(CANONICAL_AUTH_CONFIG_PROPERTY_NAME, openApiSpecId, authName, authPropertyName);
}
+ public static boolean isTokenPropagation(String openApiSpecId, String authName) {
+ return ConfigProvider.getConfig()
+ .getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION, openApiSpecId, authName),
+ Boolean.class)
+ .orElse(false);
+ }
+
+ public CredentialsProvider getCredentialsProvider() {
+ return credentialsProvider;
+ }
+
protected void addAuthorizationHeader(MultivaluedMap headers, String value) {
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(value));
}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java
index 2f9c1a04e..069c7cffb 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthenticationPropagationHeadersFactory.java
@@ -7,6 +7,8 @@
import jakarta.ws.rs.core.MultivaluedMap;
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig;
@@ -23,6 +25,8 @@ public abstract class AbstractAuthenticationPropagationHeadersFactory implements
protected OpenApiGeneratorConfig generatorConfig;
protected HeadersProvider headersProvider;
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAuthenticationPropagationHeadersFactory.class);
+
protected AbstractAuthenticationPropagationHeadersFactory(BaseCompositeAuthenticationProvider compositeProvider,
OpenApiGeneratorConfig generatorConfig,
HeadersProvider headersProvider) {
@@ -36,6 +40,11 @@ public MultivaluedMap update(MultivaluedMap inco
MultivaluedMap clientOutgoingHeaders) {
MultivaluedMap propagatedHeaders = new MultivaluedHashMap<>();
MultivaluedMap providedHeaders = headersProvider.getStringHeaders(generatorConfig);
+
+ LOGGER.debug("Incoming headers keys{}", incomingHeaders.keySet());
+ LOGGER.debug("Outgoing headers keys{}", clientOutgoingHeaders.keySet());
+ LOGGER.debug("Provided headers keys{}", providedHeaders.keySet());
+
compositeProvider.getAuthenticationProviders().stream()
.filter(AbstractAuthProvider.class::isInstance)
.map(AbstractAuthProvider.class::cast)
@@ -46,9 +55,14 @@ public MultivaluedMap update(MultivaluedMap inco
// get priority to the headers coming from the headers provider.
List headerValue = providedHeaders.get(headerName);
if (headerValue == null) {
- // lastly look into the incoming headers.
+ // next, look into the incoming headers.
headerValue = incomingHeaders.get(headerName);
}
+ if (headerValue == null) {
+ // lastly look into the outgoing headers.
+ headerValue = clientOutgoingHeaders.get(headerName);
+ }
+
if (headerValue != null) {
propagatedHeaders.put(propagationHeaderName(authProvider.getOpenApiSpecId(),
authProvider.getName(),
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java
index 2f3db8393..d3833089f 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java
@@ -11,6 +11,8 @@
import jakarta.ws.rs.core.UriBuilder;
import org.eclipse.microprofile.config.ConfigProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
@@ -23,6 +25,8 @@ public class ApiKeyAuthenticationProvider extends AbstractAuthProvider {
private final ApiKeyIn apiKeyIn;
private final String apiKeyName;
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyAuthenticationProvider.class);
+
public ApiKeyAuthenticationProvider(final String openApiSpecId, final String name, final ApiKeyIn apiKeyIn,
final String apiKeyName, List operations, CredentialsProvider credentialsProvider) {
super(name, openApiSpecId, operations, credentialsProvider);
@@ -31,11 +35,6 @@ public ApiKeyAuthenticationProvider(final String openApiSpecId, final String nam
validateConfig();
}
- public ApiKeyAuthenticationProvider(final String openApiSpecId, final String name, final ApiKeyIn apiKeyIn,
- final String apiKeyName, List operations) {
- this(openApiSpecId, name, apiKeyIn, apiKeyName, operations, new ConfigCredentialsProvider());
- }
-
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
switch (apiKeyIn) {
@@ -59,7 +58,20 @@ && isUseAuthorizationHeaderValue()) {
}
private String getApiKey(ClientRequestContext requestContext) {
- return credentialsProvider.getApiKey(requestContext, getOpenApiSpecId(), getName());
+ final String key = credentialsProvider.getApiKey(CredentialsContext.builder()
+ .requestContext(requestContext)
+ .openApiSpecId(getOpenApiSpecId())
+ .authName(getName())
+ .build()).orElse("");
+
+ if (key.isEmpty()) {
+ LOGGER.warn("configured {} property (see application.properties) is empty. hint: configure it.",
+ AbstractAuthProvider.getCanonicalAuthConfigPropertyName(ConfigCredentialsProvider.API_KEY,
+ getOpenApiSpecId(),
+ getName()));
+ }
+
+ return key;
}
private boolean isUseAuthorizationHeaderValue() {
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java
index 343b2e18d..f39622bff 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BasicAuthenticationProvider.java
@@ -24,16 +24,20 @@ public BasicAuthenticationProvider(final String openApiSpecId, String name, List
super(name, openApiSpecId, operations, credentialsProvider);
}
- public BasicAuthenticationProvider(final String openApiSpecId, String name, List operations) {
- this(openApiSpecId, name, operations, new ConfigCredentialsProvider());
- }
-
private String getUsername(ClientRequestContext requestContext) {
- return credentialsProvider.getBasicUsername(requestContext, getOpenApiSpecId(), getName());
+ return credentialsProvider.getBasicUsername(CredentialsContext.builder()
+ .requestContext(requestContext)
+ .openApiSpecId(getOpenApiSpecId())
+ .authName(getName())
+ .build()).orElse("");
}
private String getPassword(ClientRequestContext requestContext) {
- return credentialsProvider.getBasicPassword(requestContext, getOpenApiSpecId(), getName());
+ return credentialsProvider.getBasicPassword(CredentialsContext.builder()
+ .requestContext(requestContext)
+ .openApiSpecId(getOpenApiSpecId())
+ .authName(getName())
+ .build()).orElse("");
}
@Override
@@ -53,7 +57,7 @@ public void filter(ClientRequestContext requestContext) throws IOException {
" You must verify that the properties: {} and {} are properly configured, or the request header: {} is set when the token propagation is enabled.",
getName(), getCanonicalAuthConfigPropertyName(USER_NAME, getOpenApiSpecId(), getName()),
getCanonicalAuthConfigPropertyName(PASSWORD, getOpenApiSpecId(), getName()),
- getHeaderForPropagation());
+ getHeaderForPropagation(getOpenApiSpecId(), getName()));
}
}
}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java
index d5dae2a74..91742cc16 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/BearerAuthenticationProvider.java
@@ -17,9 +17,8 @@
*/
public class BearerAuthenticationProvider extends AbstractAuthProvider {
- private static final Logger LOGGER = LoggerFactory.getLogger(BearerAuthenticationProvider.class);
-
private final String scheme;
+ private static final Logger LOGGER = LoggerFactory.getLogger(BearerAuthenticationProvider.class);
public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme,
List operations, CredentialsProvider credentialsProvider) {
@@ -27,13 +26,9 @@ public BearerAuthenticationProvider(final String openApiSpecId, final String nam
this.scheme = scheme;
}
- public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme,
- List operations) {
- this(openApiSpecId, name, scheme, operations, new ConfigCredentialsProvider());
- }
-
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
+ LOGGER.debug("Headers keys set in incoming requestContext: {}", requestContext.getHeaders().keySet());
String bearerToken = getBearerToken(requestContext);
if (isTokenPropagation()) {
@@ -46,11 +41,16 @@ public void filter(ClientRequestContext requestContext) throws IOException {
LOGGER.debug("No bearer token was found for the security scheme: {}." +
" You must verify that the property: {} is properly configured, or the request header: {} is set when the token propagation is enabled.",
getName(), getCanonicalAuthConfigPropertyName(BEARER_TOKEN, getOpenApiSpecId(), getName()),
- getHeaderForPropagation());
+ getHeaderForPropagation(getOpenApiSpecId(), getName()));
}
+ LOGGER.debug("Header keys set in filtered requestContext: {}", requestContext.getHeaders().keySet());
}
private String getBearerToken(ClientRequestContext requestContext) {
- return credentialsProvider.getBearerToken(requestContext, getOpenApiSpecId(), getName());
+ return credentialsProvider.getBearerToken(CredentialsContext.builder()
+ .requestContext(requestContext)
+ .openApiSpecId(getOpenApiSpecId())
+ .authName(getName())
+ .build()).orElse("");
}
}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java
index 8f78ee1e8..294017c97 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ConfigCredentialsProvider.java
@@ -1,13 +1,12 @@
package io.quarkiverse.openapi.generator.providers;
+import java.util.Optional;
+
import jakarta.annotation.Priority;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Alternative;
-import jakarta.ws.rs.client.ClientRequestContext;
import org.eclipse.microprofile.config.ConfigProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
@Dependent
@Alternative
@@ -19,48 +18,49 @@ public class ConfigCredentialsProvider implements CredentialsProvider {
static final String BEARER_TOKEN = "bearer-token";
static final String API_KEY = "api-key";
- private static final Logger LOGGER = LoggerFactory.getLogger(ConfigCredentialsProvider.class);
-
- public ConfigCredentialsProvider() {
-
- }
-
@Override
- public String getApiKey(ClientRequestContext requestContext, String openApiSpecId, String authName) {
- final String key = ConfigProvider.getConfig()
- .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(API_KEY, openApiSpecId, authName),
- String.class)
- .orElse("");
- if (key.isEmpty()) {
- LOGGER.warn("configured {} property (see application.properties) is empty. hint: configure it.",
- AbstractAuthProvider.getCanonicalAuthConfigPropertyName(API_KEY, openApiSpecId, authName));
- }
- return key;
+ public Optional getApiKey(CredentialsContext input) {
+ return ConfigProvider.getConfig()
+ .getOptionalValue(
+ AbstractAuthProvider.getCanonicalAuthConfigPropertyName(API_KEY, getConfigKey(input),
+ input.getAuthName()),
+ String.class);
+
}
@Override
- public String getBasicUsername(ClientRequestContext requestContext, String openApiSpecId, String authName) {
+ public Optional getBasicUsername(CredentialsContext input) {
return ConfigProvider.getConfig()
- .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(USER_NAME, openApiSpecId, authName),
- String.class)
- .orElse("");
+ .getOptionalValue(
+ AbstractAuthProvider.getCanonicalAuthConfigPropertyName(USER_NAME, getConfigKey(input),
+ input.getAuthName()),
+ String.class);
}
@Override
- public String getBasicPassword(ClientRequestContext requestContext, String openApiSpecId, String authName) {
+ public Optional getBasicPassword(CredentialsContext input) {
return ConfigProvider.getConfig()
- .getOptionalValue(AbstractAuthProvider.getCanonicalAuthConfigPropertyName(PASSWORD, openApiSpecId, authName),
- String.class)
- .orElse("");
+ .getOptionalValue(
+ AbstractAuthProvider.getCanonicalAuthConfigPropertyName(PASSWORD, getConfigKey(input),
+ input.getAuthName()),
+ String.class);
}
@Override
- public String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName) {
+ public Optional getBearerToken(CredentialsContext input) {
return ConfigProvider.getConfig()
.getOptionalValue(
- AbstractAuthProvider.getCanonicalAuthConfigPropertyName(BEARER_TOKEN, openApiSpecId, authName),
- String.class)
- .orElse("");
+ AbstractAuthProvider.getCanonicalAuthConfigPropertyName(BEARER_TOKEN, getConfigKey(input),
+ input.getAuthName()),
+ String.class);
}
+ protected String getConfigKey(CredentialsContext input) {
+ return input.getOpenApiSpecId();
+ }
+
+ @Override
+ public Optional getOauth2BearerToken(CredentialsContext input) {
+ return Optional.empty();
+ }
}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsContext.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsContext.java
new file mode 100644
index 000000000..3c47b0923
--- /dev/null
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsContext.java
@@ -0,0 +1,59 @@
+package io.quarkiverse.openapi.generator.providers;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+
+public class CredentialsContext {
+ // requestContext The current request context in which set the authorization header token
+ private ClientRequestContext requestContext;
+ // openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension
+ private String openApiSpecId;
+ // authName The security schema for this Bearer Token definition
+ private String authName;
+
+ public CredentialsContext(ClientRequestContext requestContext, String openApiSpecId, String authName) {
+ this.requestContext = requestContext;
+ this.openApiSpecId = openApiSpecId;
+ this.authName = authName;
+ }
+
+ public ClientRequestContext getRequestContext() {
+ return requestContext;
+ }
+
+ public String getOpenApiSpecId() {
+ return openApiSpecId;
+ }
+
+ public String getAuthName() {
+ return authName;
+ }
+
+ public static CredentialsContextBuilder builder() {
+ return new CredentialsContextBuilder();
+ }
+
+ public static class CredentialsContextBuilder {
+ private ClientRequestContext requestContext;
+ private String openApiSpecId;
+ private String authName;
+
+ public CredentialsContextBuilder requestContext(ClientRequestContext requestContext) {
+ this.requestContext = requestContext;
+ return this;
+ }
+
+ public CredentialsContextBuilder openApiSpecId(String openApiSpecId) {
+ this.openApiSpecId = openApiSpecId;
+ return this;
+ }
+
+ public CredentialsContextBuilder authName(String authName) {
+ this.authName = authName;
+ return this;
+ }
+
+ public CredentialsContext build() {
+ return new CredentialsContext(requestContext, openApiSpecId, authName);
+ }
+ }
+}
diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java
index 3f15dd67c..6d6f13a10 100644
--- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java
+++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/CredentialsProvider.java
@@ -1,6 +1,6 @@
package io.quarkiverse.openapi.generator.providers;
-import jakarta.ws.rs.client.ClientRequestContext;
+import java.util.Optional;
/**
* Provider for security credentials. Clients can implement this interface to control how to provide security credentials in
@@ -12,36 +12,40 @@ public interface CredentialsProvider {
/**
* Gets the API Key given the OpenAPI definition and security schema
*
- * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension
- * @param authName The security schema for this API Key definition
+ * @param input the input data available to the method
* @return the API Key to use when filtering the request
*/
- String getApiKey(ClientRequestContext requestContext, String openApiSpecId, String authName);
+ Optional getApiKey(CredentialsContext input);
/**
* Gets the username given the OpenAPI definition and security schema
*
- * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension
- * @param authName The security schema for this Basic Authorization definition
+ * @param input the input data available to the method
* @return the username to use when filtering the request
*/
- String getBasicUsername(ClientRequestContext requestContext, String openApiSpecId, String authName);
+ Optional getBasicUsername(CredentialsContext input);
/**
* Gets the password given the OpenAPI definition and security schema
*
- * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension
- * @param authName The security schema for this Basic Authorization definition
+ * @param input the input data available to the method
* @return the password to use when filtering the request
*/
- String getBasicPassword(ClientRequestContext requestContext, String openApiSpecId, String authName);
+ Optional getBasicPassword(CredentialsContext input);
/**
* Gets the Bearer Token given the OpenAPI definition and security schema
*
- * @param openApiSpecId the OpenAPI Spec identification as defined by the OpenAPI Extension
- * @param authName The security schema for this Bearer Token definition
+ * @param input the input data available to the method
* @return the Bearer Token to use when filtering the request
*/
- String getBearerToken(ClientRequestContext requestContext, String openApiSpecId, String authName);
+ Optional getBearerToken(CredentialsContext input);
+
+ /**
+ * Gets the OAuth2 Bearer Token given the OpenAPI definition and security schema
+ *
+ * @param input the input data available to the method
+ * @return the Bearer Token to use when filtering the request
+ */
+ Optional getOauth2BearerToken(CredentialsContext input);
}
diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java
index d6ffd6fab..d8060fb4f 100644
--- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java
+++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/ApiKeyOpenApiSpecProviderTest.java
@@ -42,7 +42,7 @@ class ApiKeyOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest headers = new MultivaluedTreeMap<>();
doReturn(headers).when(requestContext).getHeaders();
provider = new ApiKeyAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, ApiKeyIn.cookie, API_KEY_NAME,
- List.of());
+ List.of(), new ConfigCredentialsProvider());
provider.filter(requestContext);
final List