This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/annotated in repository https://gitbox.apache.org/repos/asf/cxf.git
commit f2d8e668923d590520f744c8c90523ef5719f95a Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Tue Nov 3 16:46:36 2026 +0000 Don't apply class-level scopes and claims to inherited annotated methods --- .../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 8caa89bdf65..aac15f624f6 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 @@ -107,7 +107,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 6ccbd96ced1..4973659912f 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 @@ -179,6 +179,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"); @@ -348,6 +365,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")
