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 0a587a88b10 Enforce every applicable role annotation in
SecureAnnotationsInterceptor (#3669)
0a587a88b10 is described below
commit 0a587a88b109be709ff8a6f053b8b963fb68ad6c
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Nov 4 06:29:55 2026 +0000
Enforce every applicable role annotation in SecureAnnotationsInterceptor
(#3669)
* Enforce every applicable role annotation in SecureAnnotationsInterceptor
* Don't apply class-level roles to inherited annotated methods
---
.../security/SecureAnnotationsInterceptor.java | 217 +++++++---
.../security/SimpleAuthorizingInterceptor.java | 14 +-
.../security/SecureAnnotationsInterceptorTest.java | 477 ++++++++++++++++++++-
3 files changed, 651 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..284a8d5364f 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,184 @@ 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));
}
+ // As in JSR-250, a class-level annotation does not apply to a
method inherited from
+ // a superclass which has its own method-level annotation, only to
the class's own
+ // methods and to unannotated inherited methods
+ if (classRoles != null && (methodRoles == null ||
m.getDeclaringClass() == cls)) {
+ 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 +278,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..4956b01fb5a 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,469 @@ 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 testSubclassClassRolesDoNotApplyToInheritedAnnotatedMethods()
throws Exception {
+ // As in JSR-250, the inherited method keeps the roles of the
superclass which declares it
+ Method target = AdminInheritingService.class.getMethod("operation");
+
+ assertAllowed(new AdminInheritingService(), target, "user");
+ assertDenied(new AdminInheritingService(), target, "admin");
+ assertDenied(new AdminInheritingService(), target);
+ }
+
+ @Test
+ public void testSubclassClassRolesApplyToInheritedUnannotatedMethods()
throws Exception {
+ Method target =
AdminInheritingService.class.getMethod("unannotatedOperation");
+
+ assertAllowed(new AdminInheritingService(), target, "admin");
+ assertDenied(new AdminInheritingService(), target, "user");
+ assertDenied(new AdminInheritingService(), target);
+ }
+
+ @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 static class UnannotatedMethodBaseService extends BaseService {
+ public void unannotatedOperation() {
+ }
+ }
+
+ @RolesAllowed("admin")
+ public static class AdminInheritingService extends
UnannotatedMethodBaseService {
+ }
+
+ 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 {