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")
