Skip to content

Commit 3a9b53d

Browse files
committed
Fix #933 - Filter oauth security definitions from spec when generating
Signed-off-by: Ricardo Zanini <[email protected]>
1 parent 64835e4 commit 3a9b53d

File tree

4 files changed

+131
-2
lines changed

4 files changed

+131
-2
lines changed

client/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/template/OpenApiNamespaceResolver.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
import java.io.File;
44
import java.nio.file.Path;
5+
import java.util.ArrayList;
6+
import java.util.Collections;
57
import java.util.HashMap;
8+
import java.util.LinkedHashMap;
9+
import java.util.List;
610
import java.util.concurrent.CompletableFuture;
711
import java.util.concurrent.CompletionStage;
812
import java.util.concurrent.ExecutionException;
13+
import java.util.stream.Collectors;
914

15+
import org.openapitools.codegen.CodegenSecurity;
1016
import org.openapitools.codegen.model.OperationMap;
1117

1218
import io.quarkiverse.openapi.generator.deployment.codegen.OpenApiGeneratorOutputPaths;
@@ -58,6 +64,26 @@ public boolean hasAuthMethods(OperationMap operations) {
5864
return operations != null && operations.getOperation().stream().anyMatch(operation -> operation.hasAuthMethods);
5965
}
6066

67+
/**
68+
* Ignore the OAuth flows by filtering every oauth instance by name. The inner openapi-generator library duplicates the
69+
* OAuth instances per flow in the openapi spec.
70+
* So a specification file with more than one flow defined has two entries in the list. For now, we do not use this
71+
* information in runtime so it can be safely filtered and ignored.
72+
*
73+
* @param oauthOperations passed through the Qute template
74+
* @see "resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute"
75+
* @return The list filtered by unique auth name
76+
*/
77+
public List<CodegenSecurity> getUniqueOAuthOperations(ArrayList<CodegenSecurity> oauthOperations) {
78+
if (oauthOperations != null) {
79+
return oauthOperations.stream()
80+
.collect(Collectors.toMap(security -> security.name, security -> security,
81+
(existing, replacement) -> existing, LinkedHashMap::new))
82+
.values().stream().toList();
83+
}
84+
return Collections.emptyList();
85+
}
86+
6187
@Override
6288
public CompletionStage<Object> resolve(EvalContext context) {
6389
try {

client/deployment/src/main/resources/templates/libraries/microprofile/auth/compositeAuthenticationProvider.qute

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package {apiPackage}.auth;
22

33
@jakarta.annotation.Priority(jakarta.ws.rs.Priorities.AUTHENTICATION)
4-
{#for auth in oauthMethods.orEmpty}
5-
@io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker(name="{auth.name}", openApiSpecId="{configKey}")
4+
{#if oauthMethods.orEmpty.size > 0}
5+
{#for auth in openapi:getUniqueOAuthOperations(oauthMethods)}
6+
@io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}")
67
{/for}
8+
{/if}
79
{#for auth in httpBasicMethods.orEmpty}
810
@io.quarkiverse.openapi.generator.markers.BasicAuthenticationMarker(name="{auth.name}", openApiSpecId="{quarkus-generator.openApiSpecId}")
911
{/for}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import com.github.javaparser.ast.body.Parameter;
3434
import com.github.javaparser.ast.body.VariableDeclarator;
3535
import com.github.javaparser.ast.expr.AnnotationExpr;
36+
import com.github.javaparser.ast.expr.Expression;
37+
import com.github.javaparser.ast.expr.MemberValuePair;
3638
import com.github.javaparser.ast.expr.SimpleName;
3739
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
3840
import com.github.javaparser.ast.nodeTypes.NodeWithName;
@@ -50,6 +52,38 @@ private static Optional<MethodDeclaration> getMethodDeclarationByIdentifier(List
5052
return methodDeclarations.stream().filter(md -> md.getName().getIdentifier().equals(methodName)).findAny();
5153
}
5254

55+
@Test
56+
void verifyOAuthDuplicateAnnotationOnCompositeAuthProvider() throws URISyntaxException, FileNotFoundException {
57+
OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("issue-933-security.yaml");
58+
final List<File> generatedFiles = generatorWrapper.generate("org.issue933");
59+
60+
assertNotNull(generatedFiles);
61+
assertFalse(generatedFiles.isEmpty());
62+
63+
final Optional<File> authProviderFile = generatedFiles.stream()
64+
.filter(f -> f.getName().endsWith("CompositeAuthenticationProvider.java")).findFirst();
65+
assertThat(authProviderFile).isPresent();
66+
67+
CompilationUnit compilationUnit = StaticJavaParser.parse(authProviderFile.orElseThrow());
68+
// Get the class declaration
69+
ClassOrInterfaceDeclaration classDeclaration = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class)
70+
.orElseThrow(() -> new AssertionError("Class not found in the file"));
71+
72+
// Collect all OauthAuthenticationMarker annotations
73+
long oauthAnnotationsCount = classDeclaration.getAnnotations().stream()
74+
.filter(annotation -> annotation.getNameAsString()
75+
.equals("io.quarkiverse.openapi.generator.markers.OauthAuthenticationMarker"))
76+
.filter(Expression::isNormalAnnotationExpr)
77+
.filter(annotation -> annotation
78+
.findFirst(MemberValuePair.class,
79+
pair -> pair.getNameAsString().equals("name") && pair.getValue().toString().equals("\"oauth\""))
80+
.isPresent())
81+
.count();
82+
83+
// Assert that there's exactly one annotation with name=oauth
84+
assertThat(oauthAnnotationsCount).isEqualTo(1);
85+
}
86+
5387
@Test
5488
void verifyDiscriminatorGeneration() throws java.net.URISyntaxException, FileNotFoundException {
5589
OpenApiClientGeneratorWrapper generatorWrapper = createGeneratorWrapper("issue-852.json");
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
20+
---
21+
openapi: 3.0.3
22+
info:
23+
title: Generated API
24+
version: "1.0"
25+
paths:
26+
/:
27+
post:
28+
operationId: doOperation
29+
security:
30+
- client_id: [ ]
31+
- oauth: [ read, write ]
32+
- bearerAuth: [ ]
33+
requestBody:
34+
content:
35+
application/json:
36+
schema:
37+
$ref: '#/components/schemas/MultiplicationOperation'
38+
responses:
39+
"200":
40+
description: OK
41+
components:
42+
schemas:
43+
MultiplicationOperation:
44+
type: object
45+
securitySchemes:
46+
client_id:
47+
type: apiKey
48+
in: header
49+
name: X-Client-Id
50+
x-key-type: clientId
51+
bearerAuth:
52+
type: http
53+
scheme: bearer
54+
oauth:
55+
type: oauth2
56+
flows:
57+
authorizationCode:
58+
authorizationUrl: https://example.com/oauth/authorize
59+
tokenUrl: https://example.com/oauth/token
60+
scopes:
61+
read: Grants read access
62+
write: Grants write access
63+
admin: Grants read and write access to administrative information
64+
clientCredentials:
65+
tokenUrl: http://localhost:8382/oauth/token
66+
scopes:
67+
read: read

0 commit comments

Comments
 (0)