Skip to content

Commit 2be2367

Browse files
Add suport for basic auth token propagation
Signed-off-by: gabriel-farache <[email protected]>
1 parent a19fe98 commit 2be2367

File tree

5 files changed

+65
-23
lines changed

5 files changed

+65
-23
lines changed

client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AbstractAuthProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public abstract class AbstractAuthProvider implements AuthProvider {
1919
CredentialsProvider credentialsProvider;
2020

2121
private static final String BEARER_WITH_SPACE = "Bearer ";
22+
private static final String BASIC_WITH_SPACE = "Basic ";
23+
2224
private static final String CANONICAL_AUTH_CONFIG_PROPERTY_NAME = "quarkus." + RUNTIME_TIME_CONFIG_PREFIX
2325
+ ".%s.auth.%s.%s";
2426

@@ -41,6 +43,13 @@ protected static String sanitizeBearerToken(String token) {
4143
return token;
4244
}
4345

46+
protected static String sanitizeBasicToken(String token) {
47+
if (token != null && token.toLowerCase().startsWith(BASIC_WITH_SPACE.toLowerCase())) {
48+
return token.substring(BASIC_WITH_SPACE.length());
49+
}
50+
return token;
51+
}
52+
4453
public String getOpenApiSpecId() {
4554
return openApiSpecId;
4655
}

client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/AuthUtils.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ public final class AuthUtils {
1010
private AuthUtils() {
1111
}
1212

13-
public static String basicAuthAccessToken(final String username, final String password) {
13+
public static String basicAuthAccessTokenWithoutPrefix(final String username, final String password) {
14+
return Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes());
15+
}
16+
17+
public static String basicAuthAccessToken(final String basicToken) {
1418
return String.format("%s %s",
1519
BASIC_HEADER_PREFIX,
16-
Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes()));
20+
basicToken);
1721
}
1822

1923
public static String authTokenOrBearer(final String scheme, final String token) {
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
package io.quarkiverse.openapi.generator.providers;
22

3-
import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION;
4-
53
import java.io.IOException;
64
import java.util.List;
75

86
import jakarta.ws.rs.client.ClientRequestContext;
97
import jakarta.ws.rs.core.HttpHeaders;
108

119
import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
10+
import org.eclipse.microprofile.config.ConfigProvider;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
1213

1314
/**
1415
* Provider for Basic Authentication.
1516
* Username and password should be read by generated configuration properties, which is only known after openapi spec processing
1617
* during build time.
1718
*/
1819
public class BasicAuthenticationProvider extends AbstractAuthProvider {
20+
private static final Logger LOGGER = LoggerFactory.getLogger(BasicAuthenticationProvider.class);
1921

2022
public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations,
2123
CredentialsProvider credentialsProvider) {
2224
super(name, openApiSpecId, operations, credentialsProvider);
23-
validateConfig();
2425
}
2526

2627
public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations) {
@@ -37,18 +38,17 @@ private String getPassword(ClientRequestContext requestContext) {
3738

3839
@Override
3940
public void filter(ClientRequestContext requestContext) throws IOException {
40-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
41-
AuthUtils.basicAuthAccessToken(getUsername(requestContext), getPassword(requestContext)));
42-
}
41+
String basicToken = AuthUtils.basicAuthAccessTokenWithoutPrefix(getUsername(requestContext), getPassword(requestContext));
4342

44-
private void validateConfig() {
4543
if (isTokenPropagation()) {
46-
throw new OpenApiGeneratorException(
47-
"Token propagation is not admitted for the OpenApi securitySchemes of \"type\": \"http\", \"scheme\": \"basic\"."
48-
+
49-
" A potential source of the problem might be that the configuration property " +
50-
getCanonicalAuthConfigPropertyName(TOKEN_PROPAGATION) +
51-
" was set with the value true in your application, please check your configuration.");
44+
LOGGER.warn("Token propagation enabled for BasicAuthentication");
45+
basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders()));
5246
}
47+
48+
if (!basicToken.isBlank()) {
49+
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
50+
AuthUtils.basicAuthAccessToken(basicToken));
51+
}
52+
5353
}
5454
}
Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
package io.quarkiverse.openapi.generator.providers;
22

3-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3+
import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
44
import static org.mockito.Mockito.when;
55

66
import java.io.IOException;
77
import java.util.Base64;
88
import java.util.List;
99
import java.util.Optional;
10+
import java.util.stream.Stream;
1011

1112
import jakarta.ws.rs.core.HttpHeaders;
1213

1314
import org.eclipse.microprofile.config.Config;
1415
import org.eclipse.microprofile.config.ConfigProvider;
1516
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.Arguments;
19+
import org.junit.jupiter.params.provider.MethodSource;
1620
import org.mockito.MockedStatic;
1721
import org.mockito.Mockito;
1822

1923
import io.quarkiverse.openapi.generator.AuthConfig;
2024

2125
class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest<BasicAuthenticationProvider> {
2226

27+
private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
2328
private static final String USER = "USER";
2429
private static final String PASSWORD = "PASSWORD";
2530

31+
private static final String CUSTOM_SCHEMA = "custom_scheme";
32+
private static final String HEADER_NAME = "HEADER_NAME";
33+
2634
private static final String EXPECTED_BASIC_TOKEN = "Basic "
2735
+ Base64.getEncoder().encodeToString((USER + ":" + PASSWORD).getBytes());
2836

@@ -33,22 +41,43 @@ protected BasicAuthenticationProvider createProvider() {
3341

3442
@Test
3543
void filter() throws IOException {
44+
filter(EXPECTED_BASIC_TOKEN);
45+
}
46+
47+
private void filter(String expectedAuthorizationHeader) throws IOException {
3648
provider.filter(requestContext);
37-
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, EXPECTED_BASIC_TOKEN);
49+
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);
3850
}
3951

40-
@Test
41-
void tokenPropagationNotSupported() {
52+
@ParameterizedTest
53+
@MethodSource("filterWithPropagationTestValues")
54+
void filterWithPropagation(String headerName,
55+
String expectedAuthorizationHeader) throws IOException {
56+
String propagatedHeaderName;
57+
if (headerName == null) {
58+
propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
59+
HttpHeaders.AUTHORIZATION);
60+
} else {
61+
propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
62+
HEADER_NAME);
63+
}
4264
try (MockedStatic<ConfigProvider> configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) {
4365
Config mockedConfig = Mockito.mock(Config.class);
4466
configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig);
67+
4568
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION),
4669
Boolean.class)).thenReturn(Optional.of(true));
70+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME),
71+
String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName));
4772

48-
assertThatThrownBy(() -> new BasicAuthenticationProvider(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, List.of()))
49-
.hasMessageContaining("quarkus.openapi-generator.%s.auth.%s.token-propagation", OPEN_API_FILE_SPEC_ID,
50-
AUTH_SCHEME_NAME);
73+
headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
74+
filter(expectedAuthorizationHeader);
5175
}
76+
}
5277

78+
static Stream<Arguments> filterWithPropagationTestValues() {
79+
return Stream.of(
80+
Arguments.of(null, "Basic " + PROPAGATED_TOKEN),
81+
Arguments.of(HEADER_NAME, "Basic " + PROPAGATED_TOKEN));
5382
}
5483
}

docs/modules/ROOT/pages/includes/authorization-token-propagation.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ WARNING: When configured, the token propagation applies to all the operations se
7878

7979
=== Propagation flow configuration
8080

81-
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.
81+
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.
8282

8383
[%autowidth]
8484
|===

0 commit comments

Comments
 (0)