Kaito Kid
Kaito Kid

Reputation: 1093

XmlWriter won't generate the namespaces I need correctly

I am trying to generate XML data that matches this format:

<samlp:AuthnRequest
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
        xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
        IssueInstant="2018-07-04T19:19:53.284Z"
        ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
        Version="2.0">
    <samlp:NameIDPolicy
            AllowCreate="true"
            Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"/>
</samlp:AuthnRequest>

It's obviously missing a lot of tags for anonymity reasons.

I am using XmlWriter.

I managed to generate this data with different sets of WriteStartElement() and WriteAttributeString(), but I can never get it right. Either the result is slightly different, or it just crashes halfway with XmlException like

System.Xml.XmlException: 'The prefix '' cannot be redefined from 'samlp' to
'urn:oasis:names:tc:SAML:2.0:assertion' within the same start element tag.'

Here are a couple of my attempts and results.

Attempt #1

using (XmlWriter xw = XmlWriter.Create(sw, xws))
{
    xw.WriteStartElement("AuthnRequest", "samlp");
    xw.WriteAttributeString("xmlns", "samlp", null, "urn:oasis:names:tc:SAML:2.0:protocol");
    xw.WriteAttributeString("xmlns", "urn:oasis:names:tc:SAML:2.0:assertion");
    // ...
}

Result #1

Crashed with the above message.

Attempt #2

using (XmlWriter xw = XmlWriter.Create(sw, xws))
{
    xw.WriteStartElement("AuthnRequest", "samlp");
    xw.WriteAttributeString("xmlns", "samlp", null, "urn:oasis:names:tc:SAML:2.0:protocol");
    // ...
}

Result #2

<AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ForceAuthn="false" ID="ID_4f85b6d1-a839-4899-972c-12275bf8711c"
    IssueInstant="2018-08-15T18:23:49Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0"
    xmlns="samlp">
    ...

The rest of the attempts are all slight variations of those two, moving around parameters but none are as close as them to my desired result.

Specifically, I have a lot of trouble with generating this attribute:

xmlns="urn:oasis:names:tc:SAML:2.0:assertion"

Either it doesn't show up or is wrong.

I can't figure out what I'm missing in order to generate it the way I need.

Upvotes: 4

Views: 2374

Answers (2)

dbc
dbc

Reputation: 117283

The following code works and writes your required XML successfully:

var issueInstant = DateTime.Parse("2018-07-04T19:19:53.284Z", CultureInfo.InvariantCulture);

using (var xw = XmlWriter.Create(sw, xws))
{
    var samplNs = "urn:oasis:names:tc:SAML:2.0:protocol";
    var defaultNs = "urn:oasis:names:tc:SAML:2.0:assertion";

    // XmlWriter.WriteStartElement(String prefix, String localName, String ns)
    xw.WriteStartElement("samlp", "AuthnRequest", samplNs);

    // Write the xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" namespace.
    // Actually this is redundant as the attribute will be automatically be written at the end of the
    // attribute list do to the previous call to xw.WriteStartElement().  Call it here only if, for some
    // reason, you need to control the attribute order.
    xw.WriteAttributeString("xmlns", "samlp", null, samplNs);               

    // Write the default namespace
    xw.WriteAttributeString("xmlns", defaultNs);

    // Write attribute values.              
    xw.WriteAttributeString("IssueInstant", XmlConvert.ToString(issueInstant, XmlDateTimeSerializationMode.Utc));
    xw.WriteAttributeString("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
    xw.WriteAttributeString("Version", "2.0");

    // Write samlp:NameIDPolicy
    // No need to specify prefix since it is specified in the document root.
    xw.WriteStartElement("NameIDPolicy", samplNs);
    xw.WriteAttributeString("AllowCreate", XmlConvert.ToString(true));
    xw.WriteAttributeString("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

    // Write the end of NameIDPolicy
    xw.WriteEndElement();

    // Write the end of AuthnRequest
    xw.WriteEndElement();
}       

Notes:

  • The method XmlWriter.WriteStartElement(String localName, String ns) takes the namespace as the second argument, but you were passing the namespace prefix "samlp". So that didn't work.

  • But calling

    xw.WriteStartElement("AuthnRequest", "urn:oasis:names:tc:SAML:2.0:protocol"); 
    

    would also fail because doing so establishes "urn:oasis:names:tc:SAML:2.0:protocol" as the default namespace, whereas you want it to be a non-default namespace with the prefix sampl. Thus XmlWriter.WriteStartElement(String prefix, String localName, String ns) must be used.

  • Having explicitly written the AuthnRequest element with a specified namespace and namespace prefix, it isn't actually necessary to write the namespace attribute any more -- XmlWriter will do it for you automatically at the end of the attribute list. You only need to write the namespace attribute manually if you want to control its placement in the attribute list. However, according to the XML Standard:

    Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.

    So, you could just skip that.

  • Methods from the XmlConvert class can be used to correctly convert non-string primitives from and to XML.

Sample fiddle here: https://dotnetfiddle.net/99hu1I.

Upvotes: 3

jdweng
jdweng

Reputation: 34433

I find it is much easier to just parse a string when you have complicated namespaces. Using xml linq :

            string xml =
                "<samlp:AuthnRequest" +
                    " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"" +
                    " xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"" +
                    " IssueInstant=\"2018-07-04T19:19:53.284Z\"" +
                    " ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"" +
                    " Version=\"2.0\">" +
                    "<samlp:NameIDPolicy" +
                       " AllowCreate=\"true\"" +
                       " Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\"/>" +
                "</samlp:AuthnRequest>";

            XDocument doc = XDocument.Parse(xml);

Upvotes: 0

Related Questions