Skip to content

Commit

Permalink
New notification called for signin command
Browse files Browse the repository at this point in the history
  • Loading branch information
AndersAbel committed Mar 26, 2020
1 parent 97eaf4c commit 3a97c86
Show file tree
Hide file tree
Showing 21 changed files with 226 additions and 41 deletions.
5 changes: 5 additions & 0 deletions Sustainsys.Saml2/Configuration/Saml2Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Xml;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Configuration
{
Expand All @@ -25,6 +26,10 @@ public Action<Saml2AuthenticationRequest, IdentityProvider, IDictionary<string,
AuthenticationRequestCreated
{ get; set; } = (request, provider, dictionary) => { };

public Action<Saml2AuthenticationRequest, XDocument>
AuthenticationRequestXmlCreated
{ get; set; } = (request, xDocument) => { };

/// <summary>
/// Notification called when the SignIn command has produced a
/// <see cref="CommandResult"/>, but before anything has been applied
Expand Down
2 changes: 1 addition & 1 deletion Sustainsys.Saml2/Federation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private void Init(string metadataLocation,
LoadMetadata();
}

private object metadataLoadLock = new object();
private readonly object metadataLoadLock = new object();

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification ="We want a retry, regardless of exception type")]
private void LoadMetadata()
Expand Down
23 changes: 21 additions & 2 deletions Sustainsys.Saml2/IdentityProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Security.Claims;
using System.Diagnostics.CodeAnalysis;
using Sustainsys.Saml2.Tokens;
using System.Xml.Linq;

namespace Sustainsys.Saml2
{
Expand Down Expand Up @@ -334,16 +335,34 @@ public Saml2AuthenticationRequest CreateAuthenticateRequest(
public string OutboundSigningAlgorithm { get; set; }

/// <summary>
/// Bind a Saml2AuthenticateRequest using the active binding of the idp,
/// Bind a Saml2 message using the active binding of the idp,
/// producing a CommandResult with the result of the binding.
/// </summary>
/// <param name="request">The AuthnRequest to bind.</param>
/// <remarks>
/// This overload does not support the usage of Xml Created notifications.
/// </remarks>
/// <param name="request">The Saml2 message to bind.</param>
/// <returns>CommandResult with the bound request.</returns>
public CommandResult Bind(ISaml2Message request)
{
return Saml2Binding.Get(Binding).Bind(request);
}

/// <summary>
/// Bind a Saml2 message using the active binding of hte idp,
/// producing a CommandResult with the result of the binding.
/// </summary>
/// <typeparam name="TMessage">Type of the message.</typeparam>
/// <param name="message">The Saml2 message to bind.</param>
/// <param name="xmlCreatedNotification">Notification to call with Xml structure</param>
/// <returns>CommandResult with the bound message.</returns>
public CommandResult Bind<TMessage>(
TMessage message, Action<TMessage, XDocument> xmlCreatedNotification)
where TMessage: ISaml2Message
{
return Saml2Binding.Get(Binding).Bind(message, null, xmlCreatedNotification);
}

private readonly ConfiguredAndLoadedSigningKeysCollection signingKeys =
new ConfiguredAndLoadedSigningKeysCollection();

Expand Down
20 changes: 20 additions & 0 deletions Sustainsys.Saml2/Internal/XDocumentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Internal
{
static class XDocumentExtensions
{
public static string ToStringWithXmlDeclaration(this XDocument xDocument)
{
if (xDocument.Declaration != null)
{
return xDocument.Declaration?.ToString() + "\r\n" + xDocument.ToString();
}

return xDocument.ToString();
}
}
}
7 changes: 7 additions & 0 deletions Sustainsys.Saml2/SAML2P/ISaml2Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Sustainsys.Saml2.Metadata;

namespace Sustainsys.Saml2.Saml2P
Expand All @@ -28,6 +29,12 @@ public interface ISaml2Message
/// <returns>string containing the Xml data.</returns>
string ToXml();

/// <summary>
/// Transforms the message to an XElement object tree.
/// </summary>
/// <returns>XElement with Xml representation of the message</returns>
XElement ToXElement();

/// <summary>
/// The name of the message to use in a query string or form input
/// field. Typically "SAMLRequest" or "SAMLResponse".
Expand Down
27 changes: 27 additions & 0 deletions Sustainsys.Saml2/SAML2P/ISaml2MessageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Sustainsys.Saml2.Internal;
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Saml2P
{
static class Saml2MessageExtensions
{
/// <summary>
/// Serializes the message into wellformed XML.
/// </summary>
/// <param name="message">Saml2 message to transform to XML</param>
/// <param name="xmlCreatedNotification">Notification allowing modification of XML tree before serialization.</param>
/// <returns>string containing the Xml data.</returns>
public static string ToXml<TMessage>(this TMessage message, Action<TMessage, XDocument> xmlCreatedNotification)
where TMessage : ISaml2Message
{
var xDocument = new XDocument(message.ToXElement());

xmlCreatedNotification(message, xDocument);

return xDocument.ToStringWithXmlDeclaration();
}
}
}
5 changes: 5 additions & 0 deletions Sustainsys.Saml2/SAML2P/Saml2ArtifactResolve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@ public override string ToXml()
new XElement(Saml2Namespaces.Saml2P + "Artifact", Artifact))
.ToString();
}

public override XElement ToXElement()
{
throw new NotImplementedException();
}
}
}
5 changes: 3 additions & 2 deletions Sustainsys.Saml2/SAML2P/Saml2AuthenticationRequest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -38,7 +39,7 @@ protected override string LocalName
/// </summary>
/// <returns>XElement</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Lowercase demanded by specification.")]
public XElement ToXElement()
public override XElement ToXElement()
{
var x = new XElement(Saml2Namespaces.Saml2P + LocalName);

Expand Down Expand Up @@ -115,7 +116,7 @@ private void AddScoping(XElement xElement)
}

/// <summary>
/// Serializes the message into wellformed Xml.
/// Serializes the message into wellformed Xml
/// </summary>
/// <returns>string containing the Xml data.</returns>
public override string ToXml()
Expand Down
5 changes: 5 additions & 0 deletions Sustainsys.Saml2/SAML2P/Saml2LogoutRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,10 @@ public override string ToXml()

return x.ToString();
}

public override XElement ToXElement()
{
throw new NotImplementedException();
}
}
}
6 changes: 6 additions & 0 deletions Sustainsys.Saml2/SAML2P/Saml2LogoutResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Saml2P
{
Expand Down Expand Up @@ -33,6 +34,11 @@ public override string ToXml()
return doc.DocumentElement.OuterXml;
}

public override XElement ToXElement()
{
throw new NotImplementedException();
}

/// <summary>
/// Appends xml for the Saml2LogoutResponse to the given parent node.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Sustainsys.Saml2/SAML2P/Saml2RequestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public string MessageName
/// </summary>
protected abstract string LocalName { get; }

/// <summary>
/// Transforms the message to an XElement object tree.
/// </summary>
/// <returns>XElement with Xml representation of the message</returns>
public abstract XElement ToXElement();

/// <summary>
/// Creates XNodes for the fields of the Saml2RequestBase class. These
/// nodes should be added when creating XML out of derived classes.
Expand Down
56 changes: 31 additions & 25 deletions Sustainsys.Saml2/SAML2P/Saml2Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Sustainsys.Saml2.Exceptions;
using System.Diagnostics.CodeAnalysis;
using Sustainsys.Saml2.Metadata;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Saml2P
{
Expand Down Expand Up @@ -105,25 +106,25 @@ public Saml2Response(XmlElement xml, Saml2Id expectedInResponseTo)

xmlElement = xml;

id = new Saml2Id(xml.GetRequiredAttributeValue("ID"));
Id = new Saml2Id(xml.GetRequiredAttributeValue("ID"));

ExpectedInResponseTo = expectedInResponseTo;
ReadInResponseTo(xml);

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

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

status = StatusCodeHelper.FromString(statusString);
Status = StatusCodeHelper.FromString(statusString);

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

Issuer = new EntityId(xmlElement["Issuer", Saml2Namespaces.Saml2Name].GetTrimmedTextIfNotNull());
Expand Down Expand Up @@ -168,7 +169,7 @@ private void ValidateInResponseTo(IOptions options, IEnumerable<ClaimsIdentity>
else
{
throw new UnexpectedInResponseToException(
$"Received message {id.Value} contains unexpected InResponseTo \"{InResponseTo.Value}\". No " +
$"Received message {Id.Value} contains unexpected InResponseTo \"{InResponseTo.Value}\". No " +
$"cookie preserving state from the request was found so the message was not expected to have an " +
$"InResponseTo attribute. This error typically occurs if the cookie set when doing SP-initiated " +
$"sign on have been lost.");
Expand Down Expand Up @@ -273,8 +274,8 @@ public Saml2Response(
DestinationUrl = destinationUrl;
RelayState = relayState;
InResponseTo = inResponseTo;
id = new Saml2Id("id" + Guid.NewGuid().ToString("N"));
status = Saml2StatusCode.Success;
Id = new Saml2Id("id" + Guid.NewGuid().ToString("N"));
Status = Saml2StatusCode.Success;
this.audience = audience;
}

Expand Down Expand Up @@ -312,6 +313,20 @@ public XmlElement XmlElement
}
}

/// <summary>
/// Transforms the message to an XElement object tree.
/// </summary>
/// <remarks>This operation is inefficient, but it is only used by
/// the StubIdp so it's acceptable.</remarks>
/// <returns>XElement with Xml representation of the message</returns>
public XElement ToXElement()
{
using(var reader = new XmlNodeReader(XmlElement))
{
return XElement.Load(reader);
}
}

/// <summary>
/// SAML Message name for responses, hard coded to SAMLResponse.
/// </summary>
Expand Down Expand Up @@ -343,7 +358,7 @@ private void CreateXmlElement()
responseElement.SetAttributeNode("Destination", "").Value = DestinationUrl.ToString();
}

responseElement.SetAttributeNode("ID", "").Value = id.Value;
responseElement.SetAttributeNode("ID", "").Value = Id.Value;
responseElement.SetAttributeNode("Version", "").Value = "2.0";
responseElement.SetAttributeNode("IssueInstant", "").Value =
DateTime.UtcNow.ToSaml2DateTimeString();
Expand Down Expand Up @@ -372,12 +387,10 @@ private void CreateXmlElement()
xmlElement = xml.DocumentElement;
}

readonly Saml2Id id;

/// <summary>
/// Id of the response message.
/// </summary>
public Saml2Id Id { get { return id; } }
public Saml2Id Id { get; }

/// <summary>
/// Expected InResponseTo as extracted from
Expand All @@ -389,33 +402,26 @@ private void CreateXmlElement()
/// </summary>
public Saml2Id InResponseTo { get; private set; }

readonly DateTime issueInstant;

/// <summary>
/// Issue instant of the response message.
/// </summary>
public DateTime IssueInstant { get { return issueInstant; } }

readonly Saml2StatusCode status;
public DateTime IssueInstant { get; }

/// <summary>
/// Status code of the message according to the SAML2 spec section 3.2.2.2
/// </summary>
public Saml2StatusCode Status { get { return status; } }

readonly string statusMessage;
public Saml2StatusCode Status { get; }

/// <summary>
/// StatusMessage of the message according to the SAML2 spec section 3.2.2.1
/// </summary>
public string StatusMessage { get { return statusMessage; } }
public string StatusMessage { get; }

readonly string secondLevelStatus;
/// <summary>
/// Optional status which MAY give additional information about the cause of the problem (according to the SAML2 spec section 3.2.2.2))))))))).
/// Because it may change in future specifications let's not make enum out of it yet.
/// </summary>
public string SecondLevelStatus { get { return secondLevelStatus; } }
public string SecondLevelStatus { get; }

/// <summary>
/// Issuer (= sender) of the response.
Expand Down Expand Up @@ -585,11 +591,11 @@ private IEnumerable<ClaimsIdentity> CreateClaims(IOptions options, IdentityProvi
{
Validate(options, idp);

if (status != Saml2StatusCode.Success)
if (Status != Saml2StatusCode.Success)
{
throw new UnsuccessfulSamlOperationException(
"The Saml2Response must have status success to extract claims.",
status, statusMessage, secondLevelStatus);
Status, StatusMessage, SecondLevelStatus);
}

TokenValidationParameters validationParameters = options.SPOptions.TokenValidationParametersTemplate.Clone();
Expand Down
7 changes: 7 additions & 0 deletions Sustainsys.Saml2/SAML2P/Saml2StatusResponseType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Security.Cryptography.X509Certificates;
using Sustainsys.Saml2.Metadata;
using System.Xml.Linq;

namespace Sustainsys.Saml2.Saml2P
{
Expand Down Expand Up @@ -82,5 +83,11 @@ protected Saml2StatusResponseType(Saml2StatusCode status)
/// </summary>
/// <returns>string containing the Xml data.</returns>
public abstract string ToXml();

/// <summary>
/// Transforms the message to an XElement object tree.
/// </summary>
/// <returns>XElement with Xml representation of the message</returns>
public abstract XElement ToXElement();
}
}
Loading

0 comments on commit 3a97c86

Please sign in to comment.