Reputation: 1093
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
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
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