Skip to content

Commit 42cbc8c

Browse files
authored
Merge pull request #44995 from MikeEdgar/issue-40839
OpenAPI: check super class for inherited auto-security resource methods
2 parents da21df4 + 8f0701f commit 42cbc8c

File tree

6 files changed

+113
-45
lines changed

6 files changed

+113
-45
lines changed

extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ private Map<String, List<String>> getRolesAllowedMethodReferences(OpenApiFiltere
595595
.flatMap(Collection::stream)
596596
.flatMap(t -> getMethods(t, index))
597597
.collect(Collectors.toMap(
598-
e -> createUniqueMethodReference(e.getKey().declaringClass(), e.getKey()),
598+
e -> createUniqueMethodReference(e.getKey().classInfo(), e.getKey().method()),
599599
e -> List.of(e.getValue().value().asStringArray()),
600600
(v1, v2) -> {
601601
if (!Objects.equals(v1, v2)) {
@@ -615,7 +615,7 @@ private List<String> getPermissionsAllowedMethodReferences(
615615
.getAnnotations(DotName.createSimple(PermissionsAllowed.class))
616616
.stream()
617617
.flatMap(t -> getMethods(t, index))
618-
.map(e -> createUniqueMethodReference(e.getKey().declaringClass(), e.getKey()))
618+
.map(e -> createUniqueMethodReference(e.getKey().classInfo(), e.getKey().method()))
619619
.distinct()
620620
.toList();
621621
}
@@ -626,18 +626,18 @@ private List<String> getAuthenticatedMethodReferences(OpenApiFilteredIndexViewBu
626626
.getAnnotations(DotName.createSimple(Authenticated.class.getName()))
627627
.stream()
628628
.flatMap(t -> getMethods(t, index))
629-
.map(e -> createUniqueMethodReference(e.getKey().declaringClass(), e.getKey()))
629+
.map(e -> createUniqueMethodReference(e.getKey().classInfo(), e.getKey().method()))
630630
.distinct()
631631
.toList();
632632
}
633633

634-
private static Stream<Map.Entry<MethodInfo, AnnotationInstance>> getMethods(AnnotationInstance annotation,
634+
private static Stream<Map.Entry<ClassAndMethod, AnnotationInstance>> getMethods(AnnotationInstance annotation,
635635
IndexView index) {
636636
if (annotation.target().kind() == Kind.METHOD) {
637637
MethodInfo method = annotation.target().asMethod();
638638

639639
if (isValidOpenAPIMethodForAutoAdd(method)) {
640-
return Stream.of(Map.entry(method, annotation));
640+
return Stream.of(Map.entry(new ClassAndMethod(method.declaringClass(), method), annotation));
641641
}
642642
} else if (annotation.target().kind() == Kind.CLASS) {
643643
ClassInfo classInfo = annotation.target().asClass();
@@ -647,7 +647,23 @@ private static Stream<Map.Entry<MethodInfo, AnnotationInstance>> getMethods(Anno
647647
// drop methods that specify the annotation directly
648648
.filter(method -> !method.hasDeclaredAnnotation(annotation.name()))
649649
.filter(method -> isValidOpenAPIMethodForAutoAdd(method))
650-
.map(method -> Map.entry(method, annotation));
650+
.map(method -> {
651+
final ClassInfo resourceClass;
652+
653+
if (method.declaringClass().isInterface()) {
654+
/*
655+
* smallrye-open-api processes interfaces as the resource class as long as
656+
* there is a concrete implementation available. Using the interface method's
657+
* declaring class here allows us to match on the hash that will be set by
658+
* #handleOperation during scanning.
659+
*/
660+
resourceClass = method.declaringClass();
661+
} else {
662+
resourceClass = classInfo;
663+
}
664+
665+
return Map.entry(new ClassAndMethod(resourceClass, method), annotation);
666+
});
651667
}
652668

653669
return Stream.empty();
@@ -678,7 +694,7 @@ private Map<String, ClassAndMethod> getClassNamesMethodReferences(
678694
.getAllKnownSubclasses(declaringClass.name()), classNames);
679695
} else {
680696
String ref = createUniqueMethodReference(declaringClass, method);
681-
classNames.put(ref, new ClassAndMethod(declaringClass.simpleName(), method.name()));
697+
classNames.put(ref, new ClassAndMethod(declaringClass, method));
682698
}
683699
}
684700
}
@@ -688,16 +704,15 @@ private Map<String, ClassAndMethod> getClassNamesMethodReferences(
688704
void addMethodImplementationClassNames(MethodInfo method, Type[] params, Collection<ClassInfo> classes,
689705
Map<String, ClassAndMethod> classNames) {
690706
for (ClassInfo impl : classes) {
691-
String simpleClassName = impl.simpleName();
692707
MethodInfo implMethod = impl.method(method.name(), params);
693708

694709
if (implMethod != null) {
695710
classNames.put(createUniqueMethodReference(impl, implMethod),
696-
new ClassAndMethod(simpleClassName, implMethod.name()));
711+
new ClassAndMethod(impl, implMethod));
697712
}
698713

699714
classNames.put(createUniqueMethodReference(impl, method),
700-
new ClassAndMethod(simpleClassName, method.name()));
715+
new ClassAndMethod(impl, method));
701716
}
702717
}
703718

@@ -769,7 +784,6 @@ private static boolean isOpenAPIEndpoint(MethodInfo method) {
769784
}
770785

771786
private static List<MethodInfo> getMethods(ClassInfo declaringClass, IndexView index) {
772-
773787
List<MethodInfo> methods = new ArrayList<>();
774788
methods.addAll(declaringClass.methods());
775789

@@ -782,8 +796,17 @@ private static List<MethodInfo> getMethods(ClassInfo declaringClass, IndexView i
782796
}
783797
}
784798
}
785-
return methods;
786799

800+
DotName superClassName = declaringClass.superName();
801+
if (superClassName != null) {
802+
ClassInfo superClass = index.getClassByName(superClassName);
803+
804+
if (superClass != null) {
805+
methods.addAll(getMethods(superClass, index));
806+
}
807+
}
808+
809+
return methods;
787810
}
788811

789812
private static Set<DotName> getAllOpenAPIEndpoints() {
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package io.quarkus.smallrye.openapi.deployment.filter;
22

3-
public record ClassAndMethod(String className, String methodName) {
3+
import org.jboss.jandex.ClassInfo;
4+
import org.jboss.jandex.MethodInfo;
5+
6+
public record ClassAndMethod(ClassInfo classInfo, MethodInfo method) {
47

58
}

extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/filter/OperationFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ private void maybeSetSummaryAndTag(Operation operation, String methodRef) {
106106

107107
if (doAutoOperation && operation.getSummary() == null) {
108108
// Auto add a summary
109-
operation.setSummary(capitalizeFirstLetter(splitCamelCase(classMethod.methodName())));
109+
operation.setSummary(capitalizeFirstLetter(splitCamelCase(classMethod.method().name())));
110110
}
111111

112112
if (doAutoTag && (operation.getTags() == null || operation.getTags().isEmpty())) {
113-
operation.addTag(splitCamelCase(classMethod.className()));
113+
operation.addTag(splitCamelCase(classMethod.classInfo().simpleName()));
114114
}
115115
}
116116

extensions/smallrye-openapi/deployment/src/test/java/io/quarkus/smallrye/openapi/test/jaxrs/AutoSecurityAuthenticateTestCase.java

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.quarkus.smallrye.openapi.test.jaxrs;
22

3+
import static org.hamcrest.Matchers.notNullValue;
4+
35
import org.hamcrest.Matchers;
46
import org.jboss.shrinkwrap.api.asset.StringAsset;
57
import org.junit.jupiter.api.Test;
@@ -8,50 +10,50 @@
810
import io.quarkus.test.QuarkusUnitTest;
911
import io.restassured.RestAssured;
1012

11-
public class AutoSecurityAuthenticateTestCase {
13+
class AutoSecurityAuthenticateTestCase {
14+
1215
@RegisterExtension
1316
static QuarkusUnitTest runner = new QuarkusUnitTest()
1417
.withApplicationRoot((jar) -> jar
1518
.addClasses(ResourceBean2.class, OpenApiResourceAuthenticatedAtClassLevel.class,
19+
OpenApiResourceAuthenticatedInherited1.class, OpenApiResourceAuthenticatedInherited2.class,
1620
OpenApiResourceAuthenticatedAtMethodLevel.class, OpenApiResourceAuthenticatedAtMethodLevel2.class)
1721
.addAsResource(
18-
new StringAsset("quarkus.smallrye-openapi.security-scheme=jwt\n"
19-
+ "quarkus.smallrye-openapi.security-scheme-name=JWTCompanyAuthentication\n"
20-
+ "quarkus.smallrye-openapi.security-scheme-description=JWT Authentication"),
21-
22+
new StringAsset("""
23+
quarkus.smallrye-openapi.security-scheme=jwt
24+
quarkus.smallrye-openapi.security-scheme-name=JWTCompanyAuthentication
25+
quarkus.smallrye-openapi.security-scheme-description=JWT Authentication
26+
"""),
2227
"application.properties"));
2328

2429
@Test
25-
public void testAutoSecurityRequirement() {
30+
void testAutoSecurityRequirement() {
2631
RestAssured.given().header("Accept", "application/json")
2732
.when().get("/q/openapi")
2833
.then()
2934
.log().body()
3035
.and()
31-
.body("components.securitySchemes.JWTCompanyAuthentication", Matchers.hasEntry("type", "http"))
32-
.and()
33-
.body("components.securitySchemes.JWTCompanyAuthentication",
34-
Matchers.hasEntry("description", "JWT Authentication"))
35-
.and()
36-
.body("components.securitySchemes.JWTCompanyAuthentication", Matchers.hasEntry("scheme", "bearer"))
37-
.and()
38-
.body("components.securitySchemes.JWTCompanyAuthentication", Matchers.hasEntry("bearerFormat", "JWT"))
39-
.and()
40-
.body("paths.'/resource2/test-security/annotated'.get.security.JWTCompanyAuthentication",
41-
Matchers.notNullValue())
42-
.and()
43-
.body("paths.'/resource2/test-security/naked'.get.security.JWTCompanyAuthentication", Matchers.notNullValue())
44-
.and()
45-
.body("paths.'/resource2/test-security/classLevel/1'.get.security.JWTCompanyAuthentication",
46-
Matchers.notNullValue())
47-
.and()
48-
.body("paths.'/resource2/test-security/classLevel/2'.get.security.JWTCompanyAuthentication",
49-
Matchers.notNullValue())
50-
.and()
51-
.body("paths.'/resource2/test-security/classLevel/3'.get.security.MyOwnName",
52-
Matchers.notNullValue())
53-
.and()
54-
.body("paths.'/resource3/test-security/annotated'.get.security.AtClassLevel", Matchers.notNullValue());
36+
.body("components.securitySchemes.JWTCompanyAuthentication", Matchers.allOf(
37+
Matchers.hasEntry("type", "http"),
38+
Matchers.hasEntry("description", "JWT Authentication"),
39+
Matchers.hasEntry("scheme", "bearer"),
40+
Matchers.hasEntry("bearerFormat", "JWT")))
41+
.body("paths.'/resource2/test-security/annotated'.get.security.JWTCompanyAuthentication", notNullValue())
42+
.body("paths.'/resource2/test-security/naked'.get.security.JWTCompanyAuthentication", notNullValue())
43+
.body("paths.'/resource2/test-security/classLevel/1'.get.security.JWTCompanyAuthentication", notNullValue())
44+
.body("paths.'/resource2/test-security/classLevel/2'.get.security.JWTCompanyAuthentication", notNullValue())
45+
.body("paths.'/resource2/test-security/classLevel/3'.get.security.MyOwnName", notNullValue())
46+
.body("paths.'/resource3/test-security/annotated'.get.security.AtClassLevel", notNullValue())
47+
.body("paths.'/resource-inherited1/test-security/classLevel/1'.get.security.JWTCompanyAuthentication",
48+
notNullValue())
49+
.body("paths.'/resource-inherited1/test-security/classLevel/2'.get.security.JWTCompanyAuthentication",
50+
notNullValue())
51+
.body("paths.'/resource-inherited1/test-security/classLevel/3'.get.security.MyOwnName", notNullValue())
52+
.body("paths.'/resource-inherited2/test-security/classLevel/1'.get.security.JWTCompanyAuthentication",
53+
notNullValue())
54+
.body("paths.'/resource-inherited2/test-security/classLevel/2'.get.security.CustomOverride",
55+
notNullValue())
56+
.body("paths.'/resource-inherited2/test-security/classLevel/3'.get.security.MyOwnName", notNullValue());
5557

5658
}
5759

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.smallrye.openapi.test.jaxrs;
2+
3+
import jakarta.ws.rs.Path;
4+
5+
import org.eclipse.microprofile.openapi.annotations.servers.Server;
6+
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
7+
8+
import io.quarkus.security.Authenticated;
9+
10+
@Path("/resource-inherited1")
11+
@Tag(name = "test")
12+
@Server(url = "serverUrl")
13+
@Authenticated
14+
public class OpenApiResourceAuthenticatedInherited1 extends OpenApiResourceAuthenticatedAtClassLevel {
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.quarkus.smallrye.openapi.test.jaxrs;
2+
3+
import jakarta.ws.rs.GET;
4+
import jakarta.ws.rs.Path;
5+
6+
import org.eclipse.microprofile.openapi.annotations.security.SecurityRequirement;
7+
import org.eclipse.microprofile.openapi.annotations.servers.Server;
8+
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
9+
10+
import io.quarkus.security.Authenticated;
11+
12+
@Path("/resource-inherited2")
13+
@Tag(name = "test")
14+
@Server(url = "serverUrl")
15+
@Authenticated
16+
public class OpenApiResourceAuthenticatedInherited2 extends OpenApiResourceAuthenticatedInherited1 {
17+
18+
@GET
19+
@Path("/test-security/classLevel/2")
20+
@SecurityRequirement(name = "CustomOverride")
21+
public String secureEndpoint2() {
22+
return "secret";
23+
}
24+
25+
}

0 commit comments

Comments
 (0)