Skip to content

Commit

Permalink
Merge pull request #1126 from Sustainsys/missing_nodes_exceptions
Browse files Browse the repository at this point in the history
Meaningful exceptions for missing SamlResponse elements/attributes
  • Loading branch information
AndersAbel committed Oct 31, 2019
2 parents 1c867b7 + d8ca713 commit 2967605
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 7 deletions.
15 changes: 8 additions & 7 deletions Sustainsys.Saml2/SAML2P/Saml2Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,24 @@ public Saml2Response(XmlElement xml, Saml2Id expectedInResponseTo, IOptions opti

xmlElement = xml;

id = new Saml2Id(xml.Attributes["ID"].Value);
id = new Saml2Id(xml.GetRequiredAttributeValue("ID"));

ReadAndValidateInResponseTo(xml, expectedInResponseTo, options);

issueInstant = DateTime.Parse(xml.Attributes["IssueInstant"].Value,
issueInstant = DateTime.Parse(xml.GetRequiredAttributeValue("IssueInstant"),
CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);

var statusString = xml["Status", Saml2Namespaces.Saml2PName]
["StatusCode", Saml2Namespaces.Saml2PName].Attributes["Value"].Value;
var statusElement = xml.GetRequiredElement("Status", Saml2Namespaces.Saml2PName);
var statusCodeElement = statusElement.GetRequiredElement("StatusCode", Saml2Namespaces.Saml2PName);
var statusString = statusCodeElement.GetRequiredAttributeValue("Value");

status = StatusCodeHelper.FromString(statusString);

statusMessage = xml["Status", Saml2Namespaces.Saml2PName]
statusMessage = statusElement
["StatusMessage", Saml2Namespaces.Saml2PName].GetTrimmedTextIfNotNull();
if (xml["Status", Saml2Namespaces.Saml2PName]["StatusCode", Saml2Namespaces.Saml2PName]["StatusCode", Saml2Namespaces.Saml2PName] != null)
if (statusCodeElement["StatusCode", Saml2Namespaces.Saml2PName] != null)
{
secondLevelStatus = xml["Status", Saml2Namespaces.Saml2PName]["StatusCode", Saml2Namespaces.Saml2PName]["StatusCode", Saml2Namespaces.Saml2PName].Attributes["Value"].Value;
secondLevelStatus = statusCodeElement["StatusCode", Saml2Namespaces.Saml2PName].Attributes["Value"].Value;
}

Issuer = new EntityId(xmlElement["Issuer", Saml2Namespaces.Saml2Name].GetTrimmedTextIfNotNull());
Expand Down
22 changes: 22 additions & 0 deletions Sustainsys.Saml2/XmlHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,28 @@ internal static string GetTrimmedTextIfNotNull(this XmlElement xmlElement)
return xmlElement.InnerText.Trim();
}

internal static string GetRequiredAttributeValue(this XmlElement node, string attributeName)
{
var foundAttribute = node.Attributes[attributeName];
if (string.IsNullOrWhiteSpace(foundAttribute?.Value))
{
throw new BadFormatSamlResponseException($"Attribute '{attributeName}' (case-sensitive) was not found or its value is empty");
}

return foundAttribute.Value;
}

internal static XmlElement GetRequiredElement(this XmlElement node, string name, string namespaceValue)
{
var foundElement = node[name, namespaceValue];
if (foundElement == null)
{
throw new BadFormatSamlResponseException($"Element '{name}' (case-sensitive, namespace '{namespaceValue}') was not found");
}

return foundElement;
}

internal static XmlElement StartElement(this XmlNode parent, string name, Uri namespaceUri)
{
var xmlElement = parent.GetOwnerDoc().CreateElement(name, namespaceUri.OriginalString);
Expand Down
89 changes: 89 additions & 0 deletions Tests/Tests.Shared/Saml2P/Saml2ResponseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,95 @@ public void Saml2Response_Read_ThrowsOnWrongVersion()

}

[TestMethod]
public void Saml2Response_Read_ThrowsOnMissingId()
{
string responseText = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" Version=""2.0""
IssueInstant=""2013-01-01T00:00:00Z"" InResponseTo = ""InResponseToId"" Destination=""http://destination.example.com"">
<saml2p:Status>
<saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Requester"" />
<saml2p:StatusMessage>Unable to encrypt assertion</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>";

Action a = () => Saml2Response.Read( responseText );

a.Should().Throw<BadFormatSamlResponseException>()
.WithMessage( "Attribute 'ID' (case-sensitive) was not found or its value is empty" );
}

[TestMethod]
public void Saml2Response_Read_ThrowsOnEmptyId()
{
string responseText = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" Version=""2.0"" ID="" ""
IssueInstant=""2013-01-01T00:00:00Z"" Destination=""http://destination.example.com"">
<saml2p:Status>
<saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Requester"" />
<saml2p:StatusMessage>Unable to encrypt assertion</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>";

Action a = () => Saml2Response.Read( responseText );

a.Should().Throw<BadFormatSamlResponseException>()
.WithMessage( "Attribute 'ID' (case-sensitive) was not found or its value is empty" );
}

[TestMethod]
public void Saml2Response_Read_ThrowsOnMissingIssueInstant()
{
string responseText = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" Version=""2.0"" ID=""_abc123""
Destination=""http://destination.example.com"">
<saml2p:Status>
<saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Requester"" />
<saml2p:StatusMessage>Unable to encrypt assertion</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>";

Action a = () => Saml2Response.Read( responseText );

a.Should().Throw<BadFormatSamlResponseException>()
.WithMessage( "Attribute 'IssueInstant' (case-sensitive) was not found or its value is empty" );
}

[TestMethod]
public void Saml2Response_Read_ThrowsOnMissingStatus()
{
string responseText = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" Version=""2.0"" ID=""_abc123""
IssueInstant=""2013-01-01T00:00:00Z"" Destination=""http://destination.example.com"">
<Flatus>
<saml2p:StatusCode Value=""urn:oasis:names:tc:SAML:2.0:status:Requester"" />
<saml2p:StatusMessage>Unable to encrypt assertion</saml2p:StatusMessage>
</Flatus>
</saml2p:Response>";

Action a = () => Saml2Response.Read( responseText );

a.Should().Throw<BadFormatSamlResponseException>()
.WithMessage( "Element 'Status' (case-sensitive, namespace 'urn:oasis:names:tc:SAML:2.0:protocol') was not found" );
}

[TestMethod]
public void Saml2Response_Read_ThrowsOnMissingStatusCode()
{
string responseText = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<saml2p:Response xmlns:saml2p=""urn:oasis:names:tc:SAML:2.0:protocol"" Version=""2.0"" ID=""_abc123""
IssueInstant=""2013-01-01T00:00:00Z"" Destination=""http://destination.example.com"">
<saml2p:Status>
<saml2p:StatusMessage>Unable to encrypt assertion</saml2p:StatusMessage>
</saml2p:Status>
</saml2p:Response>";

Action a = () => Saml2Response.Read( responseText );

a.Should().Throw<BadFormatSamlResponseException>()
.WithMessage( "Element 'StatusCode' (case-sensitive, namespace 'urn:oasis:names:tc:SAML:2.0:protocol') was not found" );
}

[TestMethod]
public void Saml2Response_Read_ThrowsOnMalformedDestination()
{
Expand Down

0 comments on commit 2967605

Please sign in to comment.