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 {
