Skip to content

Commit cb9bbb3

Browse files
#308 do not duplicate Authorization header, see #308 (#311) (#326)
* #308 do not duplicate Authorization header, see #308 * #308 add IT test * #308 linter * #308 use LoggedRequest case. remove comments. * #308 count the Authorization headers * #308 log if api-key is empty * #308 fix isEmpty --------- Co-authored-by: Laurent Perez <[email protected]> (cherry picked from commit dfedbab) Co-authored-by: Laurent Perez <[email protected]>
1 parent 3b0e06b commit cb9bbb3

File tree

5 files changed

+205
-2
lines changed

5 files changed

+205
-2
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"swagger": "2.0",
3+
"info": {
4+
"description": "More description about the API",
5+
"version": "1.0.0",
6+
"title": "Swagger API Doc"
7+
},
8+
"host": "api.foo.fr/api",
9+
"basePath": "/",
10+
"tags": [
11+
{
12+
"name": "foo-resource",
13+
"description": "Foo Resource"
14+
}
15+
],
16+
"paths": {
17+
"/foo/v2.0/foo": {
18+
"get": {
19+
"tags": [
20+
"foo-resource"
21+
],
22+
"summary": "getFoos",
23+
"operationId": "getFoosUsingGET",
24+
"produces": [
25+
"*/*"
26+
],
27+
"parameters": [
28+
{
29+
"name": "Authorization",
30+
"in": "header",
31+
"description": "Authorization",
32+
"required": true,
33+
"type": "string"
34+
},
35+
{
36+
"name": "something",
37+
"in": "query",
38+
"description": "something int",
39+
"required": true,
40+
"type": "integer",
41+
"format": "int64"
42+
}
43+
],
44+
"responses": {
45+
"200": {
46+
"description": "OK",
47+
"schema": {
48+
"type": "array",
49+
"items": {
50+
"$ref": "#/definitions/FooDTO"
51+
}
52+
}
53+
},
54+
"401": {
55+
"description": "Unauthorized"
56+
},
57+
"403": {
58+
"description": "Forbidden"
59+
},
60+
"404": {
61+
"description": "Not Found"
62+
}
63+
},
64+
"security": [
65+
{
66+
"JWT": [
67+
"global"
68+
]
69+
}
70+
]
71+
}
72+
}
73+
},
74+
"securityDefinitions": {
75+
"JWT": {
76+
"type": "apiKey",
77+
"name": "Authorization",
78+
"in": "header"
79+
}
80+
},
81+
"definitions": {
82+
"FooDTO": {
83+
"type": "object",
84+
"properties": {
85+
"fubar": {
86+
"type": "string"
87+
}
88+
},
89+
"title": "FooDTO"
90+
}
91+
92+
}
93+
}

integration-tests/security/src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ org.acme.openapi.security.auth.basic_auth/password=test
77
quarkus.openapi-generator.codegen.spec.open_weather_yaml.base-package=org.acme.openapi.weather
88
quarkus.openapi-generator.open_weather_yaml.auth.app_id.api-key=12345
99

10+
quarkus.openapi-generator.codegen.spec.fooopenapi_json.base-package=org.acme.openapi.foo
11+
quarkus.openapi-generator.fooopenapi_json.auth.JWT.api-key=fooapikey
12+
1013
# Authentication properties
1114
quarkus.openapi-generator.codegen.spec.open_weather_no_security_yaml.base-package=org.acme.openapi.weathernosec
1215
quarkus.openapi-generator.open_weather_no_security_yaml.auth.app_id.api-key=12345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.quarkiverse.openapi.generator.it.security;
2+
3+
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
4+
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
5+
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertNotNull;
8+
9+
import java.util.List;
10+
11+
import javax.inject.Inject;
12+
13+
import org.acme.openapi.foo.api.FooResourceApi;
14+
import org.acme.openapi.foo.model.FooDTO;
15+
import org.eclipse.microprofile.rest.client.inject.RestClient;
16+
import org.junit.jupiter.api.Test;
17+
18+
import com.github.tomakehurst.wiremock.WireMockServer;
19+
import com.github.tomakehurst.wiremock.http.HttpHeaders;
20+
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
21+
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
22+
23+
import io.quarkus.test.common.QuarkusTestResource;
24+
import io.quarkus.test.junit.QuarkusTest;
25+
26+
@QuarkusTestResource(WiremockFoo.class)
27+
@QuarkusTest
28+
public class AuthorizationHeaderApiKeyCanFilterWithoutDuplicateAuthorizationTest {
29+
30+
WireMockServer fooServer;
31+
32+
@RestClient
33+
@Inject
34+
FooResourceApi fooResourceApi;
35+
36+
@Test
37+
public void testNoMultipleAuthorizationHeadersAreSent() {
38+
List<FooDTO> foos = fooResourceApi.getFoosUsingGET("not the fooapikey",
39+
123465L);
40+
assertNotNull(foos);
41+
42+
RequestPatternBuilder builder = getRequestedFor(
43+
urlEqualTo("/api/foo/v2.0/foo?something=123465"))
44+
.withHeader("Authorization", equalTo("fooapikey"));
45+
List<LoggedRequest> requestsWithAuthHeader = fooServer.findAll(builder);
46+
assertEquals(1, requestsWithAuthHeader.size(), "more than one request");
47+
48+
LoggedRequest loggedRequest = requestsWithAuthHeader.get(0);
49+
HttpHeaders httpHeaders = loggedRequest.getHeaders();
50+
long authHeaderCount = httpHeaders.all().stream().filter(
51+
httpHeader -> httpHeader.keyEquals("Authorization")).count();
52+
assertEquals(1, authHeaderCount, "multiple Authorization headers found");
53+
}
54+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.quarkiverse.openapi.generator.it.security;
2+
3+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
4+
import static com.github.tomakehurst.wiremock.client.WireMock.get;
5+
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
6+
7+
import java.util.Map;
8+
9+
import com.github.tomakehurst.wiremock.WireMockServer;
10+
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
11+
12+
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
13+
14+
public class WiremockFoo implements QuarkusTestResourceLifecycleManager {
15+
16+
private WireMockServer wireMockServer;
17+
18+
public static final String URL_KEY = "quarkus.rest-client.fooopenapi_json.url";
19+
20+
@Override
21+
public Map<String, String> start() {
22+
wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort());
23+
wireMockServer.start();
24+
25+
wireMockServer.stubFor(get(urlPathMatching("/api/foo(.+)"))
26+
.willReturn(aResponse()
27+
.withStatus(200)
28+
.withHeader("Content-Type", "application/json")
29+
.withBody("[{\"fubar\": \"hello\"}]")));
30+
return Map.of(URL_KEY, wireMockServer.baseUrl().concat("/api"));
31+
}
32+
33+
@Override
34+
public void inject(TestInjector testInjector) {
35+
testInjector.injectIntoFields(wireMockServer, f -> f.getName().equals("fooServer"));
36+
}
37+
38+
@Override
39+
public void stop() {
40+
if (null != wireMockServer) {
41+
wireMockServer.stop();
42+
}
43+
}
44+
}

runtime/src/main/java/io/quarkiverse/openapi/generator/providers/ApiKeyAuthenticationProvider.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import javax.ws.rs.core.Cookie;
99
import javax.ws.rs.core.UriBuilder;
1010

11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
1114
import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig;
1215
import io.quarkiverse.openapi.generator.OpenApiGeneratorException;
1316

@@ -16,6 +19,8 @@
1619
*/
1720
public class ApiKeyAuthenticationProvider extends AbstractAuthProvider {
1821

22+
private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyAuthenticationProvider.class);
23+
1924
static final String API_KEY = "api-key";
2025

2126
private final ApiKeyIn apiKeyIn;
@@ -41,13 +46,17 @@ public void filter(ClientRequestContext requestContext) throws IOException {
4146
requestContext.getCookies().put(apiKeyName, new Cookie(apiKeyName, getApiKey()));
4247
break;
4348
case header:
44-
requestContext.getHeaders().add(apiKeyName, getApiKey());
49+
requestContext.getHeaders().putSingle(apiKeyName, getApiKey());
4550
break;
4651
}
4752
}
4853

4954
private String getApiKey() {
50-
return getAuthConfigParam(API_KEY, "");
55+
final String key = getAuthConfigParam(API_KEY, "");
56+
if (key.isEmpty()) {
57+
LOGGER.warn("configured " + API_KEY + " property (see application.properties) is empty. hint: configure it.");
58+
}
59+
return key;
5160
}
5261

5362
private void validateConfig() {

0 commit comments

Comments
 (0)