Skip to content

Commit 60cba7b

Browse files
Add suport for basic auth token propagation
Signed-off-by: gabriel-farache <[email protected]>
1 parent 159c7f7 commit 60cba7b

File tree

6 files changed

+69
-24
lines changed

6 files changed

+69
-24
lines changed

client/runtime/src/main/java/io/quarkiverse/openapi/generator/AuthConfig.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package io.quarkiverse.openapi.generator;
22

3-
import java.util.Map;
4-
import java.util.Optional;
5-
63
import io.quarkiverse.openapi.generator.providers.ApiKeyAuthenticationProvider;
74
import io.quarkiverse.openapi.generator.providers.BasicAuthenticationProvider;
85
import io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider;
96
import io.quarkus.runtime.annotations.ConfigGroup;
107
import io.quarkus.runtime.annotations.ConfigItem;
118

9+
import java.util.Map;
10+
import java.util.Optional;
11+
1212
/**
1313
* This class represents the runtime authentication related configuration for an individual securityScheme present
1414
* on an OpenApi spec definition, i.e. the provided files.

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
@@ -17,6 +17,8 @@
1717
public abstract class AbstractAuthProvider implements AuthProvider {
1818

1919
private static final String BEARER_WITH_SPACE = "Bearer ";
20+
private static final String BASIC_WITH_SPACE = "Basic ";
21+
2022
private static final String CANONICAL_AUTH_CONFIG_PROPERTY_NAME = "quarkus." + RUNTIME_TIME_CONFIG_PREFIX
2123
+ ".%s.auth.%s.%s";
2224

@@ -37,6 +39,13 @@ protected static String sanitizeBearerToken(String token) {
3739
return token;
3840
}
3941

42+
protected static String sanitizeBasicToken(String token) {
43+
if (token != null && token.toLowerCase().startsWith(BASIC_WITH_SPACE.toLowerCase())) {
44+
return token.substring(BASIC_WITH_SPACE.length());
45+
}
46+
return token;
47+
}
48+
4049
public String getOpenApiSpecId() {
4150
return openApiSpecId;
4251
}

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) {

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,22 @@
1111
import org.eclipse.microprofile.config.ConfigProvider;
1212

1313
import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
1416

1517
/**
1618
* Provider for Basic Authentication.
1719
* Username and password should be read by generated configuration properties, which is only known after openapi spec processing
1820
* during build time.
1921
*/
2022
public class BasicAuthenticationProvider extends AbstractAuthProvider {
23+
private static final Logger LOGGER = LoggerFactory.getLogger(BasicAuthenticationProvider.class);
2124

2225
static final String USER_NAME = "username";
2326
static final String PASSWORD = "password";
2427

2528
public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations) {
2629
super(name, openApiSpecId, operations);
27-
validateConfig();
2830
}
2931

3032
private String getUsername() {
@@ -39,18 +41,18 @@ private String getPassword() {
3941

4042
@Override
4143
public void filter(ClientRequestContext requestContext) throws IOException {
42-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
43-
AuthUtils.basicAuthAccessToken(getUsername(), getPassword()));
44-
}
45-
46-
private void validateConfig() {
44+
String basicToken;
4745
if (isTokenPropagation()) {
48-
throw new OpenApiGeneratorException(
49-
"Token propagation is not admitted for the OpenApi securitySchemes of \"type\": \"http\", \"scheme\": \"basic\"."
50-
+
51-
" A potential source of the problem might be that the configuration property " +
52-
getCanonicalAuthConfigPropertyName(TOKEN_PROPAGATION) +
53-
" was set with the value true in your application, please check your configuration.");
46+
LOGGER.warn("Token propagation enabled for BasicAuthentication");
47+
basicToken = getTokenForPropagation(requestContext.getHeaders());
48+
basicToken = sanitizeBasicToken(basicToken);
49+
} else {
50+
basicToken = AuthUtils.basicAuthAccessTokenWithoutPrefix(getUsername(), getPassword());
5451
}
52+
if (!basicToken.isBlank()) {
53+
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
54+
AuthUtils.basicAuthAccessToken(basicToken));
55+
}
56+
5557
}
5658
}
Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
11
package io.quarkiverse.openapi.generator.providers;
22

3+
import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
34
import static org.assertj.core.api.Assertions.assertThatThrownBy;
45
import static org.mockito.Mockito.when;
56

67
import java.io.IOException;
78
import java.util.Base64;
89
import java.util.List;
910
import java.util.Optional;
11+
import java.util.stream.Stream;
1012

1113
import jakarta.ws.rs.core.HttpHeaders;
1214

1315
import org.eclipse.microprofile.config.Config;
1416
import org.eclipse.microprofile.config.ConfigProvider;
1517
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.params.ParameterizedTest;
19+
import org.junit.jupiter.params.provider.Arguments;
20+
import org.junit.jupiter.params.provider.MethodSource;
1621
import org.mockito.MockedStatic;
1722
import org.mockito.Mockito;
1823

1924
import io.quarkiverse.openapi.generator.AuthConfig;
2025

2126
class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest<BasicAuthenticationProvider> {
2227

28+
private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
2329
private static final String USER = "USER";
2430
private static final String PASSWORD = "PASSWORD";
2531

32+
private static final String CUSTOM_SCHEMA = "custom_scheme";
33+
private static final String HEADER_NAME = "HEADER_NAME";
34+
2635
private static final String EXPECTED_BASIC_TOKEN = "Basic "
2736
+ Base64.getEncoder().encodeToString((USER + ":" + PASSWORD).getBytes());
2837

@@ -33,22 +42,43 @@ protected BasicAuthenticationProvider createProvider() {
3342

3443
@Test
3544
void filter() throws IOException {
45+
filter(EXPECTED_BASIC_TOKEN);
46+
}
47+
48+
private void filter(String expectedAuthorizationHeader) throws IOException {
3649
provider.filter(requestContext);
37-
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, EXPECTED_BASIC_TOKEN);
50+
assertHeader(requestContext.getHeaders(), HttpHeaders.AUTHORIZATION, expectedAuthorizationHeader);
3851
}
3952

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

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);
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
}

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)