Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
package io.quarkiverse.openapi.generator.providers;

import static io.quarkiverse.openapi.generator.AuthConfig.TOKEN_PROPAGATION;

import java.io.IOException;
import java.util.List;

import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.core.HttpHeaders;

import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Provider for Basic Authentication.
* Username and password should be read by generated configuration properties, which is only known after openapi spec processing
* 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<OperationAuthInfo> operations,
CredentialsProvider credentialsProvider) {
super(name, openApiSpecId, operations, credentialsProvider);
validateConfig();
}

public BasicAuthenticationProvider(final String openApiSpecId, String name, List<OperationAuthInfo> operations) {
Expand All @@ -37,18 +36,18 @@ 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));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
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;

import io.quarkiverse.openapi.generator.AuthConfig;

class BasicOpenApiSpecProviderTest extends AbstractOpenApiSpecProviderTest<BasicAuthenticationProvider> {

private static final String PROPAGATED_TOKEN = "PROPAGATED_TOKEN";
private static final String USER = "USER";
private static final String PASSWORD = "PASSWORD";

private static final String USER_PROP = "username";
private static final String PASSWORD_PROP = "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());

Expand All @@ -33,22 +44,41 @@ 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 = headerName == null
? propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, HttpHeaders.AUTHORIZATION)
: propagationHeaderName(OPEN_API_FILE_SPEC_ID, AUTH_SCHEME_NAME, HEADER_NAME);
try (MockedStatic<ConfigProvider> 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));

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

static Stream<Arguments> filterWithPropagationTestValues() {
return Stream.of(
Arguments.of(null, "Basic " + PROPAGATED_TOKEN),
Arguments.of(HEADER_NAME, "Basic " + PROPAGATED_TOKEN));
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
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<BearerAuthenticationProvider> {

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 BEARER_TOKEN = "bearer-token";
private static final String CUSTOM_SCHEMA = "custom_scheme";
private static final String HEADER_NAME = "HEADER_NAME";

static Stream<Arguments> 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
Expand Down Expand Up @@ -70,7 +82,19 @@ 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<ConfigProvider> 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));
when(mockedConfig.getOptionalValue(provider.getCanonicalAuthConfigPropertyName(BEARER_TOKEN),
String.class)).thenReturn(Optional.of(INCOMING_TOKEN));

headers.putSingle(propagatedHeaderName, PROPAGATED_TOKEN);
filter(bearerScheme, expectedAuthorizationHeader);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]
|===
Expand Down