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

coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new 5948451179a Resolve generic overrides exactly in 
SecureAnnotationsInterceptor (#3679)
5948451179a is described below

commit 5948451179a375fbda69b8c0ec6d898d3b166647
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Nov 4 15:26:26 2026 +0000

    Resolve generic overrides exactly in SecureAnnotationsInterceptor (#3679)
---
 .../security/SecureAnnotationsInterceptor.java     |  33 ++++--
 .../security/SecureAnnotationsInterceptorTest.java | 112 ++++++++++++++++++++-
 2 files changed, 133 insertions(+), 12 deletions(-)

diff --git 
a/core/src/main/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptor.java
 
b/core/src/main/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptor.java
index 284a8d5364f..0ec35183c1f 100644
--- 
a/core/src/main/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptor.java
+++ 
b/core/src/main/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptor.java
@@ -52,6 +52,9 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
     // the secured object's hierarchy. Keying by signature means that 
overloaded methods do not
     // share a rule
     private final Map<String, List<RolesDeclaration>> declarationsBySignature 
= new HashMap<>();
+    // The erased signatures a method overrides with a different signature, 
e.g. update(Object)
+    // for update(String) implementing update(T) of Repository<String>
+    private final Map<String, Set<String>> overriddenSignatures = new 
HashMap<>();
     private final Map<String, Set<String>> signaturesByName = new HashMap<>();
     private final Set<Class<?>> securedClasses = new HashSet<>();
     // The classes passed to setSecuredObject, so their roles can be read 
again if the
@@ -93,6 +96,7 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
             return;
         }
         declarationsBySignature.clear();
+        overriddenSignatures.clear();
         signaturesByName.clear();
         securedClasses.clear();
         for (Class<?> cls : securedObjectClasses) {
@@ -152,6 +156,12 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
                 declarations.add(new RolesDeclaration(classRolesDeclarer, 
classRoles, false));
             }
             signaturesByName.computeIfAbsent(m.getName(), k -> new 
HashSet<>()).add(signature);
+            if (!m.isBridge()) {
+                for (Method overridden : 
ReflectionUtil.getGenericallyOverriddenMethods(cls, m)) {
+                    overriddenSignatures.computeIfAbsent(signature, k -> new 
HashSet<>())
+                        .add(ReflectionUtil.getMethodSignature(overridden));
+                }
+            }
         }
         findRoles(cls.getSuperclass(), rolesMap);
         for (Class<?> interfaceCls : cls.getInterfaces()) {
@@ -205,18 +215,21 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
     }
 
     private List<List<String>> getAnnotationRoles(Method method) {
-        List<List<String>> rules = 
getAnnotationRoles(ReflectionUtil.getMethodSignature(method));
-        if (!rules.isEmpty()) {
-            return rules;
+        String signature = ReflectionUtil.getMethodSignature(method);
+        Set<String> signatures = new HashSet<>();
+        if (declarationsBySignature.containsKey(signature)) {
+            signatures.add(signature);
+            signatures.addAll(overriddenSignatures.getOrDefault(signature, 
Collections.emptySet()));
+        } else {
+            // The method is not one of the secured object's methods, so every 
rule
+            // recorded for its name applies
+            signatures.addAll(signaturesByName.getOrDefault(method.getName(), 
Collections.emptySet()));
         }
-        // No rule applies to the method's signature, e.g. because it 
implements a generic
-        // interface method whose erased signature differs, so every rule 
recorded for its
-        // name applies
-        Set<List<String>> nameRules = new LinkedHashSet<>();
-        for (String s : signaturesByName.getOrDefault(method.getName(), 
Collections.emptySet())) {
-            nameRules.addAll(getAnnotationRoles(s));
+        Set<List<String>> rules = new LinkedHashSet<>();
+        for (String s : signatures) {
+            rules.addAll(getAnnotationRoles(s));
         }
-        return new ArrayList<>(nameRules);
+        return new ArrayList<>(rules);
     }
 
     private List<List<String>> getAnnotationRoles(String signature) {
diff --git 
a/core/src/test/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptorTest.java
 
b/core/src/test/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptorTest.java
index 4956b01fb5a..290c9ca1a4c 100644
--- 
a/core/src/test/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/cxf/interceptor/security/SecureAnnotationsInterceptorTest.java
@@ -351,11 +351,61 @@ public class SecureAnnotationsInterceptorTest {
     }
 
     @Test
-    public void testGenericInterfaceMethodRolesStillApply() throws Exception {
+    public void testGenericInterfaceMethodRolesAreEnforced() throws Exception {
+        // update(String) overrides the erased update(Object), but not the 
update() overload
         Method target = StringRepository.class.getMethod("update", 
String.class);
-
+        assertAllowed(new StringRepository(), target, "admin");
         assertDenied(new StringRepository(), target, "user");
         assertDenied(new StringRepository(), target);
+
+        Method userMethod = StringRepository.class.getMethod("update");
+        assertAllowed(new StringRepository(), userMethod, "user");
+        assertDenied(new StringRepository(), userMethod, "admin");
+    }
+
+    @Test
+    public void testGenericInterfaceMethodRolesApplyTogetherWithClassRoles() 
throws Exception {
+        Method target = ClassRolesStringRepository.class.getMethod("update", 
String.class);
+
+        assertAllowed(new ClassRolesStringRepository(), target, "user", 
"admin");
+        assertDenied(new ClassRolesStringRepository(), target, "user");
+        assertDenied(new ClassRolesStringRepository(), target, "admin");
+    }
+
+    @Test
+    public void 
testGenericInterfaceMethodRolesApplyToInheritedImplementation() throws 
Exception {
+        Method target = InheritingStringRepository.class.getMethod("update", 
String.class);
+
+        assertAllowed(new InheritingStringRepository(), target, "admin");
+        assertDenied(new InheritingStringRepository(), target, "user");
+    }
+
+    @Test
+    public void testGenericInterfaceMethodRolesApplyThroughTypeVariables() 
throws Exception {
+        Method target = TypedStringRepository.class.getMethod("update", 
String.class);
+
+        assertAllowed(new TypedStringRepository(), target, "admin");
+        assertDenied(new TypedStringRepository(), target, "user");
+    }
+
+    @Test
+    public void testUnrelatedOverloadIsNotLinkedToGenericInterfaceMethod() 
throws Exception {
+        Method integerMethod = 
OverloadingStringRepository.class.getMethod("update", Integer.class);
+        assertAllowed(new OverloadingStringRepository(), integerMethod, 
"user");
+        assertDenied(new OverloadingStringRepository(), integerMethod, 
"admin");
+
+        Method stringMethod = 
OverloadingStringRepository.class.getMethod("update", String.class);
+        assertAllowed(new OverloadingStringRepository(), stringMethod, 
"admin");
+        assertDenied(new OverloadingStringRepository(), stringMethod, "user");
+    }
+
+    @Test
+    public void testUnannotatedOverloadIsNotSecuredByOtherOverloads() throws 
Exception {
+        Method target = UnannotatedOverloadService.class.getMethod("delete");
+
+        assertAllowed(new UnannotatedOverloadService(), target, "user");
+        assertDenied(new UnannotatedOverloadService(),
+                     UnannotatedOverloadService.class.getMethod("delete", 
String.class), "user");
     }
 
     @Test
@@ -627,6 +677,64 @@ public class SecureAnnotationsInterceptorTest {
         }
     }
 
+    @RolesAllowed("user")
+    public static class ClassRolesStringRepository implements 
Repository<String> {
+        @Override
+        public void update(String value) {
+        }
+
+        @Override
+        public void update() {
+        }
+    }
+
+    public static class StringUpdater {
+        public void update(String value) {
+        }
+
+        public void update() {
+        }
+    }
+
+    public static class InheritingStringRepository extends StringUpdater 
implements Repository<String> {
+    }
+
+    public abstract static class AbstractRepository<T> implements 
Repository<T> { // NOPMD
+    }
+
+    public static class TypedStringRepository extends 
AbstractRepository<String> {
+        @Override
+        public void update(String value) {
+        }
+
+        @Override
+        public void update() {
+        }
+    }
+
+    public static class OverloadingStringRepository implements 
Repository<String> {
+        @Override
+        public void update(String value) {
+        }
+
+        @Override
+        public void update() {
+        }
+
+        @RolesAllowed("user")
+        public void update(Integer value) {
+        }
+    }
+
+    public static class UnannotatedOverloadService {
+        @RolesAllowed("admin")
+        public void delete(String id) {
+        }
+
+        public void delete() {
+        }
+    }
+
     public static class UnsecuredService {
         public void delete() {
         }

Reply via email to