diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeParser.java index 2b8fa82633..4f3f3bfdf5 100644 --- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeParser.java +++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/assertion/SAMLAttributeParser.java @@ -19,8 +19,17 @@ package org.keycloak.saml.processing.core.parsers.saml.assertion; import org.keycloak.dom.saml.v2.assertion.AttributeType; import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.util.StaxParserUtil; + +import javax.xml.namespace.QName; import javax.xml.stream.XMLEventReader; +import javax.xml.stream.events.Attribute; import javax.xml.stream.events.StartElement; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; /** * Parse the in the saml assertion @@ -31,6 +40,12 @@ public class SAMLAttributeParser extends AbstractStaxSamlAssertionParser DEFAULT_KNOWN_ATTRIBUTE_NAMES = new HashSet<>(Arrays.asList( + SAMLAssertionQNames.ATTR_NAME.getQName(), + SAMLAssertionQNames.ATTR_FRIENDLY_NAME.getQName(), + SAMLAssertionQNames.ATTR_NAME_FORMAT.getQName() + )); + private SAMLAttributeParser() { super(SAMLAssertionQNames.ATTRIBUTE); } @@ -47,14 +62,39 @@ public class SAMLAttributeParser extends AbstractStaxSamlAssertionParser collectUnknownAttributesFrom(StartElement element) { + + Map otherAttributes = new HashMap<>(); + + Iterator attributes = element.getAttributes(); + while (attributes.hasNext()) { + Attribute currentAttribute = (Attribute) attributes.next(); + QName attributeQName = currentAttribute.getName(); + if (attributeQName == null || DEFAULT_KNOWN_ATTRIBUTE_NAMES.contains(attributeQName)) { + continue; + } + String attributeValue = currentAttribute.getValue(); + otherAttributes.put(attributeQName, attributeValue); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(String.format("Adding attribute %s with value %s", attributeQName, attributeValue)); + } + } + + return otherAttributes; + } + @Override protected void processSubElement(XMLEventReader xmlEventReader, AttributeType target, SAMLAssertionQNames element, StartElement elementDetail) throws ParsingException { switch (element) { diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeParserTest.java new file mode 100644 index 0000000000..3ecc0e20ad --- /dev/null +++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeParserTest.java @@ -0,0 +1,89 @@ +package org.keycloak.saml.processing.core.parsers.saml; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.dom.saml.v2.assertion.AttributeType; +import org.keycloak.saml.common.parsers.AbstractParser; +import org.keycloak.saml.processing.core.parsers.saml.assertion.SAMLAttributeParser; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class SAMLAttributeParserTest { + + private static String XML_DOC_TEMPLATE = "\n" + + "${ATTRIBUTE_ELEMENT}\n" + + ""; + + @Test + public void parsesAttributeElementWithKnownAttributesCorrectly() throws Exception { + + String nameFormatValue = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"; + String nameValue = "urn:oid:2.5.4.42"; + String friendlyNameValue = "givenName"; + + AttributeType attributeType = parseAttributeElement(""); + + Assert.assertEquals(nameFormatValue, attributeType.getNameFormat()); + Assert.assertEquals(nameValue, attributeType.getName()); + Assert.assertEquals(friendlyNameValue, attributeType.getFriendlyName()); + Assert.assertTrue("Other attributes should be empty", attributeType.getOtherAttributes().isEmpty()); + } + + @Test + public void parsesAttributeElementWithKnownAndX509_ENCODINGAttributesCorrectly() throws Exception { + + String nameFormatValue = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"; + String nameValue = "urn:oid:2.5.4.42"; + String friendlyNameValue = "givenName"; + String encodingValue = "LDAP"; + + String x500Namespace = "urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500"; + AttributeType attributeType = parseAttributeElement(String.format("", x500Namespace, // + nameFormatValue, nameValue, friendlyNameValue, encodingValue)); + + Assert.assertEquals(nameFormatValue, attributeType.getNameFormat()); + Assert.assertEquals(nameValue, attributeType.getName()); + Assert.assertEquals(friendlyNameValue, attributeType.getFriendlyName()); + Assert.assertTrue("Other attributes should not be empty", !attributeType.getOtherAttributes().isEmpty()); + Assert.assertEquals(encodingValue, attributeType.getOtherAttributes().get(new QName(x500Namespace, "Encoding"))); + } + + @Test + public void parsesAttributeElementWithKnownAndOtherAttributesCorrectly() throws Exception { + + String nameFormatValue = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"; + String nameValue = "urn:oid:2.5.4.42"; + String friendlyNameValue = "givenName"; + + String someNs = "https://www.thenamespace.ns/path"; + String someValue1 = "v1"; + String someValue2 = "v2"; + + AttributeType attributeType = parseAttributeElement(String.format("", someNs, // + nameFormatValue, nameValue, friendlyNameValue, someValue1, someValue2)); + + Assert.assertEquals(nameFormatValue, attributeType.getNameFormat()); + Assert.assertEquals(nameValue, attributeType.getName()); + Assert.assertEquals(friendlyNameValue, attributeType.getFriendlyName()); + Assert.assertTrue("Other attributes should not be empty", !attributeType.getOtherAttributes().isEmpty()); + Assert.assertEquals(someValue1, attributeType.getOtherAttributes().get(new QName(someNs, "Value1"))); + Assert.assertEquals(someValue2, attributeType.getOtherAttributes().get(new QName(someNs, "Value2"))); + } + + protected AttributeType parseAttributeElement(String attributeXml) throws Exception { + + String xmlDoc = XML_DOC_TEMPLATE.replace("${ATTRIBUTE_ELEMENT}", attributeXml); + InputStream input = new ByteArrayInputStream(xmlDoc.getBytes(StandardCharsets.UTF_8)); + XMLEventReader xmlEventReader = AbstractParser.createEventReader(input); + xmlEventReader.nextEvent(); + return SAMLAttributeParser.getInstance().parse(xmlEventReader); + } +}