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 8eeb89357b3 Handle SAML claims without a NameFormat and combined claim 
annotations (#3685)
8eeb89357b3 is described below

commit 8eeb89357b31dab2899a245c27279ef7b1c7e922
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Fri Nov 6 06:27:23 2026 +0000

    Handle SAML claims without a NameFormat and combined claim annotations 
(#3685)
---
 .../interceptor/ClaimsAuthorizingInterceptor.java  | 34 ++++++++----
 .../ClaimsAuthorizingInterceptorTest.java          | 61 ++++++++++++++++++++++
 2 files changed, 85 insertions(+), 10 deletions(-)

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 2c52512a6ed..4236388a6dc 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
@@ -53,6 +53,9 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
 
     private static final Set<String> SKIP_METHODS = new HashSet<>(
             Arrays.asList("wait", "notify", "notifyAll", "equals", "toString", 
"hashCode"));
+    // The SAML 2.0 default for an Attribute without a NameFormat, and the 
@Claim default format
+    private static final String UNSPECIFIED_NAME_FORMAT =
+        "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified";
 
     // Claims configured explicitly via setClaims, keyed by method name
     private Map<String, List<ClaimBean>> claims = new HashMap<>();
@@ -157,16 +160,23 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
         org.apache.cxf.rt.security.claims.ClaimCollection actualClaims = 
sc.getClaims();
 
         for (ClaimBean claimBean : list) {
+            String claimType = claimBean.getClaim().getClaimType();
             org.apache.cxf.rt.security.claims.Claim matchingClaim = null;
             for (org.apache.cxf.rt.security.claims.Claim cl : actualClaims) {
+                if (claimType == null) {
+                    break;
+                }
                 if (cl instanceof SAMLClaim) {
-                    // If it's a SAMLClaim the name + nameformat must match 
what's configured
-                    if 
(((SAMLClaim)cl).getName().equals(claimBean.getClaim().getClaimType())
-                        && 
((SAMLClaim)cl).getNameFormat().equals(claimBean.getClaimFormat())) {
+                    // If it's a SAMLClaim the name + nameformat must match 
what's configured. The
+                    // NameFormat is optional, in which case it is the 
unspecified name format
+                    SAMLClaim samlClaim = (SAMLClaim)cl;
+                    if (claimType.equals(samlClaim.getName())
+                        && getNameFormat(claimBean.getClaimFormat())
+                            .equals(getNameFormat(samlClaim.getNameFormat()))) 
{
                         matchingClaim = cl;
                         break;
                     }
-                } else if 
(cl.getClaimType().equals(claimBean.getClaim().getClaimType())) {
+                } else if (claimType.equals(cl.getClaimType())) {
                     matchingClaim = cl;
                     break;
                 }
@@ -256,6 +266,10 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
         }
     }
 
+    private static String getNameFormat(String nameFormat) {
+        return nameFormat == null ? UNSPECIFIED_NAME_FORMAT : nameFormat;
+    }
+
     private static boolean isClaimOverridden(ClaimBean bean, List<ClaimBean> 
mClaims) {
         for (ClaimBean methodBean : mClaims) {
             if 
(bean.getClaim().getClaimType().equals(methodBean.getClaim().getClaimType())
@@ -270,13 +284,13 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
             Claims claimsAnn, Claim claimAnn) {
         List<ClaimBean> claimsList = new ArrayList<>();
 
-        final List<Claim> annClaims;
+        // Both @Claims and @Claim may be present, in which case all their 
claims apply
+        final List<Claim> annClaims = new ArrayList<>();
         if (claimsAnn != null) {
-            annClaims = Arrays.asList(claimsAnn.value());
-        } else if (claimAnn != null) {
-            annClaims = Collections.singletonList(claimAnn);
-        } else {
-            annClaims = Collections.emptyList();
+            annClaims.addAll(Arrays.asList(claimsAnn.value()));
+        }
+        if (claimAnn != null) {
+            annClaims.add(claimAnn);
         }
         for (Claim ann : annClaims) {
             org.apache.cxf.rt.security.claims.Claim claim = new 
org.apache.cxf.rt.security.claims.Claim();
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 e0da1ca8352..09c66236acc 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
@@ -536,6 +536,43 @@ public class ClaimsAuthorizingInterceptorTest {
         assertDenied(in, prepareMessage(method, createDefaultClaim("user")));
     }
 
+    @Test
+    public void testClaimWithoutNameFormat() throws Exception {
+        // A SAML attribute without a NameFormat has the unspecified name 
format, the default
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
NameFormatService());
+        Method method = NameFormatService.class.getMethod("defaultFormat");
+
+        in.handleMessage(prepareMessage(method,
+            createClaim(SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, null, 
"admin")));
+        assertDenied(in, prepareMessage(method,
+            createClaim(SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, null, 
"user")));
+    }
+
+    @Test
+    public void testClaimWithoutNameFormatDoesNotMatchOtherFormat() throws 
Exception {
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
NameFormatService());
+        Method method = NameFormatService.class.getMethod("specificFormat");
+
+        assertDenied(in, prepareMessage(method, 
createClaim("http://cxf/roles";, null, "admin")));
+        in.handleMessage(prepareMessage(method, 
createClaim("http://cxf/roles";, "http://claims";, "admin")));
+    }
+
+    @Test
+    public void testClaimAndClaimsAnnotationsAreCombined() throws Exception {
+        ClaimsAuthorizingInterceptor in = createInterceptor(new 
CombinedClaimsService());
+        Method method = CombinedClaimsService.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("admin"), 
location));
+        assertDenied(in, prepareMessage(method, createDefaultClaim("admin")));
+        assertDenied(in, prepareMessage(method, location));
+
+        Method classMethod = CombinedClaimsService.class.getMethod("list");
+        in.handleMessage(prepareMessage(classMethod, 
createDefaultClaim("user"), location));
+        assertDenied(in, prepareMessage(classMethod, 
createDefaultClaim("user")));
+    }
+
     private static ClaimsAuthorizingInterceptor createInterceptor(Object 
service) {
         ClaimsAuthorizingInterceptor in = new ClaimsAuthorizingInterceptor();
         in.setSecuredObject(service);
@@ -756,6 +793,30 @@ public class ClaimsAuthorizingInterceptorTest {
     @Claim(name = "http://location";, value = "UK")
     public static class LocationInheritingService extends BaseService {
     }
+    public static class NameFormatService {
+        @Claim("admin")
+        public void defaultFormat() {
+
+        }
+
+        @Claim(name = "http://cxf/roles";, format = "http://claims";, value = 
"admin")
+        public void specificFormat() {
+
+        }
+    }
+    @Claims({@Claim(name = "http://location";, value = "UK") })
+    @Claim("user")
+    public static class CombinedClaimsService {
+        @Claims({@Claim(name = "http://location";, value = "UK") })
+        @Claim("admin")
+        public void update() {
+
+        }
+
+        public void list() {
+
+        }
+    }
     public static class OverridingService extends BaseService {
         @Override
         @Claim("admin")

Reply via email to