Skip to content

Commit daa7c59

Browse files
committed
Add support to OAuth2
Signed-off-by: Ricardo Zanini <[email protected]>
1 parent 04563fb commit daa7c59

File tree

25 files changed

+632
-80
lines changed

25 files changed

+632
-80
lines changed

deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/QuarkusJavaClientCodegen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ public void processOpts() {
3737
private void replaceWithQuarkusTemplateFiles() {
3838
supportingFiles.clear();
3939

40-
// TODO: add others as we go
4140
if (ProcessUtils.hasHttpBasicMethods(this.openAPI) ||
4241
ProcessUtils.hasApiKeyMethods(this.openAPI) ||
43-
ProcessUtils.hasHttpBearerMethods(this.openAPI)) {
42+
ProcessUtils.hasHttpBearerMethods(this.openAPI) ||
43+
ProcessUtils.hasOAuthMethods(this.openAPI)) {
4444
supportingFiles.add(
4545
new SupportingFile("auth/compositeAuthenticationProvider.qute",
4646
authFileFolder(),

deployment/src/main/resources/templates/auth/compositeAuthenticationProvider.qute

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import io.quarkiverse.openapi.generator.providers.ApiKeyIn;
1515
{#if hasHttpBearerMethods}
1616
import io.quarkiverse.openapi.generator.providers.BearerAuthenticationProvider;
1717
{/if}
18+
{#if hasOAuthMethods}
19+
import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider;
20+
{/if}
1821
import io.quarkiverse.openapi.generator.providers.AbstractCompositeAuthenticationProvider;
22+
import io.quarkiverse.openapi.generator.providers.OperationAuthInfo;
1923

2024
@Priority(Priorities.AUTHENTICATION)
2125
public class CompositeAuthenticationProvider extends AbstractCompositeAuthenticationProvider {
@@ -25,17 +29,114 @@ public class CompositeAuthenticationProvider extends AbstractCompositeAuthentica
2529

2630
@PostConstruct
2731
public void init() {
28-
{#for auth in httpBasicMethods.orEmpty}this.addAuthenticationProvider(new BasicAuthenticationProvider("{auth.name}", authConfig));{/for}
29-
{#for auth in httpBearerMethods.orEmpty}this.addAuthenticationProvider(new BearerAuthenticationProvider("{auth.name}", "{auth.scheme}", authConfig));{/for}
32+
{#for auth in httpBasicMethods.orEmpty}
33+
this.addAuthenticationProvider(new BasicAuthenticationProvider("{auth.name}", authConfig)
34+
{#for api in apiInfo.apis}
35+
{#for op in api.operations.operation}
36+
{#if op.hasAuthMethods}
37+
{#for authM in op.authMethods}
38+
{#if authM.name == auth.name}
39+
.addOperation(OperationAuthInfo.builder()
40+
.withPath("{api.contextPath}{api.commonPath{op.path.orEmpty}")
41+
.withId("{op.operationId}")
42+
.withMethod("{op.httpMethod}")
43+
.build())
44+
{/if}
45+
{/for}
46+
{/if}
47+
{/for}
48+
{/for});
49+
{/for}
50+
{#for auth in oauthMethods.orEmpty}
51+
this.addAuthenticationProvider(new OAuth2AuthenticationProvider("{auth.name}")
52+
{#for api in apiInfo.apis}
53+
{#for op in api.operations.operation}
54+
{#if op.hasAuthMethods}
55+
{#for authM in op.authMethods}
56+
{#if authM.name == auth.name}
57+
.addOperation(OperationAuthInfo.builder()
58+
.withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}")
59+
.withId("{op.operationId}")
60+
.withMethod("{op.httpMethod}")
61+
.build())
62+
{/if}
63+
{/for}
64+
{/if}
65+
{/for}
66+
{/for});
67+
{/for}
68+
{#for auth in httpBearerMethods.orEmpty}
69+
this.addAuthenticationProvider(new BearerAuthenticationProvider("{auth.name}", "{auth.scheme}", authConfig)
70+
{#for api in apiInfo.apis}
71+
{#for op in api.operations.operation}
72+
{#if op.hasAuthMethods}
73+
{#for authM in op.authMethods}
74+
{#if authM.name == auth.name}
75+
.addOperation(OperationAuthInfo.builder()
76+
.withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}")
77+
.withId("{op.operationId}")
78+
.withMethod("{op.httpMethod}")
79+
.build())
80+
{/if}
81+
{/for}
82+
{/if}
83+
{/for}
84+
{/for});
85+
{/for}
3086
{#for auth in apiKeyMethods.orEmpty}
3187
{#if auth.isKeyInQuery}
32-
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.query, "{auth.keyParamName}", authConfig));
88+
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.query, "{auth.keyParamName}", authConfig)
89+
{#for api in apiInfo.apis}
90+
{#for op in api.operations.operation}
91+
{#if op.hasAuthMethods}
92+
{#for authM in op.authMethods}
93+
{#if authM.name == auth.name}
94+
.addOperation(OperationAuthInfo.builder()
95+
.withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}")
96+
.withId("{op.operationId}")
97+
.withMethod("{op.httpMethod}")
98+
.build())
99+
{/if}
100+
{/for}
101+
{/if}
102+
{/for}
103+
{/for});
33104
{/if}
34105
{#if auth.isKeyInHeader}
35-
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.header, "{auth.keyParamName}", authConfig));
106+
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.header, "{auth.keyParamName}", authConfig)
107+
{#for api in apiInfo.apis}
108+
{#for op in api.operations.operation}
109+
{#if op.hasAuthMethods}
110+
{#for authM in op.authMethods}
111+
{#if authM.name == auth.name}
112+
.addOperation(OperationAuthInfo.builder()
113+
.withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}")
114+
.withId("{op.operationId}")
115+
.withMethod("{op.httpMethod}")
116+
.build())
117+
{/if}
118+
{/for}
119+
{/if}
120+
{/for}
121+
{/for});
36122
{/if}
37123
{#if auth.isKeyInCookie}
38-
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.cookie, "{auth.keyParamName}", authConfig));
124+
this.addAuthenticationProvider(new ApiKeyAuthenticationProvider("{auth.name}", ApiKeyIn.cookie, "{auth.keyParamName}", authConfig)
125+
{#for api in apiInfo.apis}
126+
{#for op in api.operations.operation}
127+
{#if op.hasAuthMethods}
128+
{#for authM in op.authMethods}
129+
{#if authM.name == auth.name}
130+
.addOperation(OperationAuthInfo.builder()
131+
.withPath("{api.contextPath}{api.commonPath}{op.path.orEmpty}")
132+
.withId("{op.operationId}")
133+
.withMethod("{op.httpMethod}")
134+
.build())
135+
{/if}
136+
{/for}
137+
{/if}
138+
{/for}
139+
{/for});
39140
{/if}
40141
{/for}
41142
}

deployment/src/test/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapperTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,6 @@ void checkAnnotations() throws URISyntaxException, FileNotFoundException {
154154
.forEach(m -> assertThat(m).doesNotHaveCircuitBreakerAnnotation());
155155
}
156156

157-
private List<File> generateRestClientFiles() throws URISyntaxException {
158-
OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("simple-openapi.json")
159-
.withCircuitBreakerConfiguration(Map.of(
160-
"org.openapitools.client.api.DefaultApi", List.of("opThatDoesNotExist", "byeGet")));
161-
162-
return generatorWrapper.generate();
163-
}
164-
165157
@Test
166158
void verifyMultipartFormAnnotationIsGeneratedForParameter() throws URISyntaxException, FileNotFoundException {
167159
List<File> generatedFiles = createGeneratorWrapper("multipart-openapi.yml")
@@ -216,6 +208,14 @@ void verifyMultipartPojoGeneratedAndFieldsHaveAnnotations() throws URISyntaxExce
216208
});
217209
}
218210

211+
private List<File> generateRestClientFiles() throws URISyntaxException {
212+
OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("simple-openapi.json")
213+
.withCircuitBreakerConfiguration(Map.of(
214+
"org.openapitools.client.api.DefaultApi", List.of("opThatDoesNotExist", "byeGet")));
215+
216+
return generatorWrapper.generate();
217+
}
218+
219219
private OpenApiClientGeneratorWrapper createGeneratorWrapper(String specFileName) throws URISyntaxException {
220220
final Path openApiSpec = Path
221221
.of(requireNonNull(this.getClass().getResource(String.format("/openapi/%s", specFileName))).toURI());

integration-tests/example-project/src/test/java/io/quarkiverse/openapi/generator/it/MultipartTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class MultipartTest {
3939

4040
@RestClient
4141
@Inject
42-
private UserProfileDataApi userProfileDataApi;
42+
UserProfileDataApi userProfileDataApi;
4343

4444
@Test
4545
public void testUploadMultipartFormdata(@TempDir Path tempDir) throws IOException {

integration-tests/example-project/src/test/java/io/quarkiverse/openapi/generator/it/OpenWeatherTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.quarkiverse.openapi.generator.it;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
45

56
import javax.inject.Inject;
67

@@ -26,6 +27,9 @@ public class OpenWeatherTest {
2627
@ConfigProperty(name = "org.acme.openapi.weather.security.auth.app_id/api-key")
2728
String apiKey;
2829

30+
@ConfigProperty(name = "org.acme.openapi.weather.api.CurrentWeatherDataApi/mp-rest/url")
31+
String weatherUrl;
32+
2933
@RestClient
3034
@Inject
3135
CurrentWeatherDataApi weatherApi;
@@ -34,8 +38,9 @@ public class OpenWeatherTest {
3438
public void testGetWeatherByLatLon() {
3539
final Model200 model = weatherApi.currentWeatherData("", "", "10", "-10", "", "", "", "");
3640
assertEquals("Nowhere", model.getName());
41+
assertNotNull(weatherUrl);
3742
openWeatherServer.verify(WireMock.getRequestedFor(
38-
WireMock.urlEqualTo("/weather?q=&id=&lat=10&lon=-10&zip=&units=&lang=&mode=&appid=" + apiKey)));
43+
WireMock.urlEqualTo("/data/2.5/weather?q=&id=&lat=10&lon=-10&zip=&units=&lang=&mode=&appid=" + apiKey)));
3944
}
4045

4146
}

integration-tests/example-project/src/test/java/io/quarkiverse/openapi/generator/it/WiremockOpenWeather.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ public Map<String, String> start() {
2020
wireMockServer = new WireMockServer(8888);
2121
wireMockServer.start();
2222

23-
wireMockServer.stubFor(get(urlPathEqualTo("/weather"))
23+
wireMockServer.stubFor(get(urlPathEqualTo("/data/2.5/weather"))
2424
.willReturn(aResponse()
2525
.withStatus(200)
2626
.withHeader("Content-Type", "application/json")
2727
.withBody(
2828
"{\"name\": \"Nowhere\"}")));
2929
return Collections.singletonMap("org.acme.openapi.weather.api.CurrentWeatherDataApi/mp-rest/url",
30-
wireMockServer.baseUrl());
30+
wireMockServer.baseUrl().concat("/data/2.5"));
3131
}
3232

3333
@Override

integration-tests/example-project/src/test/java/io/quarkiverse/openapi/generator/it/WiremockPetStore.java

Lines changed: 0 additions & 46 deletions
This file was deleted.

integration-tests/generation-tests/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
<groupId>org.eclipse.microprofile.fault-tolerance</groupId>
3131
<artifactId>microprofile-fault-tolerance-api</artifactId>
3232
</dependency>
33+
<!-- petstore requires OAuth2 -->
34+
<dependency>
35+
<groupId>io.quarkus</groupId>
36+
<artifactId>quarkus-oidc-client-filter</artifactId>
37+
</dependency>
3338
<dependency>
3439
<groupId>io.quarkiverse.openapi.generator</groupId>
3540
<artifactId>quarkus-openapi-generator</artifactId>
@@ -48,6 +53,11 @@
4853
<artifactId>wiremock-jre8</artifactId>
4954
<scope>test</scope>
5055
</dependency>
56+
<dependency>
57+
<groupId>io.quarkiverse.openapi.generator</groupId>
58+
<artifactId>quarkus-openapi-generator-test-utils</artifactId>
59+
<scope>test</scope>
60+
</dependency>
5161
</dependencies>
5262
<build>
5363
<plugins>
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
1-
2-
1+
quarkus.oidc-client.client-enabled=false
2+
# ${keycloak.url} is provided by test-utils
3+
# petstore_auth is the name of the security scheme from the openapi spec file (petstore.json)
4+
# you can configure your OAuth2 Client the way it fits your use case here.
5+
# see https://quarkus.io/guides/security-openid-connect-client
6+
quarkus.oidc-client.petstore_auth.auth-server-url=${keycloak.url}
7+
quarkus.oidc-client.petstore_auth.discovery-enabled=false
8+
quarkus.oidc-client.petstore_auth.token-path=/tokens
9+
quarkus.oidc-client.petstore_auth.credentials.secret=secret
10+
quarkus.oidc-client.petstore_auth.grant.type=password
11+
quarkus.oidc-client.petstore_auth.grant-options.password.username=alice
12+
quarkus.oidc-client.petstore_auth.grant-options.password.password=alice
13+
quarkus.oidc-client.petstore_auth.client-id=petstore-app

integration-tests/generation-tests/src/test/java/io/quarkiverse/openapi/generator/it/PetStoreTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313

1414
import com.github.tomakehurst.wiremock.WireMockServer;
1515

16+
import io.quarkiverse.openapi.generator.testutils.keycloak.KeycloakRealmResourceManager;
1617
import io.quarkus.test.common.QuarkusTestResource;
1718
import io.quarkus.test.junit.QuarkusTest;
1819

1920
@QuarkusTestResource(WiremockPetStore.class)
21+
@QuarkusTestResource(KeycloakRealmResourceManager.class)
2022
@QuarkusTest
2123
public class PetStoreTest {
2224

0 commit comments

Comments
 (0)