Skip to content

Commit 5d7c6d7

Browse files
committed
issue-1182: Token propagation throws java.lang.UnsupportedOperationException when adding the token to the request header parameters (quarkiverse#1184)
* issue-1182: Token propagation throws java.lang.UnsupportedOperationException when adding the token to the request header parameters * Review comments 1 * Review comments 2 (cherry picked from commit c6e504a)
1 parent 9d7eb5a commit 5d7c6d7

File tree

7 files changed

+94
-45
lines changed

7 files changed

+94
-45
lines changed

client/integration-tests/security/src/main/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationResource.java

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jakarta.ws.rs.POST;
44
import jakarta.ws.rs.Path;
5+
import jakarta.ws.rs.core.Response;
56

67
import org.eclipse.microprofile.rest.client.inject.RestClient;
78

@@ -25,36 +26,31 @@ public class TokenPropagationResource {
2526

2627
@POST
2728
@Path("service1")
28-
public String service1() {
29-
defaultApi1.executeQuery1();
30-
return "hello";
29+
public Response service1() {
30+
return defaultApi1.executeQuery1();
3131
}
3232

3333
@POST
3434
@Path("service2")
35-
public String service2() {
36-
defaultApi2.executeQuery2();
37-
return "hello";
35+
public Response service2() {
36+
return defaultApi2.executeQuery2();
3837
}
3938

4039
@POST
4140
@Path("service3")
42-
public String service3() {
43-
defaultApi3.executeQuery3();
44-
return "hello";
41+
public Response service3() {
42+
return defaultApi3.executeQuery3();
4543
}
4644

4745
@POST
4846
@Path("service4")
49-
public String service4() {
50-
defaultApi4.executeQuery4();
51-
return "hello";
47+
public Response service4() {
48+
return defaultApi4.executeQuery4();
5249
}
5350

5451
@POST
5552
@Path("service5")
56-
public String service5() {
57-
defaultApi5.executeQuery5();
58-
return "hello";
53+
public Response service5() {
54+
return defaultApi5.executeQuery5();
5955
}
6056
}

client/integration-tests/security/src/test/java/io/quarkiverse/openapi/generator/it/security/TokenPropagationExternalServicesMock.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323

2424
public class TokenPropagationExternalServicesMock implements QuarkusTestResourceLifecycleManager {
2525

26-
public static final String AUTHORIZATION_TOKEN = "AUTHORIZATION_TOKEN";
26+
public static final String SERVICE1_AUTHORIZATION_TOKEN = "SERVICE1_AUTHORIZATION_TOKEN";
27+
public static final String SERVICE2_AUTHORIZATION_TOKEN = "SERVICE2_AUTHORIZATION_TOKEN";
2728
public static final String SERVICE3_HEADER_TO_PROPAGATE = "SERVICE3_HEADER_TO_PROPAGATE";
2829
public static final String SERVICE3_AUTHORIZATION_TOKEN = "SERVICE3_AUTHORIZATION_TOKEN";
2930
public static final String SERVICE4_HEADER_TO_PROPAGATE = "SERVICE4_HEADER_TO_PROPAGATE";
@@ -49,10 +50,10 @@ public Map<String, String> start() {
4950
LOGGER.info("Mocked Server started at {}", wireMockServer.baseUrl());
5051

5152
// stub the token-propagation-external-service1 invocation with the expected token
52-
stubForExternalService("/token-propagation-external-service1/executeQuery1", AUTHORIZATION_TOKEN);
53+
stubForExternalService("/token-propagation-external-service1/executeQuery1", SERVICE1_AUTHORIZATION_TOKEN);
5354

5455
// stub the token-propagation-external-service2 invocation with the expected token
55-
stubForExternalService("/token-propagation-external-service2/executeQuery2", AUTHORIZATION_TOKEN);
56+
stubForExternalService("/token-propagation-external-service2/executeQuery2", SERVICE2_AUTHORIZATION_TOKEN);
5657

5758
// stub the token-propagation-external-service3 invocation with the expected token
5859
stubForExternalService("/token-propagation-external-service3/executeQuery3", SERVICE3_AUTHORIZATION_TOKEN);
Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
package io.quarkiverse.openapi.generator.it.security;
22

3-
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.AUTHORIZATION_TOKEN;
3+
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE1_AUTHORIZATION_TOKEN;
4+
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE2_AUTHORIZATION_TOKEN;
45
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE3_AUTHORIZATION_TOKEN;
56
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE3_HEADER_TO_PROPAGATE;
67
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_AUTHORIZATION_TOKEN;
78
import static io.quarkiverse.openapi.generator.it.security.TokenPropagationExternalServicesMock.SERVICE4_HEADER_TO_PROPAGATE;
89
import static io.restassured.RestAssured.given;
910

11+
import java.util.Collections;
1012
import java.util.HashMap;
13+
import java.util.List;
1114
import java.util.Map;
15+
import java.util.stream.Stream;
1216

1317
import jakarta.ws.rs.core.HttpHeaders;
1418

1519
import org.junit.jupiter.api.Tag;
1620
import org.junit.jupiter.params.ParameterizedTest;
17-
import org.junit.jupiter.params.provider.ValueSource;
21+
import org.junit.jupiter.params.provider.Arguments;
22+
import org.junit.jupiter.params.provider.MethodSource;
1823

1924
import io.quarkus.test.common.QuarkusTestResource;
2025
import io.quarkus.test.junit.QuarkusTest;
@@ -26,21 +31,40 @@
2631
@Tag("resteasy-classic")
2732
class TokenPropagationTest {
2833

29-
@ParameterizedTest
30-
@ValueSource(strings = { "service1", "service2", "service3", "service4", "service5" })
31-
void service1(String service) {
32-
Map<String, String> headers = new HashMap<>();
33-
// service token-propagation-external-service1 and token-propagation-external-service2 will receive the AUTHORIZATION_TOKEN
34-
headers.put(HttpHeaders.AUTHORIZATION, AUTHORIZATION_TOKEN);
35-
// service token-propagation-external-service3 will receive the SERVICE3_AUTHORIZATION_TOKEN
36-
headers.put(SERVICE3_HEADER_TO_PROPAGATE, SERVICE3_AUTHORIZATION_TOKEN);
37-
// service token-propagation-external-service4 will receive the SERVICE4_AUTHORIZATION_TOKEN
38-
headers.put(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN);
34+
private static class HeaderArgument {
35+
String headerName;
36+
String headerValue;
37+
38+
HeaderArgument(String headerName, String headerValue) {
39+
this.headerName = headerName;
40+
this.headerValue = headerValue;
41+
}
42+
43+
}
3944

45+
@ParameterizedTest
46+
@MethodSource("serviceInvocationParams")
47+
void invokeService(String service, HeaderArgument headerArgument) {
48+
Map<String, List<String>> headers = new HashMap<>();
49+
headers.put(headerArgument.headerName, Collections.singletonList(headerArgument.headerValue));
4050
given()
4151
.headers(headers)
4252
.post("/token_propagation/" + service)
4353
.then()
4454
.statusCode(200);
4555
}
56+
57+
private static Stream<Arguments> serviceInvocationParams() {
58+
return Stream.of(
59+
// service token-propagation-external-service1 will receive the SERVICE1_AUTHORIZATION_TOKEN
60+
Arguments.of("service1", new HeaderArgument(HttpHeaders.AUTHORIZATION, SERVICE1_AUTHORIZATION_TOKEN)),
61+
// service token-propagation-external-service2 will receive the SERVICE2_AUTHORIZATION_TOKEN
62+
Arguments.of("service2", new HeaderArgument(HttpHeaders.AUTHORIZATION, SERVICE2_AUTHORIZATION_TOKEN)),
63+
// service token-propagation-external-service3 will receive the SERVICE3_AUTHORIZATION_TOKEN
64+
Arguments.of("service3", new HeaderArgument(SERVICE3_HEADER_TO_PROPAGATE, SERVICE3_AUTHORIZATION_TOKEN)),
65+
// service token-propagation-external-service4 will receive the SERVICE4_AUTHORIZATION_TOKEN
66+
Arguments.of("service4", new HeaderArgument(SERVICE4_HEADER_TO_PROPAGATE, SERVICE4_AUTHORIZATION_TOKEN))
67+
68+
);
69+
}
4670
}

client/oidc/src/main/java/io/quarkiverse/openapi/generator/oidc/providers/OAuth2AuthenticationProvider.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import java.util.List;
77

88
import jakarta.ws.rs.client.ClientRequestContext;
9-
import jakarta.ws.rs.core.HttpHeaders;
109

1110
import org.slf4j.Logger;
1211
import org.slf4j.LoggerFactory;
@@ -46,10 +45,14 @@ public void filter(ClientRequestContext requestContext) throws IOException {
4645
.build());
4746
}
4847

49-
if (bearerToken != null && !bearerToken.isBlank()) {
50-
requestContext.getHeaders().remove(HttpHeaders.AUTHORIZATION);
51-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
48+
if (!isEmptyOrBlank(bearerToken)) {
49+
addAuthorizationHeader(requestContext.getHeaders(),
5250
AuthUtils.authTokenOrBearer("Bearer", AbstractAuthProvider.sanitizeBearerToken(bearerToken)));
51+
} else {
52+
LOGGER.debug("No bearer token was found for the oauth2 security scheme: {}." +
53+
" You must verify that a Quarkus OIDC Client with the name: {} is properly configured," +
54+
" or the request header: {} is set when the token propagation is enabled.",
55+
getName(), getName(), getHeaderForPropagation(getOpenApiSpecId(), getName()));
5356
}
5457
}
5558

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static io.quarkiverse.openapi.generator.providers.AbstractAuthenticationPropagationHeadersFactory.propagationHeaderName;
55

66
import java.util.ArrayList;
7+
import java.util.Collections;
78
import java.util.List;
89
import java.util.Objects;
910

@@ -65,16 +66,20 @@ public boolean isTokenPropagation() {
6566

6667
public static String getTokenForPropagation(MultivaluedMap<String, Object> httpHeaders, String openApiSpecId,
6768
String authName) {
68-
String headerName = getHeaderName(openApiSpecId, authName) != null ? getHeaderName(openApiSpecId, authName)
69-
: HttpHeaders.AUTHORIZATION;
69+
String headerName = getHeaderForPropagation(openApiSpecId, authName);
7070
String propagatedHeaderName = propagationHeaderName(openApiSpecId, authName, headerName);
71-
return Objects.toString(httpHeaders.getFirst(propagatedHeaderName));
71+
return Objects.toString(httpHeaders.getFirst(propagatedHeaderName), null);
7272
}
7373

7474
public String getTokenForPropagation(MultivaluedMap<String, Object> httpHeaders) {
7575
return getTokenForPropagation(httpHeaders, getOpenApiSpecId(), getName());
7676
}
7777

78+
public static String getHeaderForPropagation(String openApiSpecId, String authName) {
79+
return getHeaderName(openApiSpecId, authName) != null ? getHeaderName(openApiSpecId, authName)
80+
: HttpHeaders.AUTHORIZATION;
81+
}
82+
7883
public String getHeaderName() {
7984
return ConfigProvider.getConfig()
8085
.getOptionalValue(getCanonicalAuthConfigPropertyName(AuthConfig.HEADER_NAME), String.class).orElse(null);
@@ -110,4 +115,12 @@ public static boolean isTokenPropagation(String openApiSpecId, String authName)
110115
public CredentialsProvider getCredentialsProvider() {
111116
return credentialsProvider;
112117
}
118+
119+
protected void addAuthorizationHeader(MultivaluedMap<String, Object> headers, String value) {
120+
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(value));
121+
}
122+
123+
protected static boolean isEmptyOrBlank(String value) {
124+
return value == null || value.isBlank();
125+
}
113126
}

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

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

3+
import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.PASSWORD;
4+
import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.USER_NAME;
5+
36
import java.io.IOException;
47
import java.util.List;
58

69
import jakarta.ws.rs.client.ClientRequestContext;
7-
import jakarta.ws.rs.core.HttpHeaders;
810

911
import org.slf4j.Logger;
1012
import org.slf4j.LoggerFactory;
@@ -48,10 +50,14 @@ public void filter(ClientRequestContext requestContext) throws IOException {
4850
basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders()));
4951
}
5052

51-
if (!basicToken.isBlank()) {
52-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION,
53-
AuthUtils.basicAuthAccessToken(basicToken));
53+
if (!isEmptyOrBlank(basicToken)) {
54+
addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.basicAuthAccessToken(basicToken));
55+
} else {
56+
LOGGER.debug("No basic authentication token was found for the security scheme: {}." +
57+
" You must verify that the properties: {} and {} are properly configured, or the request header: {} is set when the token propagation is enabled.",
58+
getName(), getCanonicalAuthConfigPropertyName(USER_NAME, getOpenApiSpecId(), getName()),
59+
getCanonicalAuthConfigPropertyName(PASSWORD, getOpenApiSpecId(), getName()),
60+
getHeaderForPropagation(getOpenApiSpecId(), getName()));
5461
}
55-
5662
}
5763
}

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

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

3+
import static io.quarkiverse.openapi.generator.providers.ConfigCredentialsProvider.BEARER_TOKEN;
4+
35
import java.io.IOException;
46
import java.util.List;
57

68
import jakarta.ws.rs.client.ClientRequestContext;
7-
import jakarta.ws.rs.core.HttpHeaders;
89

910
import org.slf4j.Logger;
1011
import org.slf4j.LoggerFactory;
@@ -34,8 +35,13 @@ public void filter(ClientRequestContext requestContext) throws IOException {
3435
bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders()));
3536
}
3637

37-
if (!bearerToken.isBlank()) {
38-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken));
38+
if (!isEmptyOrBlank(bearerToken)) {
39+
addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.authTokenOrBearer(this.scheme, bearerToken));
40+
} else {
41+
LOGGER.debug("No bearer token was found for the security scheme: {}." +
42+
" You must verify that the property: {} is properly configured, or the request header: {} is set when the token propagation is enabled.",
43+
getName(), getCanonicalAuthConfigPropertyName(BEARER_TOKEN, getOpenApiSpecId(), getName()),
44+
getHeaderForPropagation(getOpenApiSpecId(), getName()));
3945
}
4046
LOGGER.debug("Header keys set in filtered requestContext: {}", requestContext.getHeaders().keySet());
4147
}

0 commit comments

Comments
 (0)