Skip to content

Commit c6e504a

Browse files
authored
issue-1182: Token propagation throws java.lang.UnsupportedOperationException when adding the token to the request header parameters (#1184)
* issue-1182: Token propagation throws java.lang.UnsupportedOperationException when adding the token to the request header parameters * Review comments 1 * Review comments 2
1 parent 0b38a70 commit c6e504a

File tree

7 files changed

+98
-45
lines changed

7 files changed

+98
-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;
@@ -33,9 +32,13 @@ public OAuth2AuthenticationProvider(String name,
3332
@Override
3433
public void filter(ClientRequestContext requestContext) throws IOException {
3534
if (isTokenPropagation()) {
36-
String bearerToken = getTokenForPropagation(requestContext.getHeaders());
37-
bearerToken = sanitizeBearerToken(bearerToken);
38-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, OidcConstants.BEARER_SCHEME + " " + bearerToken);
35+
String bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders()));
36+
if (!isEmptyOrBlank(bearerToken)) {
37+
addAuthorizationHeader(requestContext.getHeaders(), OidcConstants.BEARER_SCHEME + " " + bearerToken);
38+
} else {
39+
LOGGER.debug("No oauth2 bearer token was found to propagate for the security scheme: {}." +
40+
" You must verify that the request header: {} is set.", getName(), getHeaderForPropagation());
41+
}
3942
} else {
4043
delegate.filter(requestContext);
4144
}

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

Lines changed: 15 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

@@ -66,9 +67,12 @@ public boolean isTokenPropagation() {
6667
}
6768

6869
public String getTokenForPropagation(MultivaluedMap<String, Object> httpHeaders) {
69-
String headerName = getHeaderName() != null ? getHeaderName() : HttpHeaders.AUTHORIZATION;
70-
String propagatedHeaderName = propagationHeaderName(getOpenApiSpecId(), getName(), headerName);
71-
return Objects.toString(httpHeaders.getFirst(propagatedHeaderName));
70+
String propagatedHeaderName = propagationHeaderName(getOpenApiSpecId(), getName(), getHeaderForPropagation());
71+
return Objects.toString(httpHeaders.getFirst(propagatedHeaderName), null);
72+
}
73+
74+
public String getHeaderForPropagation() {
75+
return getHeaderName() != null ? getHeaderName() : HttpHeaders.AUTHORIZATION;
7276
}
7377

7478
public String getHeaderName() {
@@ -88,4 +92,12 @@ public final String getCanonicalAuthConfigPropertyName(String authPropertyName)
8892
public static String getCanonicalAuthConfigPropertyName(String authPropertyName, String openApiSpecId, String authName) {
8993
return String.format(CANONICAL_AUTH_CONFIG_PROPERTY_NAME, openApiSpecId, authName, authPropertyName);
9094
}
95+
96+
protected void addAuthorizationHeader(MultivaluedMap<String, Object> headers, String value) {
97+
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(value));
98+
}
99+
100+
protected static boolean isEmptyOrBlank(String value) {
101+
return value == null || value.isBlank();
102+
}
91103
}

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;
@@ -44,10 +46,14 @@ public void filter(ClientRequestContext requestContext) throws IOException {
4446
basicToken = sanitizeBasicToken(getTokenForPropagation(requestContext.getHeaders()));
4547
}
4648

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

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
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;
9+
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
812

913
/**
1014
* Provides bearer token authentication or any other valid scheme.
@@ -13,6 +17,8 @@
1317
*/
1418
public class BearerAuthenticationProvider extends AbstractAuthProvider {
1519

20+
private static final Logger LOGGER = LoggerFactory.getLogger(BearerAuthenticationProvider.class);
21+
1622
private final String scheme;
1723

1824
public BearerAuthenticationProvider(final String openApiSpecId, final String name, final String scheme,
@@ -34,8 +40,13 @@ public void filter(ClientRequestContext requestContext) throws IOException {
3440
bearerToken = sanitizeBearerToken(getTokenForPropagation(requestContext.getHeaders()));
3541
}
3642

37-
if (!bearerToken.isBlank()) {
38-
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, AuthUtils.authTokenOrBearer(this.scheme, bearerToken));
43+
if (!isEmptyOrBlank(bearerToken)) {
44+
addAuthorizationHeader(requestContext.getHeaders(), AuthUtils.authTokenOrBearer(this.scheme, bearerToken));
45+
} else {
46+
LOGGER.debug("No bearer token was found for the security scheme: {}." +
47+
" You must verify that the property: {} is properly configured, or the request header: {} is set when the token propagation is enabled.",
48+
getName(), getCanonicalAuthConfigPropertyName(BEARER_TOKEN, getOpenApiSpecId(), getName()),
49+
getHeaderForPropagation());
3950
}
4051
}
4152

0 commit comments

Comments
 (0)