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

coheigea pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 8f53591b7a05b871f011d6ba027a0387f1aba735
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Nov 4 06:30:08 2026 +0000

    Don't apply class-level scopes and claims to inherited annotated methods 
(#3672)
    
    (cherry picked from commit 3c9a452810e7073c290b6085ddf131d783395540)
---
 .../security/oauth2/filters/OAuthScopesFilter.java |  5 ++-
 .../oauth2/filters/OAuthScopesFilterTest.java      | 26 ++++++++++++++
 .../interceptor/ClaimsAuthorizingInterceptor.java  | 16 +++++++--
 .../ClaimsAuthorizingInterceptorTest.java          | 41 ++++++++++++++++++++++
 4 files changed, 85 insertions(+), 3 deletions(-)

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 98dadd24210..580bcb0bc94 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
@@ -108,7 +108,10 @@ public class OAuthScopesFilter implements 
ContainerRequestFilter {
             if (methodScopes != null) {
                 declarations.add(new ScopesDeclaration(m.getDeclaringClass(), 
methodScopes, true));
             }
-            if (classScopes != null) {
+            // A class-level @Scopes does not apply to a method inherited from 
a superclass which
+            // has its own method-level @Scopes, only to the class's own 
methods and to
+            // unannotated inherited methods
+            if (classScopes != null && (methodScopes == null || 
m.getDeclaringClass() == cls)) {
                 declarations.add(new ScopesDeclaration(classScopesDeclarer, 
classScopes, false));
             }
             signaturesByName.computeIfAbsent(m.getName(), k -> new 
HashSet<>()).add(signature);
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 c7f328732e1..1e4e5a63695 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
@@ -180,6 +180,23 @@ public class OAuthScopesFilterTest {
         assertForbidden(new InheritingStringRepository(), method, "read");
     }
 
+    @Test
+    public void testSubclassClassScopesDoNotApplyToInheritedAnnotatedMethods() 
throws Exception {
+        // The inherited method keeps the scopes of the superclass which 
declares it
+        Method method = AdminInheritingService.class.getMethod("operation");
+
+        assertAllowed(new AdminInheritingService(), method, "read");
+        assertForbidden(new AdminInheritingService(), method, "admin");
+    }
+
+    @Test
+    public void testSubclassClassScopesApplyToInheritedUnannotatedMethods() 
throws Exception {
+        Method method = 
AdminInheritingService.class.getMethod("unannotatedOperation");
+
+        assertAllowed(new AdminInheritingService(), method, "admin");
+        assertForbidden(new AdminInheritingService(), method, "read");
+    }
+
     @Test
     public void testMethodScopesFromUnrelatedSupertypesApplyTogether() throws 
Exception {
         Method method = AdminApiReadService.class.getMethod("operation");
@@ -349,6 +366,15 @@ public class OAuthScopesFilterTest {
         }
     }
 
+    public static class UnannotatedMethodBaseService extends BaseService {
+        public void unannotatedOperation() {
+        }
+    }
+
+    @Scopes("admin")
+    public static class AdminInheritingService extends 
UnannotatedMethodBaseService {
+    }
+
     public static class OverloadingService extends BaseService {
         @Scopes("admin")
         public void operation(String value) {
diff --git 
a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
 
b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
index 84ab8e45459..2c52512a6ed 100644
--- 
a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
+++ 
b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
@@ -225,8 +225,20 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
             if (!methodClaims.isEmpty()) {
                 declarations.add(new ClaimsDeclaration(m.getDeclaringClass(), 
methodClaims, true));
             }
-            if (!clsClaims.isEmpty()) {
-                declarations.add(new ClaimsDeclaration(cls, clsClaims, false));
+            // A class-level @Claim does not apply to a method inherited from 
a superclass which
+            // has its own method-level @Claim of the same name and format, 
only to the class's
+            // own methods and to the other claims of inherited methods
+            List<ClaimBean> classClaims = clsClaims;
+            if (!methodClaims.isEmpty() && m.getDeclaringClass() != cls) {
+                classClaims = new ArrayList<>();
+                for (ClaimBean bean : clsClaims) {
+                    if (!isClaimOverridden(bean, methodClaims)) {
+                        classClaims.add(bean);
+                    }
+                }
+            }
+            if (!classClaims.isEmpty()) {
+                declarations.add(new ClaimsDeclaration(cls, classClaims, 
false));
             }
             signaturesByName.computeIfAbsent(m.getName(), k -> new 
HashSet<>()).add(signature);
             annotationClaimsFound |= !methodClaims.isEmpty() || 
!clsClaims.isEmpty();
diff --git 
a/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
 
b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
index ef1155a4677..e0da1ca8352 100644
--- 
a/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
+++ 
b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
@@ -506,6 +506,36 @@ public class ClaimsAuthorizingInterceptorTest {
             createClaim("http://old-authentication";, "http://claims";, 
"smartcard")));
     }
 
+    @Test
+    public void testSubclassClassClaimsDoNotApplyToInheritedAnnotatedMethods() 
throws Exception {
+        // The inherited method keeps the claims of the superclass which 
declares it
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
AdminInheritingService());
+        Method method = AdminInheritingService.class.getMethod("update");
+
+        in.handleMessage(prepareMessage(method, createDefaultClaim("user")));
+        assertDenied(in, prepareMessage(method, createDefaultClaim("admin")));
+    }
+
+    @Test
+    public void testSubclassClassClaimsApplyToInheritedUnannotatedMethods() 
throws Exception {
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
AdminInheritingService());
+        Method method = 
AdminInheritingService.class.getMethod("unannotatedOperation");
+
+        in.handleMessage(prepareMessage(method, createDefaultClaim("admin")));
+        assertDenied(in, prepareMessage(method, createDefaultClaim("user")));
+    }
+
+    @Test
+    public void 
testSubclassClassClaimsOfOtherTypesApplyToInheritedAnnotatedMethods() throws 
Exception {
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
LocationInheritingService());
+        Method method = LocationInheritingService.class.getMethod("update");
+        org.apache.cxf.rt.security.claims.Claim location = 
createClaim("http://location";,
+            "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", "UK");
+
+        in.handleMessage(prepareMessage(method, createDefaultClaim("user"), 
location));
+        assertDenied(in, prepareMessage(method, createDefaultClaim("user")));
+    }
+
     private static ClaimsAuthorizingInterceptor createInterceptor(Object 
service) {
         ClaimsAuthorizingInterceptor in = new ClaimsAuthorizingInterceptor();
         in.setSecuredObject(service);
@@ -715,6 +745,17 @@ public class ClaimsAuthorizingInterceptorTest {
 
         }
     }
+    public static class UnannotatedMethodBaseService extends BaseService {
+        public void unannotatedOperation() {
+
+        }
+    }
+    @Claim("admin")
+    public static class AdminInheritingService extends 
UnannotatedMethodBaseService {
+    }
+    @Claim(name = "http://location";, value = "UK")
+    public static class LocationInheritingService extends BaseService {
+    }
     public static class OverridingService extends BaseService {
         @Override
         @Claim("admin")

Reply via email to