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

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

commit 22a00c31b8acaf8741e84588ef6054c4f084a0c8
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Tue Nov 3 07:53:34 2026 +0000

    Enforce every applicable role annotation in SecureAnnotationsInterceptor
---
 .../security/SecureAnnotationsInterceptor.java     | 214 +++++++---
 .../security/SimpleAuthorizingInterceptor.java     |  14 +-
 .../security/SecureAnnotationsInterceptorTest.java | 449 ++++++++++++++++++++-
 3 files changed, 620 insertions(+), 57 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 fa4f590df8e..78c56c8b889 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
@@ -25,6 +25,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -34,6 +35,8 @@ import java.util.logging.Logger;
 import org.apache.cxf.common.classloader.ClassLoaderUtils;
 import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.ClassHelper;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.security.SecurityContext;
 
 
 public class SecureAnnotationsInterceptor extends SimpleAuthorizingInterceptor 
{
@@ -45,12 +48,15 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
             Arrays.asList("wait", "notify", "notifyAll", "equals", "toString", 
"hashCode"));
 
     private String annotationClassName = DEFAULT_ANNOTATION_CLASS_NAME;
+    // The method-level and class-level role annotations which apply to each 
method signature in
+    // 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<>();
+    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
     // annotation class changes afterwards
     private final List<Class<?>> securedObjectClasses = new ArrayList<>();
-    // The methodRolesMap entries added from the annotations, so they can be 
replaced
-    // without touching the entries configured via setMethodRolesMap
-    private final Map<String, List<String>> annotationRoles = new HashMap<>();
 
     public SecureAnnotationsInterceptor() {
         this(true);
@@ -73,7 +79,7 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
     public void setSecuredObject(Object object) {
         Class<?> cls = ClassHelper.getRealClass(object);
         securedObjectClasses.add(cls);
-        addRoles(cls, Collections.emptyMap());
+        addRoles(cls);
     }
 
     /**
@@ -86,85 +92,181 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
         if (securedObjectClasses.isEmpty()) {
             return;
         }
-        for (Map.Entry<String, List<String>> entry : 
annotationRoles.entrySet()) {
-            // Keep an entry which setMethodRolesMap has replaced since
-            if (methodRolesMap.get(entry.getKey()) == entry.getValue()) {
-                methodRolesMap.remove(entry.getKey());
-            }
-        }
-        annotationRoles.clear();
-        // What is left was configured via setMethodRolesMap after the secured 
object, so it
-        // takes precedence, as it would have if the annotation class had been 
set first
-        Map<String, List<String>> configuredRoles = new 
HashMap<>(methodRolesMap);
+        declarationsBySignature.clear();
+        signaturesByName.clear();
+        securedClasses.clear();
         for (Class<?> cls : securedObjectClasses) {
-            addRoles(cls, configuredRoles);
+            addRoles(cls);
         }
     }
 
-    private void addRoles(Class<?> cls, Map<String, List<String>> 
configuredRoles) {
+    private void addRoles(Class<?> cls) {
         Map<String, String> rolesMap = new HashMap<>();
         findRoles(cls, rolesMap);
-        if (rolesMap.isEmpty()) {
-            LOG.warning("The roles map is empty, the service object is not 
protected");
-        } else if (LOG.isLoggable(Level.FINE)) {
-            for (Map.Entry<String, String> entry : rolesMap.entrySet()) {
-                LOG.fine("Method: " + entry.getKey() + ", roles: " + 
entry.getValue());
-            }
+        // Entries added to the roles map, e.g. by a subclass, apply as 
configured method roles
+        if (!rolesMap.isEmpty()) {
+            super.setMethodRolesMap(rolesMap);
         }
-        Map<String, List<String>> previousRoles = new 
HashMap<>(methodRolesMap);
-        super.setMethodRolesMap(rolesMap);
-        // Record the entries the annotations added or replaced, using the 
keys as stored
-        // by setMethodRolesMap, and restore any configured entry they replaced
-        for (Map.Entry<String, List<String>> entry : 
methodRolesMap.entrySet()) {
-            if (entry.getValue() != previousRoles.get(entry.getKey())) {
-                List<String> configured = configuredRoles.get(entry.getKey());
-                if (configured != null) {
-                    entry.setValue(configured);
-                } else {
-                    annotationRoles.put(entry.getKey(), entry.getValue());
+        boolean protectedMethodFound = !rolesMap.isEmpty();
+        for (String signature : declarationsBySignature.keySet()) {
+            List<List<String>> rules = getAnnotationRoles(signature);
+            if (!rules.isEmpty()) {
+                protectedMethodFound = true;
+                if (LOG.isLoggable(Level.FINE)) {
+                    LOG.fine("Method: " + signature + ", roles: " + rules);
                 }
             }
         }
+        if (!protectedMethodFound) {
+            LOG.warning("The roles map is empty, the service object is not 
protected");
+        }
     }
 
+    /**
+     * Records the role annotations of the methods of a class, its 
superclasses and interfaces.
+     * The annotations are not added to the roles map: entries which are added 
to it, e.g. by
+     * a subclass, apply as configured method roles.
+     */
     protected void findRoles(Class<?> cls, Map<String, String> rolesMap) {
-        if (cls == null || cls == Object.class) {
+        if (cls == null || cls == Object.class || !securedClasses.add(cls)) {
             return;
         }
-        String classRolesAllowed = getRoles(cls.getAnnotations(), 
annotationClassName);
+        // The class-level annotation may be inherited from a superclass if it 
is @Inherited
+        List<String> classRoles = getRoles(cls.getAnnotations());
+        Class<?> classRolesDeclarer = getAnnotationDeclarer(cls, 
annotationClassName);
         for (Method m : cls.getMethods()) {
             if (SKIP_METHODS.contains(m.getName())) {
                 continue;
             }
-            String methodRolesAllowed = getRoles(m.getAnnotations(), 
annotationClassName);
-            String theRoles = methodRolesAllowed != null ? methodRolesAllowed 
: classRolesAllowed;
-            if (theRoles != null) {
-                rolesMap.put(m.getName(), theRoles);
-                rolesMap.put(createMethodSig(m), theRoles);
+            String signature = ReflectionUtil.getMethodSignature(m);
+            List<String> methodRoles = getRoles(m.getAnnotations());
+            List<RolesDeclaration> declarations =
+                declarationsBySignature.computeIfAbsent(signature, k -> new 
ArrayList<>());
+            if (methodRoles != null) {
+                declarations.add(new RolesDeclaration(m.getDeclaringClass(), 
methodRoles, true));
             }
+            if (classRoles != null) {
+                declarations.add(new RolesDeclaration(classRolesDeclarer, 
classRoles, false));
+            }
+            signaturesByName.computeIfAbsent(m.getName(), k -> new 
HashSet<>()).add(signature);
         }
-        if (!rolesMap.isEmpty()) {
-            return;
+        findRoles(cls.getSuperclass(), rolesMap);
+        for (Class<?> interfaceCls : cls.getInterfaces()) {
+            findRoles(interfaceCls, rolesMap);
         }
+    }
 
-        findRoles(cls.getSuperclass(), rolesMap);
+    @Override
+    protected boolean authorize(SecurityContext sc, Method method) {
+        List<List<String>> rules = getRoleRules(method);
+        if (rules.isEmpty()) {
+            return super.authorize(sc, method);
+        }
+        // Every rule which applies to the method has to be satisfied
+        for (List<String> roles : rules) {
+            if (roles.isEmpty() || !isUserInRole(sc, roles, false)) {
+                if (LOG.isLoggable(Level.FINE)) {
+                    LOG.fine(sc.getUserPrincipal().getName() + " is not 
authorized");
+                }
+                return false;
+            }
+        }
+        return true;
+    }
 
-        if (!rolesMap.isEmpty()) {
-            return;
+    @Override
+    protected boolean isMethodProtected(Method method) {
+        return !getRoleRules(method).isEmpty() || 
super.isMethodProtected(method);
+    }
+
+    @Override
+    protected List<String> getExpectedRoles(Method method) {
+        List<List<String>> rules = getRoleRules(method);
+        if (rules.isEmpty()) {
+            return super.getExpectedRoles(method);
+        }
+        Set<String> roles = new LinkedHashSet<>();
+        for (List<String> rule : rules) {
+            roles.addAll(rule);
         }
+        return new ArrayList<>(roles);
+    }
 
-        for (Class<?> interfaceCls : cls.getInterfaces()) {
-            findRoles(interfaceCls, rolesMap);
+    private List<List<String>> getRoleRules(Method method) {
+        List<List<String>> rules = getAnnotationRoles(method);
+        List<String> configuredRoles = getConfiguredRoles(method);
+        if (configuredRoles != null) {
+            rules.add(configuredRoles);
+        }
+        return rules;
+    }
+
+    private List<List<String>> getAnnotationRoles(Method method) {
+        List<List<String>> rules = 
getAnnotationRoles(ReflectionUtil.getMethodSignature(method));
+        if (!rules.isEmpty()) {
+            return rules;
+        }
+        // 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));
+        }
+        return new ArrayList<>(nameRules);
+    }
+
+    private List<List<String>> getAnnotationRoles(String signature) {
+        // Every role annotation which applies to the method has to be met, 
from any class or
+        // interface in the hierarchy. A class-level annotation is replaced by 
a method-level one
+        // declared on the same class or interface, or on a subclass of it
+        List<RolesDeclaration> declarations =
+            declarationsBySignature.getOrDefault(signature, 
Collections.emptyList());
+        Set<List<String>> rules = new LinkedHashSet<>();
+        for (RolesDeclaration declaration : declarations) {
+            if (declaration.methodLevel || 
!isReplacedByMethodRoles(declaration, declarations)) {
+                rules.add(declaration.roles);
+            }
+        }
+        return new ArrayList<>(rules);
+    }
+
+    private static boolean isReplacedByMethodRoles(RolesDeclaration 
classDeclaration,
+                                                   List<RolesDeclaration> 
declarations) {
+        for (RolesDeclaration declaration : declarations) {
+            if (declaration.methodLevel
+                && 
classDeclaration.declaringClass.isAssignableFrom(declaration.declaringClass)) {
+                return true;
+            }
         }
+        return false;
     }
 
-    private String getRoles(Annotation[] anns, String annName) {
+    private static Class<?> getAnnotationDeclarer(Class<?> cls, String 
annName) {
+        for (Class<?> declarer = cls; declarer != null; declarer = 
declarer.getSuperclass()) {
+            if (hasAnnotation(declarer.getDeclaredAnnotations(), annName)) {
+                return declarer;
+            }
+        }
+        return cls;
+    }
+
+    private static boolean hasAnnotation(Annotation[] anns, String annName) {
         for (Annotation ann : anns) {
             if (ann.annotationType().getName().equals(annName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<String> getRoles(Annotation[] anns) {
+        for (Annotation ann : anns) {
+            if (ann.annotationType().getName().equals(annotationClassName)) {
                 try {
                     Method valueMethod = 
ann.annotationType().getMethod("value");
                     String[] roles = (String[])valueMethod.invoke(ann);
-                    return String.join(" ", roles);
+                    return Arrays.asList(roles);
                 } catch (Exception ex) {
                     // ignore
                 }
@@ -173,4 +275,18 @@ public class SecureAnnotationsInterceptor extends 
SimpleAuthorizingInterceptor {
         }
         return null;
     }
+
+    private static final class RolesDeclaration {
+        // The class or interface the annotation is declared on
+        private final Class<?> declaringClass;
+        // The roles of which one is required
+        private final List<String> roles;
+        private final boolean methodLevel;
+
+        RolesDeclaration(Class<?> declaringClass, List<String> roles, boolean 
methodLevel) {
+            this.declaringClass = declaringClass;
+            this.roles = roles;
+            this.methodLevel = methodLevel;
+        }
+    }
 }
diff --git 
a/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java
 
b/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java
index 5b73d24be81..b4b591e388e 100644
--- 
a/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java
+++ 
b/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java
@@ -88,6 +88,15 @@ public class SimpleAuthorizingInterceptor extends 
AbstractAuthorizingInIntercept
 
     @Override
     protected List<String> getExpectedRoles(Method method) {
+        List<String> roles = getConfiguredRoles(method);
+        return roles != null ? roles : globalRoles;
+    }
+
+    /**
+     * Returns the roles configured for a method in the method roles map, by 
signature or by
+     * method name, or null if none are configured. The global roles are not 
taken into account.
+     */
+    protected List<String> getConfiguredRoles(Method method) {
         String sig = createMethodSig(method);
         List<String> roles = methodRolesMap.get(sig);
         if (roles == null) {
@@ -96,10 +105,7 @@ public class SimpleAuthorizingInterceptor extends 
AbstractAuthorizingInIntercept
         if (roles == null) {
             roles = methodRolesMap.get(method.getName());
         }
-        if (roles != null) {
-            return roles;
-        }
-        return globalRoles;
+        return roles;
     }
 
 
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 57e23e6b343..c60dab38ffd 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
@@ -19,13 +19,18 @@
 package org.apache.cxf.interceptor.security;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.security.Principal;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import jakarta.annotation.security.RolesAllowed;
 import org.apache.cxf.common.security.SimplePrincipal;
@@ -42,6 +47,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -128,9 +134,9 @@ public class SecureAnnotationsInterceptorTest {
         in.setMethodRolesMap(Collections.singletonMap("echo", "admin"));
 
         in.setAnnotationClassName(SecureRolesAllowed.class.getName());
-        // As when the annotation class is set first: the annotation's 
signature key takes
-        // precedence, and the configured method name entry is kept
-        assertEquals(Collections.singletonList("secureRole"), 
in.getExpectedRoles(echo));
+        // As when the annotation class is set first: the annotation's roles 
and the configured
+        // method name entry both apply
+        assertEquals(Arrays.asList("secureRole", "admin"), 
in.getExpectedRoles(echo));
         assertEquals(Collections.singletonList("admin"), 
in.methodRolesMap.get("echo"));
     }
 
@@ -142,7 +148,7 @@ public class SecureAnnotationsInterceptorTest {
         in.setSecuredObject(new MixedService());
         in.setMethodRolesMap(Collections.singletonMap("echo", "admin"));
 
-        assertEquals(Collections.singletonList("secureRole"), 
in.getExpectedRoles(echo));
+        assertEquals(Arrays.asList("secureRole", "admin"), 
in.getExpectedRoles(echo));
         assertEquals(Collections.singletonList("admin"), 
in.methodRolesMap.get("echo"));
     }
 
@@ -163,6 +169,441 @@ public class SecureAnnotationsInterceptorTest {
         }
     }
 
+    @Test
+    public void testOverloadedSubclassMethodKeepsItsOwnRoles() throws 
Exception {
+        Method adminMethod = OverloadingService.class.getMethod("operation", 
String.class);
+        Method userMethod = OverloadingService.class.getMethod("operation");
+
+        assertAllowed(new OverloadingService(), adminMethod, "admin");
+        assertDenied(new OverloadingService(), adminMethod, "user");
+        assertAllowed(new OverloadingService(), userMethod, "user");
+        assertDenied(new OverloadingService(), userMethod, "admin");
+    }
+
+    @Test
+    public void testOverloadedMethodsInSameClassKeepTheirOwnRoles() throws 
Exception {
+        Method userMethod = 
SameClassOverloadingService.class.getMethod("operation");
+        Method adminMethod = 
SameClassOverloadingService.class.getMethod("operation", String.class);
+
+        assertAllowed(new SameClassOverloadingService(), userMethod, "user");
+        assertDenied(new SameClassOverloadingService(), userMethod, "admin");
+        assertAllowed(new SameClassOverloadingService(), adminMethod, "admin");
+        assertDenied(new SameClassOverloadingService(), adminMethod, "user");
+    }
+
+    @Test
+    public void testOverridingMethodRolesApplyTogetherWithSuperclassRoles() 
throws Exception {
+        Method target = OverridingService.class.getMethod("operation");
+
+        assertAllowed(new OverridingService(), target, "user", "admin");
+        assertDenied(new OverridingService(), target, "user");
+        assertDenied(new OverridingService(), target, "admin");
+    }
+
+    @Test
+    public void testOverridingMethodCannotWeakenSuperclassRoles() throws 
Exception {
+        Method target = UserOverridingService.class.getMethod("operation");
+
+        assertAllowed(new UserOverridingService(), target, "user", "admin");
+        assertDenied(new UserOverridingService(), target, "user");
+    }
+
+    @Test
+    public void testOverridingMethodRolesReplaceInterfaceClassRoles() throws 
Exception {
+        Method target = UserApiAdminService.class.getMethod("operation");
+
+        assertAllowed(new UserApiAdminService(), target, "admin");
+        assertDenied(new UserApiAdminService(), target, "user");
+    }
+
+    @Test
+    public void testOverridingMethodRolesReplaceSuperclassClassRoles() throws 
Exception {
+        Method target = OverridingSubResource.class.getMethod("operation");
+
+        assertAllowed(new OverridingSubResource(), target, "admin");
+        assertDenied(new OverridingSubResource(), target, "user");
+    }
+
+    @Test
+    public void testInheritedMethodRolesStillApply() throws Exception {
+        Method target = InheritingService.class.getMethod("operation");
+
+        assertAllowed(new InheritingService(), target, "user");
+        assertDenied(new InheritingService(), target, "admin");
+    }
+
+    @Test
+    public void testUnannotatedOverrideInheritsSuperclassRoles() throws 
Exception {
+        Method target = 
UnannotatedOverridingService.class.getMethod("operation");
+
+        assertAllowed(new UnannotatedOverridingService(), target, "user");
+        assertDenied(new UnannotatedOverridingService(), target, "admin");
+    }
+
+    @Test
+    public void 
testUnannotatedOverrideKeepsSuperclassRolesWhenSubclassHasOtherRoles() throws 
Exception {
+        Method target = 
UnannotatedOverridingAnnotatedService.class.getMethod("operation");
+
+        assertAllowed(new UnannotatedOverridingAnnotatedService(), target, 
"user");
+        assertDenied(new UnannotatedOverridingAnnotatedService(), target, 
"admin");
+        assertDenied(new UnannotatedOverridingAnnotatedService(), target);
+    }
+
+    @Test
+    public void testInterfaceRolesApplyWhenImplementationHasOtherRoles() 
throws Exception {
+        Method target = AdminApiService.class.getMethod("delete");
+
+        assertAllowed(new AdminApiService(), target, "admin");
+        assertDenied(new AdminApiService(), target, "user");
+        assertDenied(new AdminApiService(), target);
+    }
+
+    @Test
+    public void testSuperclassClassRolesApplyWhenSubclassHasOtherRoles() 
throws Exception {
+        Method target = AnnotatedSubResource.class.getMethod("operation");
+
+        assertAllowed(new AnnotatedSubResource(), target, "user");
+        assertDenied(new AnnotatedSubResource(), target, "admin");
+        assertDenied(new AnnotatedSubResource(), target);
+    }
+
+    @Test
+    public void testSuperclassClassRolesDoNotApplyToSubclassMethods() throws 
Exception {
+        // @RolesAllowed is not @Inherited, so a class-level @RolesAllowed 
only applies to the
+        // methods of the class which declares it
+        Method target = AnnotatedSubResource.class.getMethod("adminOperation");
+
+        assertAllowed(new AnnotatedSubResource(), target, "admin");
+        assertDenied(new AnnotatedSubResource(), target, "user");
+    }
+
+    @Test
+    public void testInheritedClassRolesApplyToSubclassMethods() throws 
Exception {
+        Method target = 
InheritedRolesSubService.class.getMethod("subclassOperation");
+
+        assertAllowed(createInterceptor(new InheritedRolesSubService(), 
InheritedRoles.class),
+                      target, "user");
+        assertDenied(createInterceptor(new InheritedRolesSubService(), 
InheritedRoles.class),
+                     target, "admin");
+    }
+
+    @Test
+    public void testMethodRolesReplaceInheritedClassRoles() throws Exception {
+        Method target = 
InheritedRolesSubService.class.getMethod("adminOperation");
+
+        assertAllowed(createInterceptor(new InheritedRolesSubService(), 
InheritedRoles.class),
+                      target, "admin");
+        assertDenied(createInterceptor(new InheritedRolesSubService(), 
InheritedRoles.class),
+                     target, "user");
+
+        Method redeclaredMethod = 
RedeclaringInheritedRolesService.class.getMethod("adminOperation");
+        assertAllowed(createInterceptor(new 
RedeclaringInheritedRolesService(), InheritedRoles.class),
+                      redeclaredMethod, "admin");
+        assertDenied(createInterceptor(new RedeclaringInheritedRolesService(), 
InheritedRoles.class),
+                     redeclaredMethod, "user");
+    }
+
+    @Test
+    public void testMethodRolesTakePrecedenceOverInheritedClassRoles() throws 
Exception {
+        Method target = 
InheritedRolesOverridingService.class.getMethod("operation");
+
+        assertAllowed(createInterceptor(new InheritedRolesOverridingService(), 
InheritedRoles.class),
+                      target, "admin");
+        assertDenied(createInterceptor(new InheritedRolesOverridingService(), 
InheritedRoles.class),
+                     target, "user");
+    }
+
+    @Test
+    public void testSubclassRolesApplyTogetherWithSuperclassMethodRoles() 
throws Exception {
+        Method target = AdminSubService.class.getMethod("operation");
+
+        assertAllowed(new AdminSubService(), target, "user", "admin");
+        assertDenied(new AdminSubService(), target, "user");
+        assertDenied(new AdminSubService(), target, "admin");
+    }
+
+    @Test
+    public void testMethodRolesFromUnrelatedSupertypesApplyTogether() throws 
Exception {
+        Method target = AdminApiUserService.class.getMethod("operation");
+
+        assertAllowed(new AdminApiUserService(), target, "user", "admin");
+        assertDenied(new AdminApiUserService(), target, "user");
+        assertDenied(new AdminApiUserService(), target, "admin");
+    }
+
+    @Test
+    public void testGenericInterfaceMethodRolesStillApply() throws Exception {
+        Method target = StringRepository.class.getMethod("update", 
String.class);
+
+        assertDenied(new StringRepository(), target, "user");
+        assertDenied(new StringRepository(), target);
+    }
+
+    @Test
+    public void testRolesConfiguredByMethodName() throws Exception {
+        Method target = UnsecuredService.class.getMethod("delete");
+
+        SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor();
+        in.setMethodRolesMap(Collections.singletonMap("delete", "admin"));
+        assertAllowed(in, target, "admin");
+        assertDenied(in, target, "user");
+    }
+
+    @Test
+    public void testConfiguredRolesAreEnforcedTogetherWithAnnotations() throws 
Exception {
+        Method target = BaseService.class.getMethod("operation");
+
+        SecureAnnotationsInterceptor in = createInterceptor(new BaseService(), 
RolesAllowed.class);
+        in.setMethodRolesMap(Collections.singletonMap("operation", "admin"));
+        assertAllowed(in, target, "user", "admin");
+        assertDenied(in, target, "user");
+        assertDenied(in, target, "admin");
+    }
+
+    @Test
+    public void testRolesAddedToRolesMapBySubclassAreEnforced() throws 
Exception {
+        Method target = UnsecuredService.class.getMethod("delete");
+
+        SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor() {
+            @Override
+            protected void findRoles(Class<?> cls, Map<String, String> 
rolesMap) {
+                super.findRoles(cls, rolesMap);
+                rolesMap.put("delete", "admin");
+            }
+        };
+        in.setSecuredObject(new UnsecuredService());
+        assertAllowed(in, target, "admin");
+        assertDenied(in, target, "user");
+    }
+
+    private static SecureAnnotationsInterceptor createInterceptor(Object 
service,
+                                                                  Class<?> 
annotationClass) {
+        SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor();
+        in.setAnnotationClassName(annotationClass.getName());
+        in.setSecuredObject(service);
+        return in;
+    }
+
+    private static void assertAllowed(Object service, Method method, String... 
roles) {
+        assertAllowed(createInterceptor(service, RolesAllowed.class), method, 
roles);
+    }
+
+    private static void assertDenied(Object service, Method method, String... 
roles) {
+        assertDenied(createInterceptor(service, RolesAllowed.class), method, 
roles);
+    }
+
+    private static void assertAllowed(SecureAnnotationsInterceptor in, Method 
method, String... roles) {
+        in.handleMessage(createMessage(method, roles));
+    }
+
+    private static void assertDenied(SecureAnnotationsInterceptor in, Method 
method, String... roles) {
+        assertThrows(AccessDeniedException.class, () -> 
in.handleMessage(createMessage(method, roles)));
+    }
+
+    // A message from an anonymous user when no roles are given
+    private static Message createMessage(Method method, String... roles) {
+        Message m = new MessageImpl();
+        m.setExchange(new ExchangeImpl());
+        m.put("org.apache.cxf.resource.method", method);
+        if (roles.length > 0) {
+            Set<String> userRoles = new HashSet<>(Arrays.asList(roles));
+            m.put(SecurityContext.class, new SecurityContext() {
+                public Principal getUserPrincipal() {
+                    return new SimplePrincipal("user");
+                }
+
+                public boolean isUserInRole(String role) {
+                    return userRoles.contains(role);
+                }
+            });
+        }
+        return m;
+    }
+
+    @Retention (RetentionPolicy.RUNTIME)
+    @Target({ElementType.TYPE, ElementType.METHOD })
+    @Inherited
+    public @interface InheritedRoles {
+        String[] value();
+    }
+
+    public static class BaseService {
+        @RolesAllowed("user")
+        public void operation() {
+        }
+    }
+
+    public static class OverloadingService extends BaseService {
+        @RolesAllowed("admin")
+        public void operation(String value) {
+        }
+    }
+
+    public static class SameClassOverloadingService {
+        @RolesAllowed("user")
+        public void operation() {
+        }
+
+        @RolesAllowed("admin")
+        public void operation(String value) {
+        }
+    }
+
+    public static class OverridingService extends BaseService {
+        @Override
+        @RolesAllowed("admin")
+        public void operation() {
+        }
+    }
+
+    public static class AdminBaseService {
+        @RolesAllowed("admin")
+        public void operation() {
+        }
+    }
+
+    public static class UserOverridingService extends AdminBaseService {
+        @Override
+        @RolesAllowed("user")
+        public void operation() {
+        }
+    }
+
+    @RolesAllowed("user")
+    public interface UserApi {
+        void operation();
+    }
+
+    public static class UserApiAdminService implements UserApi {
+        @Override
+        @RolesAllowed("admin")
+        public void operation() {
+        }
+    }
+
+    @RolesAllowed("user")
+    public static class ClassRolesBaseResource {
+        public void operation() {
+        }
+    }
+
+    public static class OverridingSubResource extends ClassRolesBaseResource {
+        @Override
+        @RolesAllowed("admin")
+        public void operation() {
+        }
+    }
+
+    public static class AnnotatedSubResource extends ClassRolesBaseResource {
+        @RolesAllowed("admin")
+        public void adminOperation() {
+        }
+    }
+
+    public static class InheritingService extends BaseService {
+    }
+
+    public static class UnannotatedOverridingService extends BaseService {
+        @Override
+        public void operation() {
+        }
+    }
+
+    public static class UnannotatedOverridingAnnotatedService extends 
BaseService {
+        @Override
+        public void operation() {
+        }
+
+        @RolesAllowed("admin")
+        public void adminOperation() {
+        }
+    }
+
+    public interface AdminApi {
+        @RolesAllowed("admin")
+        void delete();
+    }
+
+    public static class AdminApiService implements AdminApi {
+        @Override
+        public void delete() {
+        }
+
+        @RolesAllowed("user")
+        public void list() {
+        }
+    }
+
+    @InheritedRoles("user")
+    public static class InheritedRolesBaseService {
+        @InheritedRoles("admin")
+        public void operation() {
+        }
+    }
+
+    public static class InheritedRolesOverridingService extends 
InheritedRolesBaseService {
+        @Override
+        public void operation() {
+        }
+    }
+
+    public static class InheritedRolesSubService extends 
InheritedRolesBaseService {
+        public void subclassOperation() {
+        }
+
+        @InheritedRoles("admin")
+        public void adminOperation() {
+        }
+    }
+
+    @InheritedRoles("user")
+    public static class RedeclaringInheritedRolesService extends 
InheritedRolesBaseService {
+        @InheritedRoles("admin")
+        public void adminOperation() {
+        }
+    }
+
+    @RolesAllowed("admin")
+    public static class AdminSubService extends BaseService {
+        @Override
+        public void operation() {
+        }
+    }
+
+    public interface AdminOperationApi {
+        @RolesAllowed("admin")
+        void operation();
+    }
+
+    public static class AdminApiUserService extends BaseService implements 
AdminOperationApi {
+        @Override
+        public void operation() {
+        }
+    }
+
+    public interface Repository<T> {
+        @RolesAllowed("admin")
+        void update(T value);
+
+        @RolesAllowed("user")
+        void update();
+    }
+
+    public static class StringRepository implements Repository<String> {
+        @Override
+        public void update(String value) {
+        }
+
+        @Override
+        public void update() {
+        }
+    }
+
+    public static class UnsecuredService {
+        public void delete() {
+        }
+    }
+
     @Retention (RetentionPolicy.RUNTIME)
     @Target({ElementType.TYPE, ElementType.METHOD })
     public @interface SecureRolesAllowed {

Reply via email to