You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2018/04/26 14:34:58 UTC

[cxf] 03/03: CXF-7729 - Merge duplicate Attribute elements with an AttributeStatement

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

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 0cbdea0fa72f812b80cc96ff760ae30b73d3123e
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Apr 26 15:34:11 2018 +0100

    CXF-7729 - Merge duplicate Attribute elements with an AttributeStatement
---
 .../claims/ClaimsAttributeStatementProvider.java   |   5 +-
 .../CombinedClaimsAttributeStatementProvider.java  | 180 +++++++++++++++++++++
 .../cxf/sts/token/provider/SAMLTokenProvider.java  |  18 ++-
 .../apache/cxf/sts/common/CustomClaimsHandler.java |  11 +-
 .../cxf/sts/token/provider/SAMLClaimsTest.java     |  89 +++++++++-
 5 files changed, 295 insertions(+), 8 deletions(-)

diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/ClaimsAttributeStatementProvider.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/ClaimsAttributeStatementProvider.java
index 2e3624f..a4065b9 100644
--- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/ClaimsAttributeStatementProvider.java
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/ClaimsAttributeStatementProvider.java
@@ -48,6 +48,8 @@ public class ClaimsAttributeStatementProvider implements AttributeStatementProvi
 
         List<AttributeBean> attributeList = new ArrayList<>();
         String tokenType = providerParameters.getTokenRequirements().getTokenType();
+        boolean saml2 = WSS4JConstants.WSS_SAML2_TOKEN_TYPE.equals(tokenType)
+            || WSS4JConstants.SAML2_NS.equals(tokenType);
 
         AttributeStatementBean attrBean = new AttributeStatementBean();
         while (claimIterator.hasNext()) {
@@ -55,8 +57,7 @@ public class ClaimsAttributeStatementProvider implements AttributeStatementProvi
             AttributeBean attributeBean = new AttributeBean();
 
             URI claimType = claim.getClaimType();
-            if (WSS4JConstants.WSS_SAML2_TOKEN_TYPE.equals(tokenType)
-                || WSS4JConstants.SAML2_NS.equals(tokenType)) {
+            if (saml2) {
                 attributeBean.setQualifiedName(claimType.toString());
                 attributeBean.setNameFormat(nameFormat);
             } else {
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/CombinedClaimsAttributeStatementProvider.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/CombinedClaimsAttributeStatementProvider.java
new file mode 100644
index 0000000..cea998a
--- /dev/null
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/claims/CombinedClaimsAttributeStatementProvider.java
@@ -0,0 +1,180 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.sts.claims;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.sts.token.provider.AttributeStatementProvider;
+import org.apache.cxf.sts.token.provider.TokenProviderParameters;
+import org.apache.wss4j.common.WSS4JConstants;
+import org.apache.wss4j.common.saml.bean.AttributeBean;
+import org.apache.wss4j.common.saml.bean.AttributeStatementBean;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+
+/**
+ * This class differs from the ClaimsAttributeStatementProvider in that it combines claims that have the same name.
+ */
+public class CombinedClaimsAttributeStatementProvider implements AttributeStatementProvider {
+
+    private String nameFormat = SAML2Constants.ATTRNAME_FORMAT_UNSPECIFIED;
+
+    public AttributeStatementBean getStatement(TokenProviderParameters providerParameters) {
+        // Handle Claims
+        ProcessedClaimCollection retrievedClaims = ClaimsUtils.processClaims(providerParameters);
+        if (retrievedClaims == null) {
+            return null;
+        }
+
+        Iterator<ProcessedClaim> claimIterator = retrievedClaims.iterator();
+        if (!claimIterator.hasNext()) {
+            return null;
+        }
+
+        Map<AttributeKey, AttributeBean> attributeMap = new LinkedHashMap<>();
+
+        String tokenType = providerParameters.getTokenRequirements().getTokenType();
+        boolean saml2 = WSS4JConstants.WSS_SAML2_TOKEN_TYPE.equals(tokenType)
+            || WSS4JConstants.SAML2_NS.equals(tokenType);
+
+        while (claimIterator.hasNext()) {
+            ProcessedClaim claim = claimIterator.next();
+            AttributeKey attributeKey = createAttributeKey(claim, saml2);
+
+            attributeMap.merge(
+                attributeKey,
+                createAttributeBean(attributeKey, claim.getValues()),
+                (v1, v2) -> {
+                    v1.getAttributeValues().addAll(claim.getValues());
+                    return v1;
+                });
+        }
+
+        AttributeStatementBean attrBean = new AttributeStatementBean();
+        attrBean.setSamlAttributes(new ArrayList<>(attributeMap.values()));
+
+        return attrBean;
+    }
+
+    private AttributeBean createAttributeBean(AttributeKey attributeKey, List<Object> claimValues) {
+        AttributeBean attributeBean =
+            new AttributeBean(attributeKey.getSimpleName(), attributeKey.getQualifiedName(), claimValues);
+        attributeBean.setNameFormat(attributeKey.getNameFormat());
+        return attributeBean;
+    }
+
+    private AttributeKey createAttributeKey(ProcessedClaim claim, boolean saml2) {
+
+        URI claimType = claim.getClaimType();
+        if (saml2) {
+            return new AttributeKey(claimType.toString(), nameFormat, null);
+        } else {
+            String uri = claimType.toString();
+            int lastSlash = uri.lastIndexOf("/");
+            if (lastSlash == (uri.length() - 1)) {
+                uri = uri.substring(0, lastSlash);
+                lastSlash = uri.lastIndexOf("/");
+            }
+
+            String namespace = uri.substring(0, lastSlash);
+            String name = uri.substring(lastSlash + 1, uri.length());
+
+            return new AttributeKey(namespace, null, name);
+        }
+    }
+
+    public String getNameFormat() {
+        return nameFormat;
+    }
+
+    public void setNameFormat(String nameFormat) {
+        this.nameFormat = nameFormat;
+    }
+
+    private static class AttributeKey {
+        private final String qualifiedName;
+        private final String simpleName;
+        private final String nameFormat;
+
+        // SAML 2.0 constructor
+        AttributeKey(String qualifiedName, String nameFormat, String simpleName) {
+            this.qualifiedName = qualifiedName;
+            this.nameFormat = nameFormat;
+            this.simpleName = simpleName;
+        }
+
+        public String getQualifiedName() {
+            return qualifiedName;
+        }
+
+        public String getSimpleName() {
+            return simpleName;
+        }
+
+        public String getNameFormat() {
+            return nameFormat;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof AttributeKey)) {
+                return false;
+            }
+
+            AttributeKey that = (AttributeKey) o;
+
+            if (qualifiedName == null && that.qualifiedName != null
+                || qualifiedName != null && !qualifiedName.equals(that.qualifiedName)) {
+                return false;
+            }
+
+            if (simpleName == null && that.simpleName != null
+                || simpleName != null && !simpleName.equals(that.simpleName)) {
+                return false;
+            }
+
+            return !(nameFormat == null && that.nameFormat != null
+                || nameFormat != null && !nameFormat.equals(that.nameFormat));
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            if (qualifiedName != null) {
+                result = 31 * result + qualifiedName.hashCode();
+            }
+            if (simpleName != null) {
+                result = 31 * result + simpleName.hashCode();
+            }
+            if (nameFormat != null) {
+                result = 31 * result + nameFormat.hashCode();
+            }
+
+            return result;
+        }
+    }
+}
+
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
index c5918b6..4181736 100644
--- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/provider/SAMLTokenProvider.java
@@ -36,6 +36,7 @@ import org.apache.cxf.sts.STSConstants;
 import org.apache.cxf.sts.STSPropertiesMBean;
 import org.apache.cxf.sts.cache.CacheUtils;
 import org.apache.cxf.sts.claims.ClaimsAttributeStatementProvider;
+import org.apache.cxf.sts.claims.CombinedClaimsAttributeStatementProvider;
 import org.apache.cxf.sts.request.KeyRequirements;
 import org.apache.cxf.sts.request.TokenRequirements;
 import org.apache.cxf.sts.token.realm.RealmProperties;
@@ -68,6 +69,7 @@ public class SAMLTokenProvider extends AbstractSAMLTokenProvider implements Toke
     private boolean signToken = true;
     private Map<String, RealmProperties> realmMap = new HashMap<>();
     private SamlCustomHandler samlCustomHandler;
+    private boolean combineClaimAttributes = true;
 
     /**
      * Return true if this TokenProvider implementation is capable of providing a token
@@ -388,7 +390,13 @@ public class SAMLTokenProvider extends AbstractSAMLTokenProvider implements Toke
         // Also handle "ActAs" via the ActAsAttributeStatementProvider
         if (!statementAdded) {
             attrBeanList = new ArrayList<>();
-            AttributeStatementProvider attributeProvider = new ClaimsAttributeStatementProvider();
+            AttributeStatementProvider attributeProvider = null;
+            if (combineClaimAttributes) {
+                attributeProvider = new CombinedClaimsAttributeStatementProvider();
+            } else {
+                attributeProvider = new ClaimsAttributeStatementProvider();
+            }
+
             AttributeStatementBean attributeBean = attributeProvider.getStatement(tokenParameters);
             if (attributeBean != null && attributeBean.getSamlAttributes() != null
                 && !attributeBean.getSamlAttributes().isEmpty()) {
@@ -459,5 +467,13 @@ public class SAMLTokenProvider extends AbstractSAMLTokenProvider implements Toke
 
     }
 
+    public boolean isCombineClaimAttributes() {
+        return combineClaimAttributes;
+    }
+
+    public void setCombineClaimAttributes(boolean combineClaimAttributes) {
+        this.combineClaimAttributes = combineClaimAttributes;
+    }
+
 
 }
diff --git a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/common/CustomClaimsHandler.java b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/common/CustomClaimsHandler.java
index 7b52b48..ed8182b 100644
--- a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/common/CustomClaimsHandler.java
+++ b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/common/CustomClaimsHandler.java
@@ -42,9 +42,9 @@ import org.opensaml.saml.saml2.core.AttributeValue;
  */
 public class CustomClaimsHandler implements ClaimsHandler {
 
-    private static List<URI> knownURIs = new ArrayList<>();
-    private static final URI ROLE_CLAIM =
+    public static final URI ROLE_CLAIM =
             URI.create("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role");
+    private static List<URI> knownURIs = new ArrayList<>();
 
     static {
         knownURIs.add(ClaimTypes.FIRSTNAME);
@@ -55,6 +55,8 @@ public class CustomClaimsHandler implements ClaimsHandler {
         knownURIs.add(ROLE_CLAIM);
     }
 
+    private String role = "DUMMY";
+
     public List<URI> getSupportedClaimTypes() {
         return knownURIs;
     }
@@ -108,7 +110,7 @@ public class CustomClaimsHandler implements ClaimsHandler {
                         }
                     } else {
                         // If no specific role was requested return DUMMY role for user
-                        claim.addValue("DUMMY");
+                        claim.addValue(role);
                     }
                 }
                 claimCollection.add(claim);
@@ -123,4 +125,7 @@ public class CustomClaimsHandler implements ClaimsHandler {
         return true;
     }
 
+    public void setRole(String role) {
+        this.role = role;
+    }
 }
diff --git a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/SAMLClaimsTest.java b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/SAMLClaimsTest.java
index f951052..ac67ee1 100644
--- a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/SAMLClaimsTest.java
+++ b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/provider/SAMLClaimsTest.java
@@ -20,6 +20,7 @@ package org.apache.cxf.sts.token.provider;
 
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -367,6 +368,92 @@ public class SAMLClaimsTest extends org.junit.Assert {
         assertTrue(tokenString.contains(ClaimTypes.MOBILEPHONE.toString()));
     }
 
+    /**
+     * Here we have two ClaimsHandlers that can both add claims for the same claim type. By default we will
+     * combine the Claims into one Attribute.
+     */
+    @org.junit.Test
+    public void testSaml2CombinedClaimsMultipleClaimsHandlers() throws Exception {
+        TokenProvider samlTokenProvider = new SAMLTokenProvider();
+        TokenProviderParameters providerParameters =
+            createProviderParameters(WSS4JConstants.WSS_SAML2_TOKEN_TYPE, STSConstants.BEARER_KEY_KEYTYPE, null);
+
+        ClaimsManager claimsManager = new ClaimsManager();
+        ClaimsHandler claimsHandler = new CustomClaimsHandler();
+        ClaimsHandler claimsHandler2 = new CustomClaimsHandler();
+        ((CustomClaimsHandler) claimsHandler2).setRole("CustomRole");
+        claimsManager.setClaimHandlers(new ArrayList<ClaimsHandler>(Arrays.asList(claimsHandler, claimsHandler2)));
+        providerParameters.setClaimsManager(claimsManager);
+
+        ClaimCollection claims = new ClaimCollection();
+
+        Claim claim = new Claim();
+        claim.setClaimType(CustomClaimsHandler.ROLE_CLAIM);
+        claims.add(claim);
+
+        providerParameters.setRequestedPrimaryClaims(claims);
+
+        assertTrue(samlTokenProvider.canHandleToken(WSS4JConstants.WSS_SAML2_TOKEN_TYPE));
+        TokenProviderResponse providerResponse = samlTokenProvider.createToken(providerParameters);
+        assertTrue(providerResponse != null);
+        assertTrue(providerResponse.getToken() != null && providerResponse.getTokenId() != null);
+
+        Element token = (Element)providerResponse.getToken();
+        String tokenString = DOM2Writer.nodeToString(token);
+        assertTrue(tokenString.contains(providerResponse.getTokenId()));
+        assertTrue(tokenString.contains("AttributeStatement"));
+
+        String requiredClaim = CustomClaimsHandler.ROLE_CLAIM.toString();
+        assertTrue(tokenString.contains(requiredClaim));
+        assertTrue(tokenString.contains("DUMMY"));
+        assertTrue(tokenString.contains("CustomRole"));
+        // Check only one Role Claim
+        assertEquals(tokenString.indexOf(requiredClaim), tokenString.lastIndexOf(requiredClaim));
+    }
+
+    /**
+     * Here we have two ClaimsHandlers that can both add claims for the same claim type. We configure the
+     * SAMLTokenProvider not to combine the claims unlike the test above.
+     */
+    @org.junit.Test
+    public void testSaml2SeparateClaimsMultipleClaimsHandlers() throws Exception {
+        TokenProvider samlTokenProvider = new SAMLTokenProvider();
+        ((SAMLTokenProvider) samlTokenProvider).setCombineClaimAttributes(false);
+        TokenProviderParameters providerParameters =
+            createProviderParameters(WSS4JConstants.WSS_SAML2_TOKEN_TYPE, STSConstants.BEARER_KEY_KEYTYPE, null);
+
+        ClaimsManager claimsManager = new ClaimsManager();
+        ClaimsHandler claimsHandler = new CustomClaimsHandler();
+        ClaimsHandler claimsHandler2 = new CustomClaimsHandler();
+        ((CustomClaimsHandler) claimsHandler2).setRole("CustomRole");
+        claimsManager.setClaimHandlers(new ArrayList<ClaimsHandler>(Arrays.asList(claimsHandler, claimsHandler2)));
+        providerParameters.setClaimsManager(claimsManager);
+
+        ClaimCollection claims = new ClaimCollection();
+
+        Claim claim = new Claim();
+        claim.setClaimType(CustomClaimsHandler.ROLE_CLAIM);
+        claims.add(claim);
+
+        providerParameters.setRequestedPrimaryClaims(claims);
+
+        assertTrue(samlTokenProvider.canHandleToken(WSS4JConstants.WSS_SAML2_TOKEN_TYPE));
+        TokenProviderResponse providerResponse = samlTokenProvider.createToken(providerParameters);
+        assertTrue(providerResponse != null);
+        assertTrue(providerResponse.getToken() != null && providerResponse.getTokenId() != null);
+
+        Element token = (Element)providerResponse.getToken();
+        String tokenString = DOM2Writer.nodeToString(token);
+        assertTrue(tokenString.contains(providerResponse.getTokenId()));
+        assertTrue(tokenString.contains("AttributeStatement"));
+
+        String requiredClaim = CustomClaimsHandler.ROLE_CLAIM.toString();
+        assertTrue(tokenString.contains(requiredClaim));
+        assertTrue(tokenString.contains("DUMMY"));
+        assertTrue(tokenString.contains("CustomRole"));
+        // Check we have two Role Claims
+        assertNotEquals(tokenString.indexOf(requiredClaim), tokenString.lastIndexOf(requiredClaim));
+    }
 
     private TokenProviderParameters createProviderParameters(
         String tokenType, String keyType, String appliesTo
@@ -406,8 +493,6 @@ public class SAMLClaimsTest extends org.junit.Assert {
 
         parameters.setEncryptionProperties(new EncryptionProperties());
 
-
-
         return parameters;
     }
 

-- 
To stop receiving notification emails like this one, please contact
coheigea@apache.org.