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 cabb361ee..4253357f3 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 @@ -2,6 +2,7 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.rest.client.inject.RestClient; @@ -25,36 +26,31 @@ public class TokenPropagationResource { @POST @Path("service1") - public String service1() { - defaultApi1.executeQuery1(); - return "hello"; + public Response service1() { + return defaultApi1.executeQuery1(); } @POST @Path("service2") - public String service2() { - defaultApi2.executeQuery2(); - return "hello"; + public Response service2() { + return defaultApi2.executeQuery2(); } @POST @Path("service3") - public String service3() { - defaultApi3.executeQuery3(); - return "hello"; + public Response service3() { + return defaultApi3.executeQuery3(); } @POST @Path("service4") - public String service4() { - defaultApi4.executeQuery4(); - return "hello"; + public Response service4() { + return defaultApi4.executeQuery4(); } @POST @Path("service5") - public String service5() { - defaultApi5.executeQuery5(); - return "hello"; + public Response service5() { + return defaultApi5.executeQuery5(); } } 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 43b6289e3..53ecbf2ac 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 @@ -23,7 +23,8 @@ public class TokenPropagationExternalServicesMock implements QuarkusTestResourceLifecycleManager { - public static final String AUTHORIZATION_TOKEN = "AUTHORIZATION_TOKEN"; + public static final String SERVICE1_AUTHORIZATION_TOKEN = "SERVICE1_AUTHORIZATION_TOKEN"; + public static final String SERVICE2_AUTHORIZATION_TOKEN = "SERVICE2_AUTHORIZATION_TOKEN"; public static final String SERVICE3_HEADER_TO_PROPAGATE = "SERVICE3_HEADER_TO_PROPAGATE"; public static final String SERVICE3_AUTHORIZATION_TOKEN = "SERVICE3_AUTHORIZATION_TOKEN"; public static final String SERVICE4_HEADER_TO_PROPAGATE = "SERVICE4_HEADER_TO_PROPAGATE"; @@ -49,10 +50,10 @@ public Map start() { LOGGER.info("Mocked Server started at {}", wireMockServer.baseUrl()); // stub the token-propagation-external-service1 invocation with the expected token - stubForExternalService("/token-propagation-external-service1/executeQuery1", AUTHORIZATION_TOKEN); + stubForExternalService("/token-propagation-external-service1/executeQuery1", SERVICE1_AUTHORIZATION_TOKEN); // stub the token-propagation-external-service2 invocation with the expected token - stubForExternalService("/token-propagation-external-service2/executeQuery2", AUTHORIZATION_TOKEN); + stubForExternalService("/token-propagation-external-service2/executeQuery2", SERVICE2_AUTHORIZATION_TOKEN); // stub the token-propagation-external-service3 invocation with the expected token stubForExternalService("/token-propagation-external-service3/executeQuery3", SERVICE3_AUTHORIZATION_TOKEN); diff --git a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java index 544e360d3..a131c53e5 100644 --- a/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java +++ b/client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationTest.java @@ -1,20 +1,25 @@ package io.quarkiverse.openapi.generator.it.security; -import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.AUTHORIZATION_TOKEN; +import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE1_AUTHORIZATION_TOKEN; +import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE2_AUTHORIZATION_TOKEN; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE3_AUTHORIZATION_TOKEN; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE3_HEADER_TO_PROPAGATE; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_AUTHORIZATION_TOKEN; import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_HEADER_TO_PROPAGATE; import static io.restassured.RestAssured.given; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Stream; 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 org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @@ -26,21 +31,40 @@ @Tag("resteasy-classic") class TokenPropagationTest { - @ParameterizedTest - @ValueSource(strings = { "service1", "service2", "service3", "service4", "service5" }) - void service1(String service) { - Map headers = new HashMap<>(); - // service token-propagation-external-service1 and token-propagation-external-service2 will receive the AUTHORIZATION_TOKEN - headers.put(HttpHeaders.AUTHORIZATION, AUTHORIZATION_TOKEN); - // service token-propagation-external-service3 will receive the SERVICE3_AUTHORIZATION_TOKEN - headers.put(SERVICE3_HEADER_TO_PROPAGATE, SERVICE3_AUTHORIZATION_TOKEN); - // service token-propagation-external-service4 will receive the SERVICE4_AUTHORIZATION_TOKEN - headers.put(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN); + private static class HeaderArgument { + String headerName; + String headerValue; + + HeaderArgument(String headerName, String headerValue) { + this.headerName = headerName; + this.headerValue = headerValue; + } + + } + @ParameterizedTest + @MethodSource("serviceInvocationParams") + void invokeService(String service, HeaderArgument headerArgument) { + Map> headers = new HashMap<>(); + headers.put(headerArgument.headerName, Collections.singletonList(headerArgument.headerValue)); given() .headers(headers) .post("/token_propagation/" + service) .then() .statusCode(200); } + + private static Stream serviceInvocationParams() { + return Stream.of( + // service token-propagation-external-service1 will receive the SERVICE1_AUTHORIZATION_TOKEN + Arguments.of("service1", new HeaderArgument(HttpHeaders.AUTHORIZATION, SERVICE1_AUTHORIZATION_TOKEN)), + // service token-propagation-external-service2 will receive the SERVICE2_AUTHORIZATION_TOKEN + Arguments.of("service2", new HeaderArgument(HttpHeaders.AUTHORIZATION, SERVICE2_AUTHORIZATION_TOKEN)), + // service token-propagation-external-service3 will receive the SERVICE3_AUTHORIZATION_TOKEN + Arguments.of("service3", new HeaderArgument(SERVICE3_HEADER_TO_PROPAGATE, SERVICE3_AUTHORIZATION_TOKEN)), + // service token-propagation-external-service4 will receive the SERVICE4_AUTHORIZATION_TOKEN + Arguments.of("service4", new HeaderArgument(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN)) + + ); + } } 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 8ae3a59b3..1e18caebc 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 @@ -6,7 +6,6 @@ import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; -import jakarta.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,10 +45,14 @@ public void filter(ClientRequestContext requestContext) throws IOException { .build()); } - if (bearerToken != null && !bearerToken.isBlank()) { - requestContext.getHeaders().remove(HttpHeaders.AUTHORIZATION); - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, + if (!isEmptyOrBlank(bearerToken)) { + addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.authTokenOrBearer("Bearer", AbstractAuthProvider.sanitizeBearerToken(bearerToken))); + } else { + LOGGER.debug("No bearer token was found for the oauth2 security scheme: {}." + + " You must verify that a Quarkus OIDC Client with the name: {} is properly configured," + + " or the request header: {} is set when the token propagation is enabled.", + getName(), getName(), getHeaderForPropagation(getOpenApiSpecId(), getName())); } } 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 d38a0d6fb..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 @@ -4,6 +4,7 @@ import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -65,16 +66,20 @@ public boolean isTokenPropagation() { public static String getTokenForPropagation(MultivaluedMap httpHeaders, String openApiSpecId, String authName) { - String headerName = getHeaderName(openApiSpecId, authName) != null ? getHeaderName(openApiSpecId, authName) - : HttpHeaders.AUTHORIZATION; + String headerName = getHeaderForPropagation(openApiSpecId, authName); String propagatedHeaderName = propagationHeaderName(openApiSpecId, authName, headerName); - return Objects.toString(httpHeaders.getFirst(propagatedHeaderName)); + return Objects.toString(httpHeaders.getFirst(propagatedHeaderName), null); } 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() { return ConfigProvider.getConfig() .getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME), String.class).orElse(null); @@ -110,4 +115,12 @@ public static boolean isTokenPropagation(String openApiSpecId, String authName) public CredentialsProvider getCredentialsProvider() { return credentialsProvider; } + + protected void addAuthorizationHeader(MultivaluedMap headers, String value) { + headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(value)); + } + + protected static boolean isEmptyOrBlank(String value) { + return value == null || value.isBlank(); + } } 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 9085e207a..6b25671c5 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 @@ -1,10 +1,12 @@ package io.quarkiverse.openapi.generator.providers; +import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.PASSWORD; +import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.USER_NAME; + import java.io.IOException; import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; -import jakarta.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,10 +50,14 @@ public void filter(ClientRequestContext requestContext) throws IOException { basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders())); } - if (!basicToken.isBlank()) { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, - AuthUtils.basicAuthAccessToken(basicToken)); + if (!isEmptyOrBlank(basicToken)) { + addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.basicAuthAccessToken(basicToken)); + } else { + LOGGER.debug("No basic authentication token was found for the security scheme: {}." + + " 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(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 035f49399..12a1e9af4 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 @@ -1,10 +1,11 @@ package io.quarkiverse.openapi.generator.providers; +import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.BEARER_TOKEN; + import java.io.IOException; import java.util.List; import jakarta.ws.rs.client.ClientRequestContext; -import jakarta.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +35,13 @@ public void filter(ClientRequestContext requestContext) throws IOException { bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders())); } - if (!bearerToken.isBlank()) { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken)); + if (!isEmptyOrBlank(bearerToken)) { + addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.authTokenOrBearer(this.scheme, bearerToken)); + } else { + 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(getOpenApiSpecId(), getName())); } LOGGER.debug("Header keys set in filtered requestContext: {}", requestContext.getHeaders().keySet()); }