Skip to content

Commit e354b2f

Browse files
github-actions[bot]gabriel-farachefjtiradoricardozanini
authored
Add support for basic auth propagation (#1078) (#1109)
* Fix propagation test for bearer provider * Add suport for basic auth token propagation * Refactor based on feedback and update tests acordingly --------- Signed-off-by: gabriel-farache <[email protected]> Co-authored-by: gabriel-farache <[email protected]> Co-authored-by: Francisco Javier Tirado Sarti <[email protected]> Co-authored-by: Ricardo Zanini <[email protected]>
1 parent bebe54d commit e354b2f

File tree

7 files changed

+101
-36
lines changed

7 files changed

+101
-36
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 & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
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

11-
import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
1211

1312
/**
1413
* Provider for Basic Authentication.
1514
* Username and password should be read by generated configuration properties, which is only known after openapi spec processing
1615
* during build time.
1716
*/
1817
public class BasicAuthenticationProvider extends AbstractAuthProvider {
18+
private static final Logger LOGGER = LoggerFactory.getLogger(BasicAuthenticationProvider.class);
1919

2020
public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations,
2121
CredentialsProvider credentialsProvider) {
2222
super(name, openApiSpecId, operations, credentialsProvider);
23-
validateConfig();
2423
}
2524

2625
public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations) {
@@ -37,18 +36,18 @@ private String getPassword(ClientRequestContext requestContext) {
3736

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

44-
private void validateConfig() {
4542
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.");
43+
LOGGER.warn("Token propagation enabled for BasicAuthentication");
44+
basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders()));
5245
}
46+
47+
if (!basicToken.isBlank()) {
48+
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
49+
AuthUtils.basicAuthAccessToken(basicToken));
50+
}
51+
5352
}
5453
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ public BearerAuthenticationProvider(final String openApiSpecId, final String nam
2828

2929
@Override
3030
public void filter(ClientRequestContext requestContext) throws IOException {
31-
String bearerToken;
31+
String bearerToken = getBearerToken(requestContext);
32+
3233
if (isTokenPropagation()) {
33-
bearerToken = getTokenForPropagation(requestContext.getHeaders());
34-
bearerToken = sanitizeBearerToken(bearerToken);
35-
} else {
36-
bearerToken = getBearerToken(requestContext);
34+
bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders()));
3735
}
36+
3837
if (!bearerToken.isBlank()) {
3938
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken));
4039
}
Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
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 USER_PROP = "username";
32+
private static final String PASSWORD_PROP = "password";
33+
34+
private static final String CUSTOM_SCHEMA = "custom_scheme";
35+
private static final String HEADER_NAME = "HEADER_NAME";
36+
2637
private static final String EXPECTED_BASIC_TOKEN = "Basic "
2738
+ Base64.getEncoder().encodeToString((USER + ":" + PASSWORD).getBytes());
2839

@@ -33,22 +44,41 @@ protected BasicAuthenticationProvider createProvider() {
3344

3445
@Test
3546
void filter() throws IOException {
47+
filter(EXPECTED_BASIC_TOKEN);
48+
}
49+
50+
private void filter(String expectedAuthorizationHeader) throws IOException {
3651
provider.filter(requestContext);
37-
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, EXPECTED_BASIC_TOKEN);
52+
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);
3853
}
3954

40-
@Test
41-
void tokenPropagationNotSupported() {
55+
@ParameterizedTest
56+
@MethodSource("filterWithPropagationTestValues")
57+
void filterWithPropagation(String headerName,
58+
String expectedAuthorizationHeader) throws IOException {
59+
String propagatedHeaderName = headerName == null
60+
? propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, HttpHeaders.AUTHORIZATION)
61+
: propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, HEADER_NAME);
4262
try (MockedStatic<ConfigProvider> configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) {
4363
Config mockedConfig = Mockito.mock(Config.class);
4464
configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig);
65+
4566
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION),
4667
Boolean.class)).thenReturn(Optional.of(true));
47-
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);
68+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME),
69+
String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName));
70+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(USER_PROP),
71+
String.class)).thenReturn(Optional.of(USER));
72+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(PASSWORD_PROP),
73+
String.class)).thenReturn(Optional.of(PASSWORD));
74+
headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
75+
filter(expectedAuthorizationHeader);
5176
}
77+
}
5278

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

client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/BearerOpenApiSpecProviderTest.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,44 @@
11
package io.quarkiverse.openapi.generator.providers;
22

33
import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
4+
import static org.mockito.Mockito.when;
45

56
import java.io.IOException;
67
import java.util.List;
8+
import java.util.Optional;
79
import java.util.stream.Stream;
810

911
import jakarta.ws.rs.core.HttpHeaders;
1012

13+
import org.eclipse.microprofile.config.Config;
14+
import org.eclipse.microprofile.config.ConfigProvider;
1115
import org.junit.jupiter.api.Test;
1216
import org.junit.jupiter.params.ParameterizedTest;
1317
import org.junit.jupiter.params.provider.Arguments;
1418
import org.junit.jupiter.params.provider.MethodSource;
19+
import org.mockito.MockedStatic;
20+
import org.mockito.Mockito;
21+
22+
import io.quarkiverse.openapi.generator.AuthConfig;
1523

1624
class BearerOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest<BearerAuthenticationProvider> {
1725

1826
private static final String INCOMING_TOKEN = "INCOMING_TOKEN";
1927

28+
private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
29+
2030
private static final String BEARER_SCHEMA = "bearer";
31+
32+
private static final String BEARER_TOKEN = "bearer-token";
2133
private static final String CUSTOM_SCHEMA = "custom_scheme";
2234
private static final String HEADER_NAME = "HEADER_NAME";
2335

2436
static Stream<Arguments> filterWithPropagationTestValues() {
2537
return Stream.of(
26-
Arguments.of(null, "bearer", "Bearer " + INCOMING_TOKEN),
27-
Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN),
28-
Arguments.of(HEADER_NAME, "bearer", "Bearer " + INCOMING_TOKEN),
29-
Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + INCOMING_TOKEN));
38+
Arguments.of(null, "bearer", "Bearer " + PROPAGATED_TOKEN),
39+
Arguments.of(null, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + PROPAGATED_TOKEN),
40+
Arguments.of(HEADER_NAME, "bearer", "Bearer " + PROPAGATED_TOKEN),
41+
Arguments.of(HEADER_NAME, CUSTOM_SCHEMA, CUSTOM_SCHEMA + " " + PROPAGATED_TOKEN));
3042
}
3143

3244
@Override
@@ -70,7 +82,19 @@ void filterWithPropagation(String headerName,
7082
propagatedHeaderName = propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME,
7183
HEADER_NAME);
7284
}
73-
headers.putSingle(propagatedHeaderName, INCOMING_TOKEN);
74-
filter(bearerScheme, expectedAuthorizationHeader);
85+
try (MockedStatic<ConfigProvider> configProviderMocked = Mockito.mockStatic(ConfigProvider.class)) {
86+
Config mockedConfig = Mockito.mock(Config.class);
87+
configProviderMocked.when(ConfigProvider::getConfig).thenReturn(mockedConfig);
88+
89+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.TOKEN_PROPAGATION),
90+
Boolean.class)).thenReturn(Optional.of(true));
91+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME),
92+
String.class)).thenReturn(Optional.of(headerName == null ? HttpHeaders.AUTHORIZATION : headerName));
93+
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(BEARER_TOKEN),
94+
String.class)).thenReturn(Optional.of(INCOMING_TOKEN));
95+
96+
headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
97+
filter(bearerScheme, expectedAuthorizationHeader);
98+
}
7599
}
76100
}

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)