From f498e01b47304a6b181fb5a53259cc059796c02d Mon Sep 17 00:00:00 2001 From: Karel Maxa Date: Fri, 27 Sep 2024 12:27:42 +0200 Subject: [PATCH] Fix serialization of SAML extensions --- .../identity/saml2/meta/SAML2MetaUtils.java | 97 +++++---- .../identity/saml2/protocol/Extensions.java | 36 ++-- .../saml2/protocol/impl/ExtensionsImpl.java | 199 +++++++++--------- .../saml2/meta/SAML2MetaUtilsTest.java | 41 +++- 4 files changed, 206 insertions(+), 167 deletions(-) diff --git a/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/meta/SAML2MetaUtils.java b/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/meta/SAML2MetaUtils.java index be2a0cfc33..c1e474f79a 100644 --- a/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/meta/SAML2MetaUtils.java +++ b/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/meta/SAML2MetaUtils.java @@ -25,6 +25,7 @@ * $Id: SAML2MetaUtils.java,v 1.9 2009/09/21 17:28:12 exu Exp $ * * Portions Copyrighted 2010-2015 ForgeRock AS. + * Portions Copyrighted 2024 Wren Security. */ package com.sun.identity.saml2.meta; @@ -81,14 +82,12 @@ public final class SAML2MetaUtils { "com.sun.identity.saml2.jaxb.xmlsig:" + "com.sun.identity.saml2.jaxb.assertion:" + "com.sun.identity.saml2.jaxb.metadata:" + - "com.sun.identity.saml2.jaxb.metadataattr:" + + "com.sun.identity.saml2.jaxb.metadataattr:" + "com.sun.identity.saml2.jaxb.entityconfig:" + "com.sun.identity.saml2.jaxb.schema"; private static final String JAXB_PACKAGE_LIST_PROP = "com.sun.identity.liberty.ws.jaxb.packageList"; private static JAXBContext jaxbContext = null; - private static final String PROP_JAXB_FORMATTED_OUTPUT = - "jaxb.formatted.output"; private static final String PROP_NAMESPACE_PREFIX_MAPPER = "com.sun.xml.bind.namespacePrefixMapper"; @@ -171,18 +170,31 @@ public static Object convertNodeToJAXB(Node node) return u.unmarshal(node); } + /** + * See {@link #convertJAXBToString(Object, boolean)}. + */ + public static String convertJAXBToString(Object jaxbObj) throws JAXBException { + return convertJAXBToString(jaxbObj, true, false); + } + /** * Converts a JAXB object to a String object. * @param jaxbObj a JAXB object + * @param format flag indicating whether the output XML should be formatted. + * @param fragment flag indicating whether the specified JAXB object is the fragment. * @return a String representing the JAXB object. * @exception JAXBException if an error occurs while converting JAXB object */ - public static String convertJAXBToString(Object jaxbObj) - throws JAXBException { - + public static String convertJAXBToString(Object jaxbObj, boolean format, boolean fragment) throws JAXBException { StringWriter sw = new StringWriter(); Marshaller marshaller = jaxbContext.createMarshaller(); - marshaller.setProperty(PROP_JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + if (format) { + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + } + if (fragment) { + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE); + } marshaller.setProperty(PROP_NAMESPACE_PREFIX_MAPPER, nsPrefixMapper); marshaller.marshal(jaxbObj, sw); return sw.toString(); @@ -194,12 +206,9 @@ public static String convertJAXBToString(Object jaxbObj) * @param os an OutputStream object * @exception JAXBException if an error occurs while converting JAXB object */ - public static void convertJAXBToOutputStream(Object jaxbObj, - OutputStream os) - throws JAXBException { - + public static void convertJAXBToOutputStream(Object jaxbObj, OutputStream os) throws JAXBException { Marshaller marshaller = jaxbContext.createMarshaller(); - marshaller.setProperty(PROP_JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.setProperty(PROP_NAMESPACE_PREFIX_MAPPER, nsPrefixMapper); marshaller.marshal(jaxbObj, os); } @@ -574,35 +583,35 @@ public static IDPSSOConfigElement getIDPSSOConfig( } public static String exportStandardMeta(String realm, String entityID, - boolean sign) - throws SAML2MetaException { - - try { - SAML2MetaManager metaManager = new SAML2MetaManager(); - EntityDescriptorElement descriptor = - metaManager.getEntityDescriptor(realm, entityID); - - String xmlstr = null; - if (descriptor == null) { - return null; - } - - if (sign) { - Document doc = SAML2MetaSecurityUtils.sign(realm, descriptor); - if (doc != null) { + boolean sign) + throws SAML2MetaException { + + try { + SAML2MetaManager metaManager = new SAML2MetaManager(); + EntityDescriptorElement descriptor = + metaManager.getEntityDescriptor(realm, entityID); + + String xmlstr = null; + if (descriptor == null) { + return null; + } + + if (sign) { + Document doc = SAML2MetaSecurityUtils.sign(realm, descriptor); + if (doc != null) { xmlstr = XMLUtils.print(doc); - } + } } if (xmlstr == null) { - xmlstr = convertJAXBToString(descriptor); - xmlstr = SAML2MetaSecurityUtils.formatBase64BinaryElement( + xmlstr = convertJAXBToString(descriptor); + xmlstr = SAML2MetaSecurityUtils.formatBase64BinaryElement( xmlstr); } xmlstr = workaroundAbstractRoleDescriptor(xmlstr); return xmlstr; - } catch (JAXBException e) { + } catch (JAXBException e) { throw new SAML2MetaException(e.getMessage()); - } + } } /** @@ -734,7 +743,7 @@ private static String importSAML2Entity(SAML2MetaManager metaManager, String rea return result; } - + private static Object workaroundJAXBBug(Object obj) throws JAXBException { String metadata = convertJAXBToString(obj); @@ -787,28 +796,28 @@ private static void workaroundAbstractRoleDescriptor(Document doc) { } private static String workaroundAbstractRoleDescriptor(String xmlstr) { - int index = - xmlstr.indexOf(":" +SAML2MetaConstants.ATTRIBUTE_QUERY_DESCRIPTOR); - if (index == -1) { + int index = + xmlstr.indexOf(":" +SAML2MetaConstants.ATTRIBUTE_QUERY_DESCRIPTOR); + if (index == -1) { return xmlstr; - } + } int index2 = xmlstr.lastIndexOf("<", index); - if (index2 == -1) { + if (index2 == -1) { return xmlstr; - } + } String prefix = xmlstr.substring(index2 + 1, index); - String type = prefix + ":" + + String type = prefix + ":" + SAML2MetaConstants.ATTRIBUTE_QUERY_DESCRIPTOR_TYPE; - xmlstr = xmlstr.replaceAll("<" + prefix + ":" + + xmlstr = xmlstr.replaceAll("<" + prefix + ":" + SAML2MetaConstants.ATTRIBUTE_QUERY_DESCRIPTOR, "<" + SAML2MetaConstants.ROLE_DESCRIPTOR + " " + SAML2Constants.XSI_DECLARE_STR + " xsi:type=\"" + type + "\""); - xmlstr = xmlstr.replaceAll("Extensions defines methods for * adding protcol message extension elements. * @@ -45,33 +45,33 @@ @JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS, defaultImpl = ExtensionsImpl.class) public interface Extensions { - - /** + + /** * Sets the Extensions object. * * @param value List of Document Elements Extensions objects * @throws SAML2Exception if the object is immutable. * @see #getAny */ - public void setAny(List value) throws SAML2Exception; - - /** + public void setAny(List value) throws SAML2Exception; + + /** * Returns the list of Extensions object. * * @return a List of Document Elements Extensions objects. * @see #setAny(List) */ - public List getAny() ; - - /** + public List getAny() ; + + /** * Returns a String representation of this object. * * @return a String representation of this object. * @throws SAML2Exception if cannot convert to String. */ public String toXMLString() throws SAML2Exception; - - /** + + /** * Returns a String representation of this object. * * @param includeNSPrefix determines whether or not the namespace @@ -81,17 +81,17 @@ public interface Extensions { * @return the String representation of this Object. * @throws SAML2Exception if cannot convert to String. **/ - + public String toXMLString(boolean includeNSPrefix, boolean declareNS) throws SAML2Exception; - - /** - * Makes this object immutable. + + /** + * Makes this object immutable. * */ public void makeImmutable() ; - - /** + + /** * Returns value true if object is mutable. * * @return true if object is mutable. diff --git a/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/protocol/impl/ExtensionsImpl.java b/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/protocol/impl/ExtensionsImpl.java index 7b40e07fef..65737b24ba 100644 --- a/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/protocol/impl/ExtensionsImpl.java +++ b/openam-federation/openam-federation-library/src/main/java/com/sun/identity/saml2/protocol/impl/ExtensionsImpl.java @@ -22,42 +22,37 @@ * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * - * $Id: ExtensionsImpl.java,v 1.2 2008/06/25 05:47:59 qcheng Exp $ - * + * Portions Copyrighted 2024 Wren Security. */ - package com.sun.identity.saml2.protocol.impl; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; +import javax.xml.bind.JAXBException; import com.sun.identity.shared.xml.XMLUtils; +import com.sun.xml.bind.JAXBObject; import com.sun.identity.saml2.common.SAML2Constants; import com.sun.identity.saml2.common.SAML2Exception; import com.sun.identity.saml2.common.SAML2SDKUtils; +import com.sun.identity.saml2.meta.SAML2MetaUtils; import com.sun.identity.saml2.protocol.Extensions; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -/** - * The class defines methods for adding protcol message extension elements. +/** + * The class defines methods for adding protocol message extension elements. */ - public class ExtensionsImpl implements Extensions { - - private boolean isMutable=false; - private List extensionsList=null; - /** - * Constructor to create the Extensions Object. - * - */ + private boolean isMutable = false; + private List extensionsList = null; + public ExtensionsImpl() { - isMutable=true; + isMutable = true; } /** @@ -66,9 +61,8 @@ public ExtensionsImpl() { * @param element the Document Element of Extensions object. * @throws SAML2Exception if Extensions cannot be created. */ - public ExtensionsImpl(Element element) throws SAML2Exception { - parseElement(element); + parseElement(element); } /** @@ -78,52 +72,49 @@ public ExtensionsImpl(Element element) throws SAML2Exception { * @throws SAML2Exception if Extensions cannot be created. */ public ExtensionsImpl(String xmlString) throws SAML2Exception { - Document xmlDocument = - XMLUtils.toDOMDocument(xmlString,SAML2SDKUtils.debug); - if (xmlDocument == null) { - throw new SAML2Exception( - SAML2SDKUtils.bundle.getString("errorObtainingElement")); - } + Document xmlDocument = XMLUtils.toDOMDocument(xmlString,SAML2SDKUtils.debug); + if (xmlDocument == null) { + throw new SAML2Exception(SAML2SDKUtils.bundle.getString("errorObtainingElement")); + } parseElement(xmlDocument.getDocumentElement()); } - /** + /** * Sets the Extensions object. * * @param value List of XML Strings Extensions objects * @throws SAML2Exception if the object is immutable. * @see #getAny() */ - public void setAny(List value) throws SAML2Exception { - if (isMutable) { - extensionsList = value; - } else { - throw new SAML2Exception( - SAML2SDKUtils.bundle.getString("objectImmutable")); - } + public void setAny(List value) throws SAML2Exception { + if (isMutable) { + extensionsList = value; + } else { + throw new SAML2Exception(SAML2SDKUtils.bundle.getString("objectImmutable")); + } } - - /** + + /** * Returns the list of Extensions object. * * @return a List of XML Strings Extensions objects. * @see #setAny(List) */ - public List getAny() { - return extensionsList; + public List getAny() { + return extensionsList; } - - /** + + /** * Returns a String representation of this object. * * @return a String representation of this object. * @throws SAML2Exception if cannot convert to String. */ public String toXMLString() throws SAML2Exception { - return toXMLString(true,false); + return toXMLString(true,false); } - - /** + + /** * Returns a String representation of this object. * * @param includeNSPrefix determines whether or not the namespace @@ -133,72 +124,90 @@ public String toXMLString() throws SAML2Exception { * @return the String representation of this Object. * @throws SAML2Exception if cannot convert to String. */ - - public String toXMLString(boolean includeNSPrefix, - boolean declareNS) throws SAML2Exception { - String xmlStr = null; - if ((extensionsList != null) && (!extensionsList.isEmpty())) { - StringBuffer xmlString = new StringBuffer(500); - xmlString.append(SAML2Constants.START_TAG); - if (includeNSPrefix) { - xmlString.append(SAML2Constants.PROTOCOL_PREFIX); - } - xmlString.append(SAML2Constants.EXTENSIONS); - if (declareNS) { - xmlString.append(SAML2Constants.PROTOCOL_DECLARE_STR); - } - xmlString.append(SAML2Constants.END_TAG); - - Iterator extIterator = extensionsList.iterator(); - while (extIterator.hasNext()) { - String extString = (String) extIterator.next(); - xmlString.append(SAML2Constants.NEWLINE) - .append(extString); - } - xmlString.append(SAML2Constants.NEWLINE) - .append(SAML2Constants.SAML2_END_TAG) - .append(SAML2Constants.EXTENSIONS) - .append(SAML2Constants.END_TAG); - - xmlStr = xmlString.toString(); - } - return xmlStr; + public String toXMLString(boolean includeNSPrefix, boolean declareNS) throws SAML2Exception { + if (extensionsList == null || extensionsList.isEmpty()) { + return null; + } + StringBuffer xmlString = new StringBuffer(500); + xmlString.append(SAML2Constants.START_TAG); + if (includeNSPrefix) { + xmlString.append(SAML2Constants.PROTOCOL_PREFIX); + } + xmlString.append(SAML2Constants.EXTENSIONS); + if (declareNS) { + xmlString.append(SAML2Constants.PROTOCOL_DECLARE_STR); + } + xmlString.append(SAML2Constants.END_TAG); + for (Object extension : extensionsList) { + String content = serializeExtension(extension); + if (content != null) { + xmlString.append(SAML2Constants.NEWLINE).append(content); + } + } + xmlString.append(SAML2Constants.NEWLINE) + .append(SAML2Constants.SAML2_END_TAG) + .append(SAML2Constants.EXTENSIONS) + .append(SAML2Constants.END_TAG); + return xmlString.toString(); } - - /** - * Makes this object immutable. + + /** + * Makes this object immutable. */ public void makeImmutable() { - if (isMutable) { - isMutable = false; - } + if (isMutable) { + isMutable = false; + } } - - /** + + /** * Returns value true if object is mutable. * * @return true if object is mutable. */ public boolean isMutable() { - return isMutable; + return isMutable; } - /* Parses the Extensions Element. */ + /** + * Parse the specified Extension DOM element. + */ private void parseElement(Element element) { - NodeList nList = element.getChildNodes(); - if ((extensionsList == null) || (extensionsList.isEmpty())) { - extensionsList = new ArrayList(); - } - if ((nList != null) && (nList.getLength() > 0)) { - for (int i = 0; i < nList.getLength(); i++) { - Node childNode = nList.item(i); - if (childNode.getLocalName() != null) { - extensionsList.add(XMLUtils.print(childNode)); - } - } - if ((extensionsList != null) && (!extensionsList.isEmpty())) { - extensionsList = Collections.unmodifiableList(extensionsList); - } - } + NodeList nList = element.getChildNodes(); + if (extensionsList == null || extensionsList.isEmpty()) { + extensionsList = new ArrayList<>(); + } + if (nList != null && nList.getLength() > 0) { + for (int i = 0; i < nList.getLength(); i++) { + Node childNode = nList.item(i); + if (childNode.getLocalName() != null) { + extensionsList.add(XMLUtils.print(childNode)); + } + } + if (extensionsList != null && !extensionsList.isEmpty()) { + extensionsList = Collections.unmodifiableList(extensionsList); + } + } } + + /** + * Serialize the specified extension object into string value. + */ + private String serializeExtension(Object extension) { + if (extension == null) { + return null; + } + if (extension instanceof String) { + return (String) extension; + } + if (extension instanceof JAXBObject) { + try { + return SAML2MetaUtils.convertJAXBToString(extension, false, true); + } catch (JAXBException e) { + throw new IllegalStateException("Failed to serialize JAXB object.", e); + } + } + return null; + } + } diff --git a/openam-federation/openam-federation-library/src/test/java/com/sun/identity/saml2/meta/SAML2MetaUtilsTest.java b/openam-federation/openam-federation-library/src/test/java/com/sun/identity/saml2/meta/SAML2MetaUtilsTest.java index da389c3515..c4ab9fcc7e 100644 --- a/openam-federation/openam-federation-library/src/test/java/com/sun/identity/saml2/meta/SAML2MetaUtilsTest.java +++ b/openam-federation/openam-federation-library/src/test/java/com/sun/identity/saml2/meta/SAML2MetaUtilsTest.java @@ -12,11 +12,18 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2014 ForgeRock AS. + * Portions Copyrighted 2024 Wren Security. */ package com.sun.identity.saml2.meta; import static org.testng.Assert.*; + +import com.sun.identity.saml2.jaxb.metadata.EntityDescriptorElement; +import com.sun.identity.saml2.jaxb.metadata.NameIDFormatElement; +import com.sun.identity.saml2.jaxb.metadata.impl.EntityDescriptorElementImpl; +import com.sun.identity.saml2.jaxb.metadata.impl.NameIDFormatElementImpl; +import javax.xml.bind.JAXBException; import org.testng.annotations.Test; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -24,12 +31,12 @@ import org.testng.annotations.BeforeMethod; public class SAML2MetaUtilsTest { - + private static final String PATH_SEPARATOR = "/"; private static final String TEST_ENTITY = "someEntity"; private static final String PREFIX = PATH_SEPARATOR + "abcdefg"; private static final String TEST_SUB_REALM = "subsub"; - + public SAML2MetaUtilsTest() { } @@ -51,21 +58,35 @@ public void tearDownMethod() throws Exception { @Test public void testGetMetaDataByURI_DefaultRealm() { - final String uri = PREFIX + final String uri = PREFIX + PATH_SEPARATOR + SAML2MetaManager.NAME_META_ALIAS_IN_URI + PATH_SEPARATOR + TEST_ENTITY; final String result = SAML2MetaUtils.getMetaAliasByUri(uri); assertEquals(result, PATH_SEPARATOR + TEST_ENTITY); } - + @Test public void testGetMetaDataByURI_SubRealm() { - final String uri = PREFIX - + PATH_SEPARATOR + SAML2MetaManager.NAME_META_ALIAS_IN_URI - + PATH_SEPARATOR + TEST_SUB_REALM + final String uri = PREFIX + + PATH_SEPARATOR + SAML2MetaManager.NAME_META_ALIAS_IN_URI + + PATH_SEPARATOR + TEST_SUB_REALM + PATH_SEPARATOR + TEST_ENTITY; final String result = SAML2MetaUtils.getMetaAliasByUri(uri); - assertEquals(result, PATH_SEPARATOR + TEST_SUB_REALM + PATH_SEPARATOR + TEST_ENTITY); - } - + assertEquals(result, PATH_SEPARATOR + TEST_SUB_REALM + PATH_SEPARATOR + TEST_ENTITY); + } + + @Test + public void testConvertJAXBToString() throws JAXBException { + // Document + EntityDescriptorElement descriptor = new EntityDescriptorElementImpl(); + descriptor.setEntityID("foobar"); + String expected = "\n" + + "\n"; + assertEquals(SAML2MetaUtils.convertJAXBToString(descriptor), expected); + // Fragment + NameIDFormatElement nameId = new NameIDFormatElementImpl("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"); + expected = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"; + assertEquals(SAML2MetaUtils.convertJAXBToString(nameId, false, true), expected); + } + }