This is an automated email from the ASF dual-hosted git repository.
rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git
The following commit(s) were added to refs/heads/main by this push:
new 59698bfe1f TOMEE-4730 - Apply class-level security annotations of a
resource superclass (#3081)
59698bfe1f is described below
commit 59698bfe1fa93e57ff9d27209aa190811938af5c
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.
---
...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();