Skip to content

Commit d2dd0ce

Browse files
Configure authentication at Build time (#795)
Co-authored-by: Ricardo Zanini <[email protected]>
1 parent 029828b commit d2dd0ce

File tree

38 files changed

+1070
-314
lines changed

38 files changed

+1070
-314
lines changed

client/deployment/pom.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,21 @@
118118
<artifactId>quarkus-openapi-generator-test-utils</artifactId>
119119
<scope>test</scope>
120120
</dependency>
121-
121+
<dependency>
122+
<groupId>io.quarkus</groupId>
123+
<artifactId>quarkus-rest-client-oidc-filter</artifactId>
124+
<scope>test</scope>
125+
</dependency>
126+
<dependency>
127+
<groupId>jakarta.ws.rs</groupId>
128+
<artifactId>jakarta.ws.rs-api</artifactId>
129+
<scope>test</scope>
130+
</dependency>
131+
<dependency>
132+
<groupId>org.eclipse.microprofile.rest.client</groupId>
133+
<artifactId>microprofile-rest-client-api</artifactId>
134+
<scope>test</scope>
135+
</dependency>
122136
<dependency>
123137
<groupId>io.quarkus</groupId>
124138
<artifactId>quarkus-junit5-internal</artifactId>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.quarkiverse.openapi.generator.deployment;
2+
3+
import io.quarkus.builder.item.MultiBuildItem;
4+
5+
public final class AuthProviderBuildItem extends MultiBuildItem {
6+
7+
final String openApiSpecId;
8+
final String name;
9+
10+
AuthProviderBuildItem(String openApiSpecId, String name) {
11+
this.openApiSpecId = openApiSpecId;
12+
this.name = name;
13+
}
14+
15+
public String getOpenApiSpecId() {
16+
return openApiSpecId;
17+
}
18+
19+
public String getName() {
20+
return name;
21+
}
22+
}
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package io.quarkiverse.openapi.generator.deployment;
2+
3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.stream.Collectors;
7+
8+
import jakarta.enterprise.context.Dependent;
9+
import jakarta.enterprise.inject.Instance;
10+
11+
import org.jboss.jandex.AnnotationInstance;
12+
import org.jboss.jandex.ClassType;
13+
import org.jboss.jandex.DotName;
14+
import org.jboss.jandex.ParameterizedType;
15+
16+
import io.quarkiverse.openapi.generator.AuthName;
17+
import io.quarkiverse.openapi.generator.AuthenticationRecorder;
18+
import io.quarkiverse.openapi.generator.ClassicOidcClientRequestFilterDelegate;
19+
import io.quarkiverse.openapi.generator.OidcClient;
20+
import io.quarkiverse.openapi.generator.OpenApiGeneratorConfig;
21+
import io.quarkiverse.openapi.generator.OpenApiSpec;
22+
import io.quarkiverse.openapi.generator.ReactiveOidcClientRequestFilterDelegate;
23+
import io.quarkiverse.openapi.generator.markers.ApiKeyAuthenticationMarker;
24+
import io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker;
25+
import io.quarkiverse.openapi.generator.markers.BearerAuthenticationMarker;
26+
import io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker;
27+
import io.quarkiverse.openapi.generator.markers.OperationMarker;
28+
import io.quarkiverse.openapi.generator.providers.ApiKeyIn;
29+
import io.quarkiverse.openapi.generator.providers.AuthProvider;
30+
import io.quarkiverse.openapi.generator.providers.CompositeAuthenticationProvider;
31+
import io.quarkiverse.openapi.generator.providers.OAuth2AuthenticationProvider.OidcClientRequestFilterDelegate;
32+
import io.quarkiverse.openapi.generator.providers.OperationAuthInfo;
33+
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
34+
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
35+
import io.quarkus.deployment.Capabilities;
36+
import io.quarkus.deployment.Capability;
37+
import io.quarkus.deployment.annotations.BuildProducer;
38+
import io.quarkus.deployment.annotations.BuildStep;
39+
import io.quarkus.deployment.annotations.ExecutionTime;
40+
import io.quarkus.deployment.annotations.Record;
41+
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
42+
import io.quarkus.deployment.builditem.FeatureBuildItem;
43+
44+
public class GeneratorProcessor {
45+
46+
private static final String FEATURE = "openapi-generator";
47+
private static final DotName OAUTH_AUTHENTICATION_MARKER = DotName.createSimple(OauthAuthenticationMarker.class);
48+
private static final DotName BASIC_AUTHENTICATION_MARKER = DotName.createSimple(BasicAuthenticationMarker.class);
49+
private static final DotName BEARER_AUTHENTICATION_MARKER = DotName.createSimple(BearerAuthenticationMarker.class);
50+
private static final DotName API_KEY_AUTHENTICATION_MARKER = DotName.createSimple(ApiKeyAuthenticationMarker.class);
51+
52+
private static final DotName OPERATION_MARKER = DotName.createSimple(OperationMarker.class);
53+
54+
@BuildStep
55+
FeatureBuildItem feature() {
56+
return new FeatureBuildItem(FEATURE);
57+
}
58+
59+
@BuildStep
60+
void additionalBean(
61+
Capabilities capabilities,
62+
BuildProducer<AdditionalBeanBuildItem> producer) {
63+
64+
if (capabilities.isPresent(Capability.REST_CLIENT_REACTIVE)) {
65+
producer.produce(
66+
AdditionalBeanBuildItem.builder().addBeanClass(ReactiveOidcClientRequestFilterDelegate.class)
67+
.setDefaultScope(DotName.createSimple(Dependent.class))
68+
.setUnremovable()
69+
.build());
70+
} else {
71+
producer.produce(
72+
AdditionalBeanBuildItem.builder().addBeanClass(ClassicOidcClientRequestFilterDelegate.class)
73+
.setDefaultScope(DotName.createSimple(Dependent.class))
74+
.setUnremovable()
75+
.build());
76+
}
77+
}
78+
79+
@BuildStep
80+
@Record(ExecutionTime.STATIC_INIT)
81+
void produceCompositeProviders(AuthenticationRecorder recorder,
82+
List<AuthProviderBuildItem> authProviders,
83+
BuildProducer<SyntheticBeanBuildItem> beanProducer) {
84+
Map<String, List<AuthProviderBuildItem>> providersBySpec = authProviders.stream()
85+
.collect(Collectors.groupingBy(AuthProviderBuildItem::getOpenApiSpecId));
86+
providersBySpec.forEach((openApiSpecId, providers) -> {
87+
beanProducer.produce(SyntheticBeanBuildItem.configure(CompositeAuthenticationProvider.class)
88+
.scope(Dependent.class)
89+
.addQualifier()
90+
.annotation(OpenApiSpec.class)
91+
.addValue("openApiSpecId", openApiSpecId)
92+
.done()
93+
.addInjectionPoint(
94+
ParameterizedType.create(Instance.class, ClassType.create(AuthProvider.class)),
95+
AnnotationInstance.builder(OpenApiSpec.class)
96+
.add("openApiSpecId", openApiSpecId)
97+
.build())
98+
.createWith(recorder.recordCompositeProvider(openApiSpecId))
99+
.done());
100+
101+
});
102+
}
103+
104+
@BuildStep
105+
@Record(ExecutionTime.STATIC_INIT)
106+
void produceOauthAuthentication(CombinedIndexBuildItem beanArchiveBuildItem,
107+
BuildProducer<AuthProviderBuildItem> authenticationProviders,
108+
BuildProducer<SyntheticBeanBuildItem> beanProducer,
109+
AuthenticationRecorder recorder) {
110+
Collection<AnnotationInstance> authenticationMarkers = beanArchiveBuildItem.getIndex()
111+
.getAnnotationsWithRepeatable(OAUTH_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex());
112+
113+
Map<String, List<AnnotationInstance>> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem);
114+
115+
for (AnnotationInstance authenticationMarker : authenticationMarkers) {
116+
String name = authenticationMarker.value("name").asString();
117+
String openApiSpecId = authenticationMarker.value("openApiSpecId").asString();
118+
List<OperationAuthInfo> operations = getOperations(operationsBySpec, openApiSpecId, name);
119+
authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name));
120+
beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class)
121+
.scope(Dependent.class)
122+
.addQualifier()
123+
.annotation(AuthName.class)
124+
.addValue("name", name)
125+
.done()
126+
.addQualifier()
127+
.annotation(OpenApiSpec.class)
128+
.addValue("openApiSpecId", openApiSpecId)
129+
.done()
130+
.addInjectionPoint(ClassType.create(OidcClientRequestFilterDelegate.class),
131+
AnnotationInstance.builder(OidcClient.class)
132+
.add("name", sanitizeAuthName(name))
133+
.build())
134+
.createWith(recorder.recordOauthAuthProvider(
135+
sanitizeAuthName(name),
136+
openApiSpecId,
137+
operations))
138+
.unremovable()
139+
.done());
140+
}
141+
}
142+
143+
@BuildStep
144+
@Record(ExecutionTime.STATIC_INIT)
145+
void produceBasicAuthentication(CombinedIndexBuildItem beanArchiveBuildItem,
146+
BuildProducer<AuthProviderBuildItem> authenticationProviders,
147+
BuildProducer<SyntheticBeanBuildItem> beanProducer,
148+
AuthenticationRecorder recorder) {
149+
150+
Collection<AnnotationInstance> authenticationMarkers = beanArchiveBuildItem.getIndex()
151+
.getAnnotationsWithRepeatable(BASIC_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex());
152+
153+
Map<String, List<AnnotationInstance>> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem);
154+
for (AnnotationInstance authenticationMarker : authenticationMarkers) {
155+
String name = authenticationMarker.value("name").asString();
156+
String openApiSpecId = authenticationMarker.value("openApiSpecId").asString();
157+
158+
List<OperationAuthInfo> operations = getOperations(operationsBySpec, openApiSpecId, name);
159+
160+
authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name));
161+
162+
beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class)
163+
.scope(Dependent.class)
164+
.addQualifier()
165+
.annotation(AuthName.class)
166+
.addValue("name", name)
167+
.done()
168+
.addQualifier()
169+
.annotation(OpenApiSpec.class)
170+
.addValue("openApiSpecId", openApiSpecId)
171+
.done()
172+
.createWith(recorder.recordBasicAuthProvider(
173+
sanitizeAuthName(name),
174+
openApiSpecId,
175+
operations))
176+
.unremovable()
177+
.done());
178+
}
179+
}
180+
181+
@BuildStep
182+
@Record(ExecutionTime.STATIC_INIT)
183+
void produceBearerAuthentication(CombinedIndexBuildItem beanArchiveBuildItem,
184+
BuildProducer<AuthProviderBuildItem> authenticationProviders,
185+
BuildProducer<SyntheticBeanBuildItem> beanProducer,
186+
AuthenticationRecorder recorder) {
187+
188+
Collection<AnnotationInstance> authenticationMarkers = beanArchiveBuildItem.getIndex()
189+
.getAnnotationsWithRepeatable(BEARER_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex());
190+
191+
Map<String, List<AnnotationInstance>> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem);
192+
for (AnnotationInstance authenticationMarker : authenticationMarkers) {
193+
String name = authenticationMarker.value("name").asString();
194+
String scheme = authenticationMarker.value("scheme").asString();
195+
String openApiSpecId = authenticationMarker.value("openApiSpecId").asString();
196+
197+
List<OperationAuthInfo> operations = getOperations(operationsBySpec, openApiSpecId, name);
198+
authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name));
199+
beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class)
200+
.scope(Dependent.class)
201+
.addQualifier()
202+
.annotation(AuthName.class)
203+
.addValue("name", name)
204+
.done()
205+
.addQualifier()
206+
.annotation(OpenApiSpec.class)
207+
.addValue("openApiSpecId", openApiSpecId)
208+
.done()
209+
.createWith(recorder.recordBearerAuthProvider(
210+
sanitizeAuthName(name),
211+
scheme,
212+
openApiSpecId,
213+
operations))
214+
.unremovable()
215+
.done());
216+
217+
}
218+
}
219+
220+
@BuildStep
221+
@Record(ExecutionTime.STATIC_INIT)
222+
void produceApiKeyAuthentication(CombinedIndexBuildItem beanArchiveBuildItem,
223+
BuildProducer<AuthProviderBuildItem> authenticationProviders,
224+
BuildProducer<SyntheticBeanBuildItem> beanProducer,
225+
AuthenticationRecorder recorder) {
226+
227+
Collection<AnnotationInstance> authenticationMarkers = beanArchiveBuildItem.getIndex()
228+
.getAnnotationsWithRepeatable(API_KEY_AUTHENTICATION_MARKER, beanArchiveBuildItem.getIndex());
229+
Map<String, List<AnnotationInstance>> operationsBySpec = getOperationsBySpec(beanArchiveBuildItem);
230+
for (AnnotationInstance authenticationMarker : authenticationMarkers) {
231+
String name = authenticationMarker.value("name").asString();
232+
String openApiSpecId = authenticationMarker.value("openApiSpecId").asString();
233+
String apiKeyName = authenticationMarker.value("apiKeyName").asString();
234+
ApiKeyIn apiKeyIn = ApiKeyIn.valueOf(authenticationMarker.value("apiKeyIn").asEnum());
235+
236+
List<OperationAuthInfo> operations = getOperations(operationsBySpec, openApiSpecId, name);
237+
238+
authenticationProviders.produce(new AuthProviderBuildItem(openApiSpecId, name));
239+
240+
beanProducer.produce(SyntheticBeanBuildItem.configure(AuthProvider.class)
241+
.scope(Dependent.class)
242+
.addQualifier()
243+
.annotation(AuthName.class)
244+
.addValue("name", name)
245+
.done()
246+
.addQualifier()
247+
.annotation(OpenApiSpec.class)
248+
.addValue("openApiSpecId", openApiSpecId)
249+
.done()
250+
.createWith(recorder.recordApiKeyAuthProvider(
251+
sanitizeAuthName(name),
252+
openApiSpecId,
253+
apiKeyIn,
254+
apiKeyName,
255+
operations))
256+
.unremovable()
257+
.done());
258+
}
259+
260+
}
261+
262+
private static String sanitizeAuthName(String schemeName) {
263+
return OpenApiGeneratorConfig.getSanitizedSecuritySchemeName(schemeName);
264+
}
265+
266+
private static Map<String, List<AnnotationInstance>> getOperationsBySpec(CombinedIndexBuildItem beanArchiveBuildItem) {
267+
Map<String, List<AnnotationInstance>> operationsBySpec = beanArchiveBuildItem.getIndex()
268+
.getAnnotationsWithRepeatable(OPERATION_MARKER, beanArchiveBuildItem.getIndex()).stream()
269+
.collect(Collectors.groupingBy(
270+
marker -> marker.value("openApiSpecId").asString() + "_" + marker.value("name").asString()));
271+
return operationsBySpec;
272+
}
273+
274+
private static List<OperationAuthInfo> getOperations(Map<String, List<AnnotationInstance>> operationsBySpec,
275+
String openApiSpecId, String name) {
276+
return operationsBySpec.getOrDefault(openApiSpecId + "_" + name, List.of()).stream()
277+
.map(op -> OperationAuthInfo.builder()
278+
.withPath(op.value("path").asString())
279+
.withId(op.value("operationId").asString())
280+
.withMethod(op.value("method").asString())
281+
.build())
282+
.collect(Collectors.toList());
283+
}
284+
}

client/deployment/src/main/resources/templates/libraries/microprofile/api.qute

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public interface {classname} {
3333
/**
3434
{#include operationJavaDoc.qute op=op/}
3535
*/
36+
{#if op.hasAuthMethods}
37+
{#for auth in op.authMethods}
38+
@io.quarkiverse.openapi.generator.markers.OperationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}", operationId="{op.operationId}", method="{op.httpMethod}", path="{contextPath}{commonPath}{op.path.orEmpty}")
39+
{/for}
40+
{#else}
41+
@io.quarkiverse.openapi.generator.markers.OperationMarker(name="{defaultSecurityScheme}", openApiSpecId="{quarkus-generator.openApiSpecId}", operationId="{op.operationId}", method="{op.httpMethod}", path="{contextPath}{commonPath}{op.path.orEmpty}")
42+
{/if}
3643
@jakarta.ws.rs.{op.httpMethod}
3744
{#if op.subresourceOperation}
3845
@jakarta.ws.rs.Path("{op.path}")

0 commit comments

Comments
 (0)