Skip to content

Commit

Permalink
Notifications to allow modification of generated XML
Browse files Browse the repository at this point in the history
- Closes #978
- Closes #1158
  • Loading branch information
AndersAbel committed Mar 26, 2020
2 parents 4a629eb + 714e743 commit f4f5fd5
Show file tree
Hide file tree
Showing 26 changed files with 445 additions and 109 deletions.
24 changes: 24 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 class Saml2Notifications
AuthenticationRequestCreated
{ get; set; } = (request, provider, dictionary) => { };

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

/// <summary>
/// Notification called when the SignIn command has produced a
/// <see cref="CommandResult"/>, but before anything has been applied
Expand Down Expand Up @@ -124,6 +129,25 @@ public class Saml2Notifications
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout")]
public Action<CommandResult> LogoutCommandResultCreated { get; set; } = cr => { };

/// <summary>
/// Notification called when a logout request is created to initiate single log
/// out with an identity provider.
/// </summary>
public Action<Saml2LogoutRequest, ClaimsPrincipal, IdentityProvider> LogoutRequestCreated { get; set; } = (lr, user, idp) => { };

/// <summary>
/// Notification called when a logout request has been transformed to an XML node tree.
/// </summary>
public Action<Saml2LogoutRequest, XDocument, Saml2BindingType> LogoutRequestXmlCreated { get; set; } = (lr, xd, bt) => { };

/// <summary>
/// Notification called when a logout request has been received and processed and a Logout Response has been created.
/// </summary>
public Action<Saml2LogoutResponse, Saml2LogoutRequest, ClaimsPrincipal, IdentityProvider> LogoutResponseCreated { get; set; }
= (resp, req, u, idp) => { };

public Action<Saml2LogoutResponse, XDocument, Saml2BindingType> LogoutResponseXmlCreated { get; set; } = (lr, xd, bt) => { };

/// <summary>
/// Notification called when metadata has been created, but before
/// signing. At this point the contents of the metadata can be
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 @@ public Federation(string metadataLocation, bool allowUnsolicitedAuthnResponse, I
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 string MetadataLocation
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, Saml2BindingType> xmlCreatedNotification)
where TMessage: ISaml2Message
{
return Saml2Binding.Get(Binding).Bind(message, spOptions.Logger, 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
29 changes: 29 additions & 0 deletions Sustainsys.Saml2/SAML2P/ISaml2MessageExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Sustainsys.Saml2.Internal;
using Sustainsys.Saml2.WebSso;
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<XDocument> xmlCreatedNotification)
where TMessage : ISaml2Message
{
var xDocument = new XDocument(message.ToXElement());

xmlCreatedNotification(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
7 changes: 6 additions & 1 deletion Sustainsys.Saml2/SAML2P/Saml2LogoutRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ protected override string LocalName
/// </summary>
/// <returns>string containing the Xml data.</returns>
public override string ToXml()
{
return ToXElement().ToString();
}

public override XElement ToXElement()
{
var x = new XElement(Saml2Namespaces.Saml2P + LocalName);

Expand All @@ -103,7 +108,7 @@ public override string ToXml()
x.Add(new XElement(Saml2Namespaces.Saml2P + "SessionIndex",
SessionIndex));

return x.ToString();
return x;
}
}
}
22 changes: 22 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,27 @@ public override string ToXml()
return doc.DocumentElement.OuterXml;
}

public override XElement ToXElement()
{
var xe = new XElement(Saml2Namespaces.Saml2P + "LogoutResponse",
new XAttribute("ID", Id.Value),
new XAttribute("Version", "2.0"),
new XAttribute("IssueInstant", IssueInstant.ToSaml2DateTimeString()),
new XElement(Saml2Namespaces.Saml2P + "Status",
new XElement(Saml2Namespaces.Saml2P + "StatusCode",
new XAttribute("Value", StatusCodeHelper.FromCode(Status)))));

if(Issuer != null)
{
xe.AddFirst(new XElement(Saml2Namespaces.Saml2 + "Issuer", Issuer.Id));
}

xe.AddAttributeIfNotNullOrEmpty("InResponseTo", InResponseTo?.Value);
xe.AddAttributeIfNotNullOrEmpty("Destination", DestinationUrl?.OriginalString);

return xe;
}

/// <summary>
/// Appends xml for the Saml2LogoutResponse to the given parent node.
/// </summary>
Expand Down
31 changes: 8 additions & 23 deletions Sustainsys.Saml2/SAML2P/Saml2RequestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,10 @@ namespace Sustainsys.Saml2.Saml2P
/// </summary>
public abstract class Saml2RequestBase : ISaml2Message
{
private Saml2Id id = new Saml2Id("id" + Guid.NewGuid().ToString("N"));

/// <summary>
/// The id of the request.
/// </summary>
public Saml2Id Id
{
get
{
return id;
}
protected set
{
id = value;
}
}
public Saml2Id Id { get; protected set; } = new Saml2Id("id" + Guid.NewGuid().ToString("N"));

/// <summary>
/// Version of the SAML request. Always returns "2.0"
Expand All @@ -47,19 +35,10 @@ public string Version
}
}

private readonly string issueInstant =
DateTime.UtcNow.ToSaml2DateTimeString();

/// <summary>
/// The instant that the request was issued (well actually, created).
/// </summary>
public string IssueInstant
{
get
{
return issueInstant;
}
}
public string IssueInstant { get; } = DateTime.UtcNow.ToSaml2DateTimeString();

/// <summary>
/// SAML message name for requests - hard coded to SAMLRequest.
Expand Down Expand Up @@ -92,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

0 comments on commit f4f5fd5

Please sign in to comment.