From a19fe9870318841e64ee69628b04a0bdc083ea14 Mon Sep 17 00:00:00 2001 From: gabriel-farache Date: Fri, 28 Mar 2025 15:44:04 +0100 Subject: [PATCH 1/3] Fix propagation test for bearer provider Signed-off-by: gabriel-farache --- .../BearerOpenApiSpecProviderTest.java | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java index 447c39f1c..11099b276 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java @@ -1,32 +1,42 @@ package io.quarkiverse.openapi.generator.providers; 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.core.HttpHeaders; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import io.quarkiverse.openapi.generator.AuthConfig; class BearerOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest { private static final String INCOMING_TOKEN = "INCOMING_TOKEN"; + private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN"; + private static final String BEARER_SCHEMA = "bearer"; private static final String CUSTOM_SCHEMA = "custom_scheme"; private static final String HEADER_NAME = "HEADER_NAME"; static Stream filterWithPropagationTestValues() { return Stream.of( - Arguments.of(null, "bearer", "Bearer " + INCOMING_TOKEN), - Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN), - Arguments.of(HEADER_NAME, "bearer", "Bearer " + INCOMING_TOKEN), - Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN)); + Arguments.of(null, "bearer", "Bearer " + PROPAGATED_TOKEN), + Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + PROPAGATED_TOKEN), + Arguments.of(HEADER_NAME, "bearer", "Bearer " + PROPAGATED_TOKEN), + Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + PROPAGATED_TOKEN)); } @Override @@ -70,7 +80,17 @@ void filterWithPropagation(String headerName, propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, HEADER_NAME); } - headers.putSingle(propagatedHeaderName, INCOMING_TOKEN); - filter(bearerScheme, expectedAuthorizationHeader); + 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(bearerScheme, expectedAuthorizationHeader); + } } } From 2be23678e3a1e5bf26b0e4ea48603c7f3059a634 Mon Sep 17 00:00:00 2001 From: gabriel-farache Date: Fri, 28 Mar 2025 16:33:14 +0100 Subject: [PATCH 2/3] Add suport for basic auth token propagation Signed-off-by: gabriel-farache --- .../providers/AbstractAuthProvider.java | 9 ++++ .../generator/providers/AuthUtils.java | 8 +++- .../BasicAuthenticationProvider.java | 26 +++++------ .../BasicOpenApiSpecProviderTest.java | 43 ++++++++++++++++--- .../authorization-token-propagation.adoc | 2 +- 5 files changed, 65 insertions(+), 23 deletions(-) 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 da4983d2e..d5a776fc1 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 @@ -19,6 +19,8 @@ public abstract class AbstractAuthProvider implements AuthProvider { CredentialsProvider credentialsProvider; private static final String BEARER_WITH_SPACE = "Bearer "; + private static final String BASIC_WITH_SPACE = "Basic "; + private static final String CANONICAL_AUTH_CONFIG_PROPERTY_NAME = "quarkus." + RUNTIME_TIME_CONFIG_PREFIX + ".%s.auth.%s.%s"; @@ -41,6 +43,13 @@ protected static String sanitizeBearerToken(String token) { return token; } + protected static String sanitizeBasicToken(String token) { + if (token != null && token.toLowerCase().startsWith(BASIC_WITH_SPACE.toLowerCase())) { + return token.substring(BASIC_WITH_SPACE.length()); + } + return token; + } + public String getOpenApiSpecId() { return openApiSpecId; } diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthUtils.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthUtils.java index 3baed3bca..2948e0108 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthUtils.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthUtils.java @@ -10,10 +10,14 @@ public final class AuthUtils { private AuthUtils() { } - public static String basicAuthAccessToken(final String username, final String password) { + public static String basicAuthAccessTokenWithoutPrefix(final String username, final String password) { + return Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes()); + } + + public static String basicAuthAccessToken(final String basicToken) { return String.format("%s %s", BASIC_HEADER_PREFIX, - Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes())); + basicToken); } public static String authTokenOrBearer(final String scheme, final String token) { 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 e2c8c19b8..842fd8512 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,7 +1,5 @@ package io.quarkiverse.openapi.generator.providers; -import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION; - import java.io.IOException; import java.util.List; @@ -9,6 +7,9 @@ import jakarta.ws.rs.core.HttpHeaders; import io.quarkiverse.openapi.generator.OpenApiGeneratorException; +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Provider for Basic Authentication. @@ -16,11 +17,11 @@ * during build time. */ public class BasicAuthenticationProvider extends AbstractAuthProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(BasicAuthenticationProvider.class); public BasicAuthenticationProvider(final String openApiSpecId, String name, List operations, CredentialsProvider credentialsProvider) { super(name, openApiSpecId, operations, credentialsProvider); - validateConfig(); } public BasicAuthenticationProvider(final String openApiSpecId, String name, List operations) { @@ -37,18 +38,17 @@ private String getPassword(ClientRequestContext requestContext) { @Override public void filter(ClientRequestContext requestContext) throws IOException { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, - AuthUtils.basicAuthAccessToken(getUsername(requestContext), getPassword(requestContext))); - } + String basicToken = AuthUtils.basicAuthAccessTokenWithoutPrefix(getUsername(requestContext), getPassword(requestContext)); - private void validateConfig() { if (isTokenPropagation()) { - throw new OpenApiGeneratorException( - "Token propagation is not admitted for the OpenApi securitySchemes of \"type\": \"http\", \"scheme\": \"basic\"." - + - " A potential source of the problem might be that the configuration property " + - getCanonicalAuthConfigPropertyName(TOKEN_PROPAGATION) + - " was set with the value true in your application, please check your configuration."); + LOGGER.warn("Token propagation enabled for BasicAuthentication"); + basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders())); } + + if (!basicToken.isBlank()) { + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, + AuthUtils.basicAuthAccessToken(basicToken)); + } + } } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java index 86716bb85..2b9d980fb 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java @@ -1,18 +1,22 @@ package io.quarkiverse.openapi.generator.providers; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.Base64; import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import jakarta.ws.rs.core.HttpHeaders; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -20,9 +24,13 @@ class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest { + private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN"; private static final String USER = "USER"; private static final String PASSWORD = "PASSWORD"; + private static final String CUSTOM_SCHEMA = "custom_scheme"; + private static final String HEADER_NAME = "HEADER_NAME"; + private static final String EXPECTED_BASIC_TOKEN = "Basic " + Base64.getEncoder().encodeToString((USER + ":" + PASSWORD).getBytes()); @@ -33,22 +41,43 @@ protected BasicAuthenticationProvider createProvider() { @Test void filter() throws IOException { + filter(EXPECTED_BASIC_TOKEN); + } + + private void filter(String expectedAuthorizationHeader) throws IOException { provider.filter(requestContext); - assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, EXPECTED_BASIC_TOKEN); + assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader); } - @Test - void tokenPropagationNotSupported() { + @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)); - assertThatThrownBy(() -> new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, List.of())) - .hasMessageContaining("quarkus.openapi-generator.%s.auth.%s.token-propagation", OPEN_API_FILE_SPEC_ID, - AUTH_SCHEME_NAME); + headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN); + filter(expectedAuthorizationHeader); } + } + static Stream filterWithPropagationTestValues() { + return Stream.of( + Arguments.of(null, "Basic " + PROPAGATED_TOKEN), + Arguments.of(HEADER_NAME, "Basic " + PROPAGATED_TOKEN)); } } diff --git a/docs/modules/ROOT/pages/includes/authorization-token-propagation.adoc b/docs/modules/ROOT/pages/includes/authorization-token-propagation.adoc index 58726989b..656d39d46 100644 --- a/docs/modules/ROOT/pages/includes/authorization-token-propagation.adoc +++ b/docs/modules/ROOT/pages/includes/authorization-token-propagation.adoc @@ -78,7 +78,7 @@ WARNING: When configured, the token propagation applies to all the operations se === Propagation flow configuration -The token propagation can be used with type "oauth2" or "bearer" security schemes. Finally, considering that a given security scheme might be configured on a set of operations in the same specification file when configured, it'll apply to all these operations. +The token propagation can be used with type "oauth2", "bearer" or "basic" security schemes. Finally, considering that a given security scheme might be configured on a set of operations in the same specification file when configured, it'll apply to all these operations. [%autowidth] |=== From eb973e09be4274daa44d83a7a2598c7fb47de074 Mon Sep 17 00:00:00 2001 From: gabriel-farache Date: Fri, 4 Apr 2025 09:37:56 +0200 Subject: [PATCH 3/3] Refactor based on feedback and update tests acordingly Co-authored-by: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> --- .../BasicAuthenticationProvider.java | 7 +++---- .../BearerAuthenticationProvider.java | 9 ++++----- .../BasicOpenApiSpecProviderTest.java | 19 ++++++++++--------- .../BearerOpenApiSpecProviderTest.java | 4 ++++ 4 files changed, 21 insertions(+), 18 deletions(-) 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 842fd8512..10011a9b3 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 @@ -6,8 +6,6 @@ import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.core.HttpHeaders; -import io.quarkiverse.openapi.generator.OpenApiGeneratorException; -import org.eclipse.microprofile.config.ConfigProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +36,8 @@ private String getPassword(ClientRequestContext requestContext) { @Override public void filter(ClientRequestContext requestContext) throws IOException { - String basicToken = AuthUtils.basicAuthAccessTokenWithoutPrefix(getUsername(requestContext), getPassword(requestContext)); + String basicToken = AuthUtils.basicAuthAccessTokenWithoutPrefix(getUsername(requestContext), + getPassword(requestContext)); if (isTokenPropagation()) { LOGGER.warn("Token propagation enabled for BasicAuthentication"); @@ -47,7 +46,7 @@ public void filter(ClientRequestContext requestContext) throws IOException { if (!basicToken.isBlank()) { requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, - AuthUtils.basicAuthAccessToken(basicToken)); + AuthUtils.basicAuthAccessToken(basicToken)); } } 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 4362e5a2e..5454b1808 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 @@ -28,13 +28,12 @@ public BearerAuthenticationProvider(final String openApiSpecId, final String nam @Override public void filter(ClientRequestContext requestContext) throws IOException { - String bearerToken; + String bearerToken = getBearerToken(requestContext); + if (isTokenPropagation()) { - bearerToken = getTokenForPropagation(requestContext.getHeaders()); - bearerToken = sanitizeBearerToken(bearerToken); - } else { - bearerToken = getBearerToken(requestContext); + bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders())); } + if (!bearerToken.isBlank()) { requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken)); } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java index 2b9d980fb..20a26d577 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BasicOpenApiSpecProviderTest.java @@ -28,6 +28,9 @@ class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) { Config mockedConfig = Mockito.mock(Config.class); configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig); @@ -69,7 +67,10 @@ void filterWithPropagation(String headerName, Boolean.class)).thenReturn(Optional.of(true)); when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME), String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName)); - + when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(USER_PROP), + String.class)).thenReturn(Optional.of(USER)); + when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(PASSWORD_PROP), + String.class)).thenReturn(Optional.of(PASSWORD)); headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN); filter(expectedAuthorizationHeader); } diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java index 11099b276..0321b99da 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java @@ -28,6 +28,8 @@ class BearerOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest