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 3c9a452810e Don't apply class-level scopes and claims to inherited
annotated methods (#3672)
3c9a452810e is described below
commit 3c9a452810e7073c290b6085ddf131d783395540
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)
---
.../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")