This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
new e6787195118 Enforce every applicable @Scopes rule in OAuthScopesFilter
(#3651)
e6787195118 is described below
commit e67871951185478cfb8252be315761b97c356aae
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Oct 30 04:36:30 2026 +0000
Enforce every applicable @Scopes rule in OAuthScopesFilter (#3651)
* Enforce every applicable @Scopes rule in OAuthScopesFilter
* Fixes following review
(cherry picked from commit d82604c8ade55f8d58cb25ddd38b40ea4237d408)
---
.../org/apache/cxf/common/util/ReflectionUtil.java | 93 +++++++++
.../apache/cxf/common/util/ReflectionUtilTest.java | 92 +++++++++
.../security/oauth2/filters/OAuthScopesFilter.java | 121 ++++++++---
.../oauth2/filters/OAuthScopesFilterTest.java | 229 ++++++++++++++++++++-
4 files changed, 497 insertions(+), 38 deletions(-)
diff --git a/core/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
b/core/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
index 92f89082aa1..26a58b11bf1 100644
--- a/core/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
+++ b/core/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
@@ -23,16 +23,28 @@ import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
@@ -187,6 +199,87 @@ public final class ReflectionUtil {
return sb.append(')').toString();
}
+ /**
+ * Returns the methods of the superclasses and interfaces of a class which
one of its public
+ * methods overrides or implements with different (erased) parameter
types, because the
+ * overridden method's parameter types are type variables. For example,
for update(String) of
+ * a class implementing Repository<String>, update(T) of Repository
is returned.
+ */
+ public static List<Method> getGenericallyOverriddenMethods(final Class<?>
cls, final Method method) {
+ Map<TypeVariable<?>, Type> typeArguments = new HashMap<>();
+ Set<Class<?>> supertypes = new LinkedHashSet<>();
+ collectTypeArguments(cls, typeArguments, supertypes);
+
+ List<Method> overridden = new ArrayList<>();
+ Class<?>[] paramTypes = method.getParameterTypes();
+ for (Class<?> supertype : supertypes) {
+ for (Method m : getDeclaredMethods(supertype)) {
+ if (m.isBridge() || Modifier.isStatic(m.getModifiers())
+ || Modifier.isPrivate(m.getModifiers())
+ || !m.getName().equals(method.getName())
+ || m.getParameterCount() != paramTypes.length
+ || Arrays.equals(m.getParameterTypes(), paramTypes)) {
+ continue;
+ }
+ Type[] genericParamTypes = m.getGenericParameterTypes();
+ boolean matches = genericParamTypes.length ==
paramTypes.length;
+ for (int i = 0; matches && i < paramTypes.length; i++) {
+ matches = resolveType(genericParamTypes[i], typeArguments)
== paramTypes[i];
+ }
+ if (matches) {
+ overridden.add(m);
+ }
+ }
+ }
+ return overridden;
+ }
+
+ private static void collectTypeArguments(Type type, Map<TypeVariable<?>,
Type> typeArguments,
+ Set<Class<?>> supertypes) {
+ final Class<?> cls;
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType)type;
+ cls = (Class<?>)parameterizedType.getRawType();
+ TypeVariable<?>[] typeVariables = cls.getTypeParameters();
+ Type[] actualTypes = parameterizedType.getActualTypeArguments();
+ for (int i = 0; i < typeVariables.length && i <
actualTypes.length; i++) {
+ typeArguments.putIfAbsent(typeVariables[i], actualTypes[i]);
+ }
+ } else if (type instanceof Class) {
+ cls = (Class<?>)type;
+ } else {
+ return;
+ }
+ if (cls == Object.class || !supertypes.add(cls)) {
+ return;
+ }
+ collectTypeArguments(cls.getGenericSuperclass(), typeArguments,
supertypes);
+ for (Type interfaceType : cls.getGenericInterfaces()) {
+ collectTypeArguments(interfaceType, typeArguments, supertypes);
+ }
+ }
+
+ private static Class<?> resolveType(Type type, Map<TypeVariable<?>, Type>
typeArguments) {
+ if (type instanceof Class) {
+ return (Class<?>)type;
+ } else if (type instanceof ParameterizedType) {
+ return (Class<?>)((ParameterizedType)type).getRawType();
+ } else if (type instanceof GenericArrayType) {
+ Class<?> componentType =
+
resolveType(((GenericArrayType)type).getGenericComponentType(), typeArguments);
+ return Array.newInstance(componentType, 0).getClass();
+ } else if (type instanceof WildcardType) {
+ return resolveType(((WildcardType)type).getUpperBounds()[0],
typeArguments);
+ } else if (type instanceof TypeVariable) {
+ // A type variable which is not bound by the class's hierarchy
resolves to its bound
+ Type actualType = typeArguments.get(type);
+ return actualType != null && actualType != type
+ ? resolveType(actualType, typeArguments)
+ : resolveType(((TypeVariable<?>)type).getBounds()[0],
typeArguments);
+ }
+ return Object.class;
+ }
+
public static Field[] getDeclaredFields(final Class<?> cls) {
return AccessController.doPrivileged(new PrivilegedAction<Field[]>() {
public Field[] run() {
diff --git
a/core/src/test/java/org/apache/cxf/common/util/ReflectionUtilTest.java
b/core/src/test/java/org/apache/cxf/common/util/ReflectionUtilTest.java
new file mode 100644
index 00000000000..d4f094253b0
--- /dev/null
+++ b/core/src/test/java/org/apache/cxf/common/util/ReflectionUtilTest.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.common.util;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ReflectionUtilTest {
+
+ @Test
+ public void testGetMethodSignature() throws Exception {
+ assertEquals("update(java.lang.String,int)",
+ ReflectionUtil.getMethodSignature(Impl.class.getMethod("update",
String.class, int.class)));
+ assertEquals("update()",
ReflectionUtil.getMethodSignature(Impl.class.getMethod("update")));
+ }
+
+ @Test
+ public void testGetGenericallyOverriddenMethods() throws Exception {
+ List<Method> overridden =
ReflectionUtil.getGenericallyOverriddenMethods(Impl.class,
+ Impl.class.getMethod("update", String.class));
+
assertEquals(Collections.singletonList(Repository.class.getMethod("update",
Object.class)),
+ overridden);
+ }
+
+ @Test
+ public void testGetGenericallyOverriddenMethodsIgnoresUnrelatedOverloads()
throws Exception {
+ assertTrue(ReflectionUtil.getGenericallyOverriddenMethods(Impl.class,
+ Impl.class.getMethod("update", Integer.class)).isEmpty());
+ assertTrue(ReflectionUtil.getGenericallyOverriddenMethods(Impl.class,
+ Impl.class.getMethod("update")).isEmpty());
+ }
+
+ @Test
+ public void
testGetGenericallyOverriddenMethodsWithInheritedImplementation() throws
Exception {
+ List<Method> overridden =
ReflectionUtil.getGenericallyOverriddenMethods(InheritingImpl.class,
+ InheritingImpl.class.getMethod("update", String.class));
+
assertEquals(Collections.singletonList(Repository.class.getMethod("update",
Object.class)),
+ overridden);
+ }
+
+ public interface Repository<T> {
+ void update(T value);
+ }
+
+ public abstract static class AbstractRepository<T> implements
Repository<T> { // NOPMD
+ }
+
+ public static class Impl extends AbstractRepository<String> {
+ @Override
+ public void update(String value) {
+ }
+
+ public void update(Integer value) {
+ }
+
+ public void update(String value, int count) {
+ }
+
+ public void update() {
+ }
+ }
+
+ public static class StringUpdater {
+ public void update(String value) {
+ }
+ }
+
+ public static class InheritingImpl extends StringUpdater implements
Repository<String> {
+ }
+}
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilter.java
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilter.java
index a43cb02d6c1..c3c50bf4f0e 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilter.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilter.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.LinkedList;
import java.util.List;
import java.util.Map;
@@ -58,53 +59,61 @@ public class OAuthScopesFilter implements
ContainerRequestFilter {
// Scopes configured explicitly via setScopesMap/setScopesStringMap, keyed
by method name
private Map<String, List<String>> scopesMap = new HashMap<>();
private Map<String, Boolean> scopesMatchAllMap = new HashMap<>();
- // Scopes discovered from @Scopes annotations. Method-level and
class-level annotations
- // are keyed by method signature so that overloaded methods do not share a
rule and an
- // annotation on an overriding method is not replaced by one from a
superclass or interface
- private final Map<String, Scopes> methodScopesBySignature = new
HashMap<>();
- private final Map<String, Scopes> classScopesBySignature = new HashMap<>();
- // All the annotation-derived rules for a given method name, enforced
together when the
- // invoked method's signature is not recorded (e.g. it implements a
generic interface method)
- private final Map<String, Set<Scopes>> annotationScopesByName = new
HashMap<>();
+ // The method-level and class-level @Scopes declarations 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<ScopesDeclaration>> declarationsBySignature
= new HashMap<>();
+ // The erased signatures a method overrides with a different signature,
e.g. update(Object)
+ // for update(String) implementing update(T) of Repository<String>
+ private final Map<String, Set<String>> overriddenSignatures = new
HashMap<>();
+ private final Map<String, Set<String>> signaturesByName = new HashMap<>();
+ private final Set<Class<?>> securedClasses = new HashSet<>();
+ private boolean annotationScopesFound;
private Set<String> confidentialClientMethods = new HashSet<>();
public void setSecuredObject(Object object) {
Class<?> cls = ClassHelper.getRealClass(object);
checkSecureClass(cls);
- if (annotationScopesByName.isEmpty()) {
+ if (!annotationScopesFound) {
LOG.warning("The scopes map is empty");
} else if (LOG.isLoggable(Level.FINE)) {
- for (Map.Entry<String, Set<Scopes>> entry :
annotationScopesByName.entrySet()) {
- for (Scopes scopes : entry.getValue()) {
- LOG.fine("Method: " + entry.getKey() + ", scopes: " +
Arrays.asList(scopes.value()));
+ for (String signature : declarationsBySignature.keySet()) {
+ for (Scopes scopes : getAnnotationScopes(signature)) {
+ LOG.fine("Method: " + signature + ", scopes: " +
Arrays.asList(scopes.value()));
}
}
}
}
protected void checkSecureClass(Class<?> cls) {
- if (cls == null || cls == Object.class) {
+ if (cls == null || cls == Object.class || !securedClasses.add(cls)) {
return;
}
+ // The class-level @Scopes may be inherited from a superclass (@Scopes
is @Inherited)
Scopes classScopes = cls.getAnnotation(Scopes.class);
+ Class<?> classScopesDeclarer = getScopesDeclarer(cls);
ConfidentialClient classConfClient =
cls.getAnnotation(ConfidentialClient.class);
for (Method m : cls.getMethods()) {
if (SKIP_METHODS.contains(m.getName())) {
continue;
}
- // Subclasses are scanned before their superclasses and
interfaces, so the most
- // specific declaration of a given signature is recorded first and
kept
String signature = ReflectionUtil.getMethodSignature(m);
Scopes methodScopes = m.getAnnotation(Scopes.class);
- // Each overload has its own signature and so its own rule. The
rules are also
- // collected by name, but that is only used as a fallback when the
invoked
- // method's signature was not recorded, and then all of them have
to be met
+ List<ScopesDeclaration> declarations =
+ declarationsBySignature.computeIfAbsent(signature, k -> new
ArrayList<>());
if (methodScopes != null) {
- methodScopesBySignature.putIfAbsent(signature, methodScopes);
- annotationScopesByName.computeIfAbsent(m.getName(), k -> new
HashSet<>()).add(methodScopes);
- } else if (classScopes != null) {
- classScopesBySignature.putIfAbsent(signature, classScopes);
- annotationScopesByName.computeIfAbsent(m.getName(), k -> new
HashSet<>()).add(classScopes);
+ declarations.add(new ScopesDeclaration(m.getDeclaringClass(),
methodScopes, true));
+ }
+ if (classScopes != null) {
+ declarations.add(new ScopesDeclaration(classScopesDeclarer,
classScopes, false));
+ }
+ signaturesByName.computeIfAbsent(m.getName(), k -> new
HashSet<>()).add(signature);
+ annotationScopesFound |= methodScopes != null || classScopes !=
null;
+ if (!m.isBridge()) {
+ for (Method overridden :
ReflectionUtil.getGenericallyOverriddenMethods(cls, m)) {
+ overriddenSignatures.computeIfAbsent(signature, k -> new
HashSet<>())
+ .add(ReflectionUtil.getMethodSignature(overridden));
+ }
}
ConfidentialClient mConfClient =
m.getAnnotation(ConfidentialClient.class);
@@ -161,19 +170,56 @@ public class OAuthScopesFilter implements
ContainerRequestFilter {
}
}
+ private static Class<?> getScopesDeclarer(Class<?> cls) {
+ Class<?> declarer = cls;
+ while (declarer != null &&
declarer.getDeclaredAnnotation(Scopes.class) == null) {
+ declarer = declarer.getSuperclass();
+ }
+ return declarer == null ? cls : declarer;
+ }
+
private List<Scopes> getAnnotationScopes(Method m) {
String signature = ReflectionUtil.getMethodSignature(m);
- // A method-level annotation anywhere in the hierarchy takes
precedence over a
- // class-level one, otherwise an inherited class-level @Scopes would
replace it
- Scopes scopes = methodScopesBySignature.get(signature);
- if (scopes == null) {
- scopes = classScopesBySignature.get(signature);
+ Set<String> signatures = new HashSet<>();
+ if (declarationsBySignature.containsKey(signature)) {
+ signatures.add(signature);
+ signatures.addAll(overriddenSignatures.getOrDefault(signature,
Collections.emptySet()));
+ } else {
+ // The method is not one of the secured object's methods, so every
rule
+ // recorded for its name applies
+ signatures.addAll(signaturesByName.getOrDefault(m.getName(),
Collections.emptySet()));
+ }
+ Set<Scopes> scopes = new LinkedHashSet<>();
+ for (String s : signatures) {
+ scopes.addAll(getAnnotationScopes(s));
+ }
+ return new ArrayList<>(scopes);
+ }
+
+ private List<Scopes> getAnnotationScopes(String signature) {
+ // Every @Scopes which applies to the method has to be met, from any
class or interface
+ // in the hierarchy. A class-level @Scopes is replaced by a
method-level @Scopes declared on
+ // the same class or interface, or on a subclass of it
+ List<ScopesDeclaration> declarations =
+ declarationsBySignature.getOrDefault(signature,
Collections.emptyList());
+ Set<Scopes> scopes = new LinkedHashSet<>();
+ for (ScopesDeclaration declaration : declarations) {
+ if (declaration.methodLevel ||
!isReplacedByMethodScopes(declaration, declarations)) {
+ scopes.add(declaration.scopes);
+ }
}
- if (scopes != null) {
- return Collections.singletonList(scopes);
+ return new ArrayList<>(scopes);
+ }
+
+ private static boolean isReplacedByMethodScopes(ScopesDeclaration
classDeclaration,
+ List<ScopesDeclaration>
declarations) {
+ for (ScopesDeclaration declaration : declarations) {
+ if (declaration.methodLevel
+ &&
classDeclaration.declaringClass.isAssignableFrom(declaration.declaringClass)) {
+ return true;
+ }
}
- Set<Scopes> scopesForName = annotationScopesByName.get(m.getName());
- return scopesForName == null ? Collections.emptyList() : new
ArrayList<>(scopesForName);
+ return false;
}
private static boolean isAllowed(List<String> requestScopes, List<String>
methodScopes,
@@ -215,5 +261,16 @@ public class OAuthScopesFilter implements
ContainerRequestFilter {
this.confidentialClientMethods = confidentialClientMethods;
}
+ private static final class ScopesDeclaration {
+ // The class or interface the annotation is declared on
+ private final Class<?> declaringClass;
+ private final Scopes scopes;
+ private final boolean methodLevel;
+ ScopesDeclaration(Class<?> declaringClass, Scopes scopes, boolean
methodLevel) {
+ this.declaringClass = declaringClass;
+ this.scopes = scopes;
+ this.methodLevel = methodLevel;
+ }
+ }
}
diff --git
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilterTest.java
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilterTest.java
index ca125bfef26..c8d165e7f10 100644
---
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilterTest.java
+++
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/OAuthScopesFilterTest.java
@@ -63,21 +63,38 @@ public class OAuthScopesFilterTest {
}
@Test
- public void testOverridingMethodScopesTakePrecedenceOverSuperclass()
throws Exception {
+ public void testOverridingMethodScopesApplyTogetherWithSuperclassScopes()
throws Exception {
Method method = OverridingService.class.getMethod("operation");
- assertAllowed(new OverridingService(), method, "admin");
+ assertAllowed(new OverridingService(), method, "read", "admin");
assertForbidden(new OverridingService(), method, "read");
+ assertForbidden(new OverridingService(), method, "admin");
}
@Test
- public void
testOverridingMethodScopesTakePrecedenceOverInterfaceClassScopes() throws
Exception {
+ public void testOverridingMethodCannotWeakenSuperclassScopes() throws
Exception {
+ Method method = ReadOverridingService.class.getMethod("operation");
+
+ assertAllowed(new ReadOverridingService(), method, "read", "admin");
+ assertForbidden(new ReadOverridingService(), method, "read");
+ }
+
+ @Test
+ public void testOverridingMethodScopesReplaceInterfaceClassScopes() throws
Exception {
Method method =
InterfaceOverridingService.class.getMethod("operation");
assertAllowed(new InterfaceOverridingService(), method, "admin");
assertForbidden(new InterfaceOverridingService(), method, "read");
}
+ @Test
+ public void testOverridingMethodScopesReplaceSuperclassClassScopes()
throws Exception {
+ Method method = OverridingSubResource.class.getMethod("operation");
+
+ assertAllowed(new OverridingSubResource(), method, "admin");
+ assertForbidden(new OverridingSubResource(), method, "read");
+ }
+
@Test
public void testMatchAllIsResolvedPerSignature() throws Exception {
Method matchAllMethod = MatchAllService.class.getMethod("operation",
String.class);
@@ -127,13 +144,95 @@ public class OAuthScopesFilterTest {
public void testGenericInterfaceMethodScopesAreEnforced() throws Exception
{
Method updateMethod = StringRepository.class.getMethod("update",
String.class);
- // The erased interface signature does not match, so every rule for
the name applies
- assertAllowed(new StringRepository(), updateMethod, "admin", "read");
+ // update(String) overrides the erased update(Object), but not the
update() overload
+ assertAllowed(new StringRepository(), updateMethod, "admin");
assertForbidden(new StringRepository(), updateMethod, "read");
- assertForbidden(new StringRepository(), updateMethod, "admin");
Method readMethod = StringRepository.class.getMethod("update");
assertAllowed(new StringRepository(), readMethod, "read");
+ assertForbidden(new StringRepository(), readMethod, "admin");
+ }
+
+ @Test
+ public void testGenericInterfaceMethodScopesApplyTogetherWithClassScopes()
throws Exception {
+ Method method = ClassScopesStringRepository.class.getMethod("update",
String.class);
+
+ assertAllowed(new ClassScopesStringRepository(), method, "user",
"admin");
+ assertForbidden(new ClassScopesStringRepository(), method, "user");
+ assertForbidden(new ClassScopesStringRepository(), method, "admin");
+ }
+
+ @Test
+ public void testSubclassScopesApplyTogetherWithSuperclassMethodScopes()
throws Exception {
+ Method method = AdminSubService.class.getMethod("operation");
+
+ assertAllowed(new AdminSubService(), method, "read", "admin");
+ assertForbidden(new AdminSubService(), method, "read");
+ assertForbidden(new AdminSubService(), method, "admin");
+ }
+
+ @Test
+ public void
testGenericInterfaceMethodScopesApplyToInheritedImplementation() throws
Exception {
+ Method method = InheritingStringRepository.class.getMethod("update",
String.class);
+
+ assertAllowed(new InheritingStringRepository(), method, "admin");
+ assertForbidden(new InheritingStringRepository(), method, "read");
+ }
+
+ @Test
+ public void testMethodScopesFromUnrelatedSupertypesApplyTogether() throws
Exception {
+ Method method = AdminApiReadService.class.getMethod("operation");
+
+ assertAllowed(new AdminApiReadService(), method, "read", "admin");
+ assertForbidden(new AdminApiReadService(), method, "read");
+ assertForbidden(new AdminApiReadService(), method, "admin");
+ }
+
+ @Test
+ public void testMethodScopesReplaceInheritedClassScopes() throws Exception
{
+ Method method =
InheritingAdminService.class.getMethod("adminOperation");
+ assertAllowed(new InheritingAdminService(), method, "admin");
+ assertForbidden(new InheritingAdminService(), method, "read");
+
+ Method redeclaredMethod =
RedeclaringAdminService.class.getMethod("adminOperation");
+ assertAllowed(new RedeclaringAdminService(), redeclaredMethod,
"admin");
+ assertForbidden(new RedeclaringAdminService(), redeclaredMethod,
"read");
+ }
+
+ @Test
+ public void testUnrelatedOverloadIsNotLinkedToGenericInterfaceMethod()
throws Exception {
+ Method integerMethod =
OverloadingStringRepository.class.getMethod("update", Integer.class);
+ assertAllowed(new OverloadingStringRepository(), integerMethod,
"read");
+ assertForbidden(new OverloadingStringRepository(), integerMethod,
"admin");
+
+ Method stringMethod =
OverloadingStringRepository.class.getMethod("update", String.class);
+ assertAllowed(new OverloadingStringRepository(), stringMethod,
"admin");
+ assertForbidden(new OverloadingStringRepository(), stringMethod,
"read");
+ }
+
+ @Test
+ public void testGenericInterfaceMethodScopesApplyThroughTypeVariables()
throws Exception {
+ Method method = TypedStringRepository.class.getMethod("update",
String.class);
+
+ assertAllowed(new TypedStringRepository(), method, "admin");
+ assertForbidden(new TypedStringRepository(), method, "read");
+ }
+
+ @Test
+ public void testUnannotatedOverloadIsNotSecuredByOtherOverloads() throws
Exception {
+ Method method = UnannotatedOverloadService.class.getMethod("delete");
+
+ assertAllowed(new UnannotatedOverloadService(), method, "user");
+ assertForbidden(new UnannotatedOverloadService(),
+ UnannotatedOverloadService.class.getMethod("delete",
String.class), "user");
+ }
+
+ @Test
+ public void testInheritedClassScopesApplyToSubclassMethods() throws
Exception {
+ Method method =
InheritedClassScopesService.class.getMethod("subclassOperation");
+
+ assertAllowed(new InheritedClassScopesService(), method, "read");
+ assertForbidden(new InheritedClassScopesService(), method, "write");
}
@Test
@@ -213,6 +312,19 @@ public class OAuthScopesFilterTest {
}
}
+ public static class AdminBaseService {
+ @Scopes("admin")
+ public void operation() {
+ }
+ }
+
+ public static class ReadOverridingService extends AdminBaseService {
+ @Override
+ @Scopes("read")
+ public void operation() {
+ }
+ }
+
public static class UnannotatedOverridingService extends BaseService {
@Override
public void operation() {
@@ -248,6 +360,9 @@ public class OAuthScopesFilterTest {
@Override
public void operation() {
}
+
+ public void subclassOperation() {
+ }
}
public interface Repository<T> {
@@ -268,6 +383,108 @@ public class OAuthScopesFilterTest {
}
}
+ @Scopes("user")
+ public static class ClassScopesStringRepository implements
Repository<String> {
+ @Override
+ public void update(String value) {
+ }
+
+ @Override
+ public void update() {
+ }
+ }
+
+ @Scopes("admin")
+ public static class AdminSubService extends BaseService {
+ @Override
+ public void operation() {
+ }
+ }
+
+ public static class StringUpdater {
+ public void update(String value) {
+ }
+
+ public void update() {
+ }
+ }
+
+ public static class InheritingStringRepository extends StringUpdater
implements Repository<String> {
+ }
+
+ public interface AdminApi {
+ @Scopes("admin")
+ void operation();
+ }
+
+ public static class AdminApiReadService extends BaseService implements
AdminApi {
+ @Override
+ public void operation() {
+ }
+ }
+
+ @Scopes("read")
+ public static class ClassScopesBaseResource {
+ public void operation() {
+ }
+ }
+
+ public static class OverridingSubResource extends ClassScopesBaseResource {
+ @Override
+ @Scopes("admin")
+ public void operation() {
+ }
+ }
+
+ public static class InheritingAdminService extends
InheritedClassScopesBaseService {
+ @Scopes("admin")
+ public void adminOperation() {
+ }
+ }
+
+ @Scopes("read")
+ public static class RedeclaringAdminService extends
InheritedClassScopesBaseService {
+ @Scopes("admin")
+ public void adminOperation() {
+ }
+ }
+
+ public static class OverloadingStringRepository implements
Repository<String> {
+ @Override
+ public void update(String value) {
+ }
+
+ @Override
+ public void update() {
+ }
+
+ @Scopes("read")
+ public void update(Integer value) {
+ }
+ }
+
+ public abstract static class AbstractRepository<T> implements
Repository<T> { //NOPMD
+ }
+
+ public static class TypedStringRepository extends
AbstractRepository<String> {
+ @Override
+ public void update(String value) {
+ }
+
+ @Override
+ public void update() {
+ }
+ }
+
+ public static class UnannotatedOverloadService {
+ @Scopes("admin")
+ public void delete(String id) {
+ }
+
+ public void delete() {
+ }
+ }
+
public static class UnsecuredService {
public void operation(String value) {
}