This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git


The following commit(s) were added to refs/heads/tomee-10.x by this push:
     new 6326c23f7d TOMEE-4730 - Apply class-level security annotations of a 
resource superclass (#3081)
6326c23f7d is described below

commit 6326c23f7d3de768b24a1744f517779580b584e8
Author: Richard Zowalla <[email protected]>
AuthorDate: Tue Oct 13 14:56:39 2026 +0200

    TOMEE-4730 - Apply class-level security annotations of a resource 
superclass (#3081)
    
    Security annotations are not inherited, so methods inherited from an
    annotated base class were left unprotected in unannotated subclasses.
    
    (cherry picked from commit 59698bfe1fa93e57ff9d27209aa190811938af5c)
---
 ...PJWTSecurityAnnotationsInterceptorsFeature.java | 91 ++++++++++++----------
 ...SecurityAnnotationsInterceptorsFeatureTest.java | 70 +++++++++++++++++
 2 files changed, 120 insertions(+), 41 deletions(-)

diff --git 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
index b4afaef9ff..cc9f1d6544 100644
--- 
a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
+++ 
b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeature.java
@@ -24,8 +24,8 @@ import jakarta.ws.rs.container.ResourceInfo;
 import jakarta.ws.rs.core.FeatureContext;
 import jakarta.ws.rs.ext.Provider;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -34,6 +34,9 @@ import java.util.Set;
 @Provider
 public class MPJWTSecurityAnnotationsInterceptorsFeature implements 
DynamicFeature {
 
+    private static final List<Class<? extends Annotation>> 
SECURITY_ANNOTATIONS =
+            Arrays.asList(RolesAllowed.class, PermitAll.class, DenyAll.class);
+
     @Override
     public void configure(final ResourceInfo resourceInfo, final 
FeatureContext context) {
 
@@ -48,65 +51,71 @@ public class MPJWTSecurityAnnotationsInterceptorsFeature 
implements DynamicFeatu
 
     }
 
-    private MPJWTSecurityAnnotationsInterceptor 
processSecurityAnnotations(final Class clazz, final Method method) {
-
-        final List<Class<? extends Annotation>[]> classSecurityAnnotations = 
hasClassLevelAnnotations(clazz,
-                RolesAllowed.class, PermitAll.class, DenyAll.class);
+    private MPJWTSecurityAnnotationsInterceptor 
processSecurityAnnotations(final Class<?> resourceClass, final Method method) {
 
-        final List<Class<? extends Annotation>[]> methodSecurityAnnotations = 
hasMethodLevelAnnotations(method,
-                RolesAllowed.class, PermitAll.class, DenyAll.class);
+        if (countSecurityAnnotations(resourceClass) > 1) {
+            throw new IllegalStateException(resourceClass.getName() + " has 
more than one security annotation (RolesAllowed, PermitAll, DenyAll).");
+        }
 
-        if (classSecurityAnnotations.isEmpty() && 
methodSecurityAnnotations.isEmpty()) {
-            return null; // nothing to do
+        final int methodSecurityAnnotations = countSecurityAnnotations(method);
+        if (methodSecurityAnnotations > 1) {
+            throw new IllegalStateException(method.toString() + " has more 
than one security annotation (RolesAllowed, PermitAll, DenyAll).");
         }
 
-        /*
-         * Process annotations at the class level
-         */
-        if (classSecurityAnnotations.size() > 1) {
-            throw new IllegalStateException(clazz.getName() + " has more than 
one security annotation (RolesAllowed, PermitAll, DenyAll).");
+        if (methodSecurityAnnotations == 1) { // method level annotations 
override class level ones
+            return toInterceptor(method);
         }
 
-        if (methodSecurityAnnotations.size() > 1) {
-            throw new IllegalStateException(method.toString() + " has more 
than one security annotation (RolesAllowed, PermitAll, DenyAll).");
+        final Class<?> constrainingClass = 
findConstrainingClass(resourceClass, method.getDeclaringClass());
+        if (constrainingClass == null) {
+            return null; // nothing to do
         }
 
-        if (!methodSecurityAnnotations.isEmpty()) { // method level 
annotations override class level ones
-            return toInterceptor(method.getAnnotation(RolesAllowed.class),
-                    method.isAnnotationPresent(DenyAll.class),
-                    method.isAnnotationPresent(PermitAll.class));
+        if (countSecurityAnnotations(constrainingClass) > 1) {
+            throw new IllegalStateException(constrainingClass.getName() + " 
has more than one security annotation (RolesAllowed, PermitAll, DenyAll).");
         }
 
-        return toInterceptor((RolesAllowed) 
clazz.getAnnotation(RolesAllowed.class),
-                clazz.isAnnotationPresent(DenyAll.class),
-                clazz.isAnnotationPresent(PermitAll.class));
+        return toInterceptor(constrainingClass);
     }
 
-    private MPJWTSecurityAnnotationsInterceptor toInterceptor(final 
RolesAllowed rolesAllowed,
-                                                              final boolean 
denyAll,
-                                                              final boolean 
permitAll) {
-        final Set<String> roles = rolesAllowed == null ? null : new 
HashSet<>(Arrays.asList(rolesAllowed.value()));
-        return new MPJWTSecurityAnnotationsInterceptor(roles, denyAll, 
permitAll);
-    }
+    /*
+     * Security annotations are not @Inherited, so a class level annotation 
applies to the methods declared by
+     * that class. If the declaring class has none, the closest annotated 
class between the resource class and
+     * the declaring class applies, so a subclass can still secure the methods 
it inherits.
+     */
+    private Class<?> findConstrainingClass(final Class<?> resourceClass, final 
Class<?> declaringClass) {
+        if (countSecurityAnnotations(declaringClass) > 0) {
+            return declaringClass;
+        }
 
-    private List<Class<? extends Annotation>[]> hasClassLevelAnnotations(final 
Class clazz, final Class<? extends Annotation>... annotationsToCheck) {
-        final List<Class<? extends Annotation>[]> list = new ArrayList<>();
-        for (Class<? extends Annotation> annotationToCheck : 
annotationsToCheck) {
-            if (clazz.isAnnotationPresent(annotationToCheck)) {
-                list.add(annotationsToCheck);
+        for (Class<?> current = resourceClass;
+             current != null && current != declaringClass && current != 
Object.class;
+             current = current.getSuperclass()) {
+
+            if (countSecurityAnnotations(current) > 0) {
+                return current;
             }
         }
-        return list;
+
+        return null;
+    }
+
+    private MPJWTSecurityAnnotationsInterceptor toInterceptor(final 
AnnotatedElement element) {
+        final RolesAllowed rolesAllowed = 
element.getAnnotation(RolesAllowed.class);
+        final Set<String> roles = rolesAllowed == null ? null : new 
HashSet<>(Arrays.asList(rolesAllowed.value()));
+        return new MPJWTSecurityAnnotationsInterceptor(roles,
+                element.isAnnotationPresent(DenyAll.class),
+                element.isAnnotationPresent(PermitAll.class));
     }
 
-    private List<Class<? extends Annotation>[]> 
hasMethodLevelAnnotations(final Method method, final Class<? extends 
Annotation>... annotationsToCheck) {
-        final List<Class<? extends Annotation>[]> list = new ArrayList<>();
-        for (Class<? extends Annotation> annotationToCheck : 
annotationsToCheck) {
-            if (method.isAnnotationPresent(annotationToCheck)) {
-                list.add(annotationsToCheck);
+    private int countSecurityAnnotations(final AnnotatedElement element) {
+        int count = 0;
+        for (final Class<? extends Annotation> annotation : 
SECURITY_ANNOTATIONS) {
+            if (element.isAnnotationPresent(annotation)) {
+                count++;
             }
         }
-        return list;
+        return count;
     }
 
 }
\ No newline at end of file
diff --git 
a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeatureTest.java
 
b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeatureTest.java
index b8e74c77fe..e11f0d8026 100644
--- 
a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeatureTest.java
+++ 
b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/jaxrs/MPJWTSecurityAnnotationsInterceptorsFeatureTest.java
@@ -90,6 +90,38 @@ public class MPJWTSecurityAnnotationsInterceptorsFeatureTest 
{
     public static class NoAnnotationResource extends BaseResource {
     }
 
+    @RolesAllowed("base")
+    public static class SecuredBaseResource {
+        public String get() {
+            return "ok";
+        }
+
+        @PermitAll
+        public String open() {
+            return "ok";
+        }
+    }
+
+    public static class UnannotatedSecuredChildResource extends 
SecuredBaseResource {
+        public String own() {
+            return "ok";
+        }
+    }
+
+    @RolesAllowed("child")
+    public static class AnnotatedSecuredChildResource extends 
SecuredBaseResource {
+        public String own() {
+            return "ok";
+        }
+    }
+
+    @RolesAllowed("mid")
+    public static class MidResource extends BaseResource {
+    }
+
+    public static class LeafResource extends MidResource {
+    }
+
     public static class EmptyMethodRolesResource {
         @RolesAllowed({})
         public String get() {
@@ -117,6 +149,44 @@ public class 
MPJWTSecurityAnnotationsInterceptorsFeatureTest {
         assertAllowed(user, "user");
     }
 
+    @Test
+    public void inheritedMethodKeepsDeclaringClassConstraint() throws 
Exception {
+        final MPJWTSecurityAnnotationsInterceptorsFeature feature = new 
MPJWTSecurityAnnotationsInterceptorsFeature();
+
+        final ContainerRequestFilter unannotated = configure(feature, 
UnannotatedSecuredChildResource.class, "get");
+        assertForbidden(unannotated);
+        assertForbidden(unannotated, "user");
+        assertAllowed(unannotated, "base");
+
+        final ContainerRequestFilter annotated = configure(feature, 
AnnotatedSecuredChildResource.class, "get");
+        assertForbidden(annotated, "child");
+        assertAllowed(annotated, "base");
+
+        assertAllowed(configure(feature, 
UnannotatedSecuredChildResource.class, "open"));
+        assertAllowed(configure(feature, AnnotatedSecuredChildResource.class, 
"open"));
+    }
+
+    @Test
+    public void methodDeclaredInSubclassUsesSubclassConstraint() throws 
Exception {
+        final MPJWTSecurityAnnotationsInterceptorsFeature feature = new 
MPJWTSecurityAnnotationsInterceptorsFeature();
+
+        final ContainerRequestFilter annotated = configure(feature, 
AnnotatedSecuredChildResource.class, "own");
+        assertForbidden(annotated, "base");
+        assertAllowed(annotated, "child");
+
+        assertNull(configure(feature, UnannotatedSecuredChildResource.class, 
"own"));
+    }
+
+    @Test
+    public void closestAnnotatedSuperclassSecuresUnannotatedDeclaringClass() 
throws Exception {
+        final MPJWTSecurityAnnotationsInterceptorsFeature feature = new 
MPJWTSecurityAnnotationsInterceptorsFeature();
+
+        final ContainerRequestFilter leaf = configure(feature, 
LeafResource.class, "get");
+        assertForbidden(leaf);
+        assertForbidden(leaf, "admin");
+        assertAllowed(leaf, "mid");
+    }
+
     @Test
     public void emptyRolesAllowedDeniesEveryone() throws Exception {
         final MPJWTSecurityAnnotationsInterceptorsFeature feature = new 
MPJWTSecurityAnnotationsInterceptorsFeature();

Reply via email to