Skip to content

Commit

Permalink
Support for Audience Restriction in StubIdp
Browse files Browse the repository at this point in the history
  • Loading branch information
AndersAbel committed Feb 25, 2016
2 parents 4218e82 + 24f33c2 commit 96480e4
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 46 deletions.
11 changes: 6 additions & 5 deletions Kentor.AuthServices.IntegrationTests/SignInTests.cs
Expand Up @@ -19,6 +19,7 @@ public void SignInAndOut_IdpInitiated_MVC()
{
I.Open("http://localhost:52071/")
.Enter("http://localhost:2181/AuthServices/Acs").In("#AssertionModel_AssertionConsumerServiceUrl")
.Enter("http://localhost:2181/AuthServices").In("#AssertionModel_Audience")
.Click("#binding_artifact")
.Click("#submit");

Expand All @@ -41,6 +42,7 @@ public void SignInAndOut_IdpInitiated_HttpModule()
{
I.Open("http://localhost:52071/")
.Enter("http://localhost:17009/SamplePath/AuthServices/Acs").In("#AssertionModel_AssertionConsumerServiceUrl")
.Enter("http://localhost:17009/SamplePath/AuthServices").In("#AssertionModel_Audience")
.Click("#submit")
.Assert.Text("JohnDoe").In("tbody tr td:nth-child(2)");

Expand Down Expand Up @@ -115,11 +117,10 @@ public void SignIn_AuthnRequest_MVC_SpecificIdp()
[TestMethod]
public void SignInAndOut_IdpInitiated_Owin()
{
I.Open("http://localhost:52071/");

I.Enter("http://localhost:57294/AuthServices/Acs").In("#AssertionModel_AssertionConsumerServiceUrl");

I.Enter("IntegrationTestNameId").In("#AssertionModel_NameId");
I.Open("http://localhost:52071/")
.Enter("http://localhost:57294/AuthServices/Acs").In("#AssertionModel_AssertionConsumerServiceUrl")
.Enter("IntegrationTestNameId").In("#AssertionModel_NameId")
.Enter("http://localhost:57294/AuthServices").In("#AssertionModel_Audience");

I.Click("#submit")
.Wait(1);
Expand Down
1 change: 1 addition & 0 deletions Kentor.AuthServices.StubIdp/Controllers/HomeController.cs
Expand Up @@ -56,6 +56,7 @@ public ActionResult Index(Guid? idpId)
model.AssertionModel.InResponseTo = request.Id.Value;
model.AssertionModel.AssertionConsumerServiceUrl = request.AssertionConsumerServiceUrl.ToString();
model.AssertionModel.RelayState = extractedMessage.RelayState;
model.AssertionModel.Audience = request.Issuer.Id;
model.AssertionModel.AuthnRequestXml = extractedMessage.Data.PrettyPrint();
}

Expand Down
9 changes: 8 additions & 1 deletion Kentor.AuthServices.StubIdp/Models/AssertionModel.cs
Expand Up @@ -29,6 +29,9 @@ public class AssertionModel
[Required]
public string NameId { get; set; }

[Display(Name = "Audience")]
public string Audience { get; set; }

public ICollection<AttributeStatementModel> AttributeStatements { get; set; }

public const string DefaultSessionIndex = "42";
Expand Down Expand Up @@ -71,11 +74,15 @@ public Saml2Response ToSaml2Response()
{
saml2Id = new Saml2Id(InResponseTo);
}

var audienceUrl = string.IsNullOrEmpty(Audience)
? null
: new Uri(Audience);

return new Saml2Response(
new EntityId(UrlResolver.MetadataUrl.ToString()),
CertificateHelper.SigningCertificate, new Uri(AssertionConsumerServiceUrl),
saml2Id, RelayState, identity);
saml2Id, RelayState, audienceUrl, identity);
}

[Display(Name = "Incoming AuthnRequest")]
Expand Down
6 changes: 6 additions & 0 deletions Kentor.AuthServices.StubIdp/Views/Home/Index.cshtml
Expand Up @@ -70,6 +70,12 @@ else
@Html.ValidationMessageFor(m => m.AssertionModel.AssertionConsumerServiceUrl)
</div>

<div class="hide-details">
@Html.LabelFor(m => m.AssertionModel.Audience)
@Html.EditorFor(m => m.AssertionModel.Audience)
@Html.ValidationMessageFor(m => m.AssertionModel.Audience)
</div>

<div class="hide-details">
<label>Send response via</label>
<div class="radio-button-group">
Expand Down
19 changes: 19 additions & 0 deletions Kentor.AuthServices.Tests/ClaimsIdentityExtensionsTests.cs
Expand Up @@ -117,5 +117,24 @@ public void ClaimsIdentityExtensions_ToSaml2Assertion_Includes_DefaultCondition(
// Default validity time is hearby defined to two minutes.
a.Conditions.NotOnOrAfter.Value.Should().BeCloseTo(DateTime.UtcNow.AddMinutes(2));
}

[TestMethod]
public void ClaimsIdentityExtensions_ToSaml2Assertion_Includes_AudienceRestriction()
{
var ci = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.NameIdentifier, "JohnDoe")
});

var audience = "http://sp.example.com/";

var a = ci.ToSaml2Assertion(
new EntityId("http://idp.example.com/"),
new Uri(audience));

a.Conditions.AudienceRestrictions.Should().HaveCount(1, "there should be one set of audience restrictions")
.And.Subject.Single().Audiences.Should().HaveCount(1, "there should be one allowed audience")
.And.Subject.Single().AbsoluteUri.Should()
.Be("http://sp.example.com/");
}
}
}
34 changes: 30 additions & 4 deletions Kentor.AuthServices.Tests/Saml2ConditionsExtensionsTests.cs
Expand Up @@ -2,6 +2,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IdentityModel.Tokens;
using FluentAssertions;
using System.Linq;
using System.Xml.Linq;

namespace Kentor.AuthServices.Tests
{
Expand All @@ -17,18 +19,42 @@ public void Saml2ConditionsExtensions_ToXElement_Nullcheck()
}

[TestMethod]
public void Saml2ConditionsExtensions_ToXElement()
public void Saml2ConditionsExtensions_ToXElement_OnlyNotOnOrAfter()
{
var conditions = new Saml2Conditions()
{
NotOnOrAfter = new DateTime(2099, 07, 25, 19, 52, 42, DateTimeKind.Utc)
};

var subject = conditions.ToXElement();
var actual = conditions.ToXElement();

subject.Name.Should().Be(Saml2Namespaces.Saml2 + "Conditions");
actual.Name.Should().Be(Saml2Namespaces.Saml2 + "Conditions");

subject.Attribute("NotOnOrAfter").Value.Should().Be("2099-07-25T19:52:42Z");
actual.Attribute("NotOnOrAfter").Value.Should().Be("2099-07-25T19:52:42Z");
}

[TestMethod]
public void Saml2ConditionsExtensions_ToXElement_OnlyAudienceRestriction()
{
var conditions = new Saml2Conditions();
conditions.AudienceRestrictions.Add(new Saml2AudienceRestriction(new[]
{
new Uri("http://foo1"),
new Uri("http://foo2")
}));

conditions.AudienceRestrictions.Add(new Saml2AudienceRestriction(new Uri("http://bar")));

var actual = conditions.ToXElement();

var expected = new XElement(Saml2Namespaces.Saml2 + "Conditions",
new XElement(Saml2Namespaces.Saml2 + "AudienceRestriction",
new XElement(Saml2Namespaces.Saml2 + "Audience", "http://foo1"),
new XElement(Saml2Namespaces.Saml2 + "Audience", "http://foo2")),
new XElement(Saml2Namespaces.Saml2 + "AudienceRestriction",
new XElement(Saml2Namespaces.Saml2 + "Audience", "http://bar")));

actual.Should().BeEquivalentTo(expected);
}
}
}
30 changes: 29 additions & 1 deletion Kentor.AuthServices.Tests/Saml2P/Saml2ResponseTests.cs
Expand Up @@ -1781,10 +1781,38 @@ public void Saml2Response_Xml_FromData_ContainsInResponseTo()
xml.GetAttribute("InResponseTo").Should().Be("InResponseToID");
}

[TestMethod]
public void Saml2Response_Xml_FromData_ContainsAudienceRestriction()
{
var identity = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "JohnDoe")
});

var audience = "http://sp.example.com";

var subject = new Saml2Response(
new EntityId("issuer"),
SignedXmlHelper.TestCert,
new Uri("http://destination.example.com"),
new Saml2Id("InResponseToID"),
null,
new Uri(audience),
identity);

var actual = subject.XmlElement;

actual["Assertion", Saml2Namespaces.Saml2Name].Should().NotBeNull("Assertion element should be present")
.And.Subject["Conditions", Saml2Namespaces.Saml2Name].Should().NotBeNull("Conditions element should be present")
.And.Subject["AudienceRestriction", Saml2Namespaces.Saml2Name].Should().NotBeNull("AudienceRestriction element should be present")
.And.Subject["Audience", Saml2Namespaces.Saml2Name].Should().NotBeNull("Audience element should be present")
.And.Subject.InnerText.Should().Be(audience);
}

[TestMethod]
public void Saml2Response_FromData_RelayState()
{
var subject = new Saml2Response(new EntityId("issuer"), null, null, null, "ABC123", null);
var subject = new Saml2Response(new EntityId("issuer"), null, null, null, "ABC123");

subject.RelayState.Should().Be("ABC123");
}
Expand Down
32 changes: 28 additions & 4 deletions Kentor.AuthServices/ClaimsIdentityExtensions.cs
Expand Up @@ -14,8 +14,25 @@ public static class ClaimsIdentityExtensions
/// <summary>
/// Creates a Saml2Assertion from a ClaimsIdentity.
/// </summary>
/// <param name="identity">Claims to include in Assertion.</param>
/// <param name="issuer">Issuer to include in assertion.</param>
/// <returns>Saml2Assertion</returns>
public static Saml2Assertion ToSaml2Assertion(this ClaimsIdentity identity, EntityId issuer)
{
return ToSaml2Assertion(identity, issuer, null);
}

/// <summary>
/// Creates a Saml2Assertion from a ClaimsIdentity.
/// </summary>
/// <param name="identity">Claims to include in Assertion.</param>
/// <param name="issuer">Issuer to include in assertion.</param>
/// <param name="audience">Audience to set as audience restriction.</param>
/// <returns>Saml2Assertion</returns>
public static Saml2Assertion ToSaml2Assertion(
this ClaimsIdentity identity,
EntityId issuer,
Uri audience)
{
if (identity == null)
{
Expand All @@ -27,10 +44,11 @@ public static Saml2Assertion ToSaml2Assertion(this ClaimsIdentity identity, Enti
throw new ArgumentNullException(nameof(issuer));
}

var assertion = new Saml2Assertion(new Saml2NameIdentifier(issuer.Id));

assertion.Subject = new Saml2Subject(new Saml2NameIdentifier(
identity.Claims.Single(c => c.Type == ClaimTypes.NameIdentifier).Value));
var assertion = new Saml2Assertion(new Saml2NameIdentifier(issuer.Id))
{
Subject = new Saml2Subject(new Saml2NameIdentifier(
identity.Claims.Single(c => c.Type == ClaimTypes.NameIdentifier).Value)),
};

assertion.Statements.Add(
new Saml2AuthenticationStatement(
Expand Down Expand Up @@ -58,6 +76,12 @@ public static Saml2Assertion ToSaml2Assertion(this ClaimsIdentity identity, Enti
NotOnOrAfter = DateTime.UtcNow.AddMinutes(2)
};

if(audience != null)
{
assertion.Conditions.AudienceRestrictions.Add(
new Saml2AudienceRestriction(audience));
}

return assertion;
}
}
Expand Down
28 changes: 27 additions & 1 deletion Kentor.AuthServices/SAML2P/Saml2Response.cs
Expand Up @@ -131,6 +131,27 @@ public Saml2Response(XmlElement xml, string relayState)
: this(issuer, signingCertificate, destinationUrl, inResponseTo, null, claimsIdentities)
{ }

/// <summary>
/// Create a response with the supplied data.
/// </summary>
/// <param name="issuer">Issuer of the response.</param>
/// <param name="signingCertificate">The certificate to use when signing
/// this response in XML form.</param>
/// <param name="destinationUrl">The destination Uri for the message</param>
/// <param name="inResponseTo">In response to id</param>
/// <param name="relayState">RelayState associated with the message.</param>
/// <param name="claimsIdentities">Claims identities to be included in the
/// response. Each identity is translated into a separate assertion.</param>
public Saml2Response(
EntityId issuer,
X509Certificate2 signingCertificate,
Uri destinationUrl,
Saml2Id inResponseTo,
string relayState,
params ClaimsIdentity[] claimsIdentities)
: this(issuer, signingCertificate, destinationUrl, inResponseTo, relayState, null, claimsIdentities)
{ }

/// <summary>
/// Create a response with the supplied data.
/// </summary>
Expand All @@ -141,13 +162,15 @@ public Saml2Response(XmlElement xml, string relayState)
/// <param name="inResponseTo">In response to id</param>
/// <param name="relayState">RelayState associated with the message.</param>
/// <param name="claimsIdentities">Claims identities to be included in the
/// <param name="audience">Audience of the response, set as AudienceRestriction</param>
/// response. Each identity is translated into a separate assertion.</param>
public Saml2Response(
EntityId issuer,
X509Certificate2 issuerCertificate,
Uri destinationUrl,
Saml2Id inResponseTo,
string relayState,
Uri audience,
params ClaimsIdentity[] claimsIdentities)
{
Issuer = issuer;
Expand All @@ -158,6 +181,7 @@ public Saml2Response(XmlElement xml, string relayState)
InResponseTo = inResponseTo;
id = new Saml2Id("id" + Guid.NewGuid().ToString("N"));
status = Saml2StatusCode.Success;
this.audience = audience;
}

/// <summary>
Expand Down Expand Up @@ -240,7 +264,7 @@ private void CreateXmlElement()
foreach (var ci in claimsIdentities)
{
responseElement.AppendChild(xml.ReadNode(
ci.ToSaml2Assertion(Issuer).ToXElement().CreateReader()));
ci.ToSaml2Assertion(Issuer, audience).ToXElement().CreateReader()));
}

xmlElement = xml.DocumentElement;
Expand Down Expand Up @@ -457,6 +481,8 @@ private void ValidateSignature(IOptions options)
}
}

private Uri audience;

private IEnumerable<ClaimsIdentity> claimsIdentities;
private Exception createClaimsException;

Expand Down
16 changes: 13 additions & 3 deletions Kentor.AuthServices/Saml2ConditionsExtensions.cs
Expand Up @@ -26,9 +26,19 @@ public static XElement ToXElement(this Saml2Conditions conditions)
throw new ArgumentNullException(nameof(conditions));
}

return new XElement(Saml2Namespaces.Saml2 + "Conditions",
new XAttribute("NotOnOrAfter",
conditions.NotOnOrAfter.Value.ToSaml2DateTimeString()));
var xml = new XElement(Saml2Namespaces.Saml2 + "Conditions");

xml.AddAttributeIfNotNullOrEmpty("NotOnOrAfter",
conditions.NotOnOrAfter?.ToSaml2DateTimeString());

foreach(var ar in conditions.AudienceRestrictions)
{
xml.Add(new XElement(Saml2Namespaces.Saml2 + "AudienceRestriction",
ar.Audiences.Select(a =>
new XElement(Saml2Namespaces.Saml2 + "Audience", a.OriginalString))));
}

return xml;
}
}
}
9 changes: 0 additions & 9 deletions SampleApplication/Web.config
Expand Up @@ -57,15 +57,6 @@
<add fileName="~/App_Data/Kentor.AuthServices.Tests.pfx" />
</serviceCertificates>
</kentor.authServices>
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration>
<audienceUris mode="Never" />
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" name="SampleApplicationAuth"/>
Expand Down
3 changes: 0 additions & 3 deletions SampleIdentityServer3/Startup.cs
Expand Up @@ -128,9 +128,6 @@ private void ConfigureSaml2(IAppBuilder app, string signInAsType)

UseIdSrv3LogoutOnFederatedLogout(app, options);

options.SPOptions.SystemIdentityModelIdentityConfiguration.AudienceRestriction.AudienceMode
= AudienceUriMode.Never;

options.SPOptions.ServiceCertificates.Add(new X509Certificate2(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/Kentor.AuthServices.Tests.pfx"));

Expand Down
9 changes: 0 additions & 9 deletions SampleMvcApplication/Web.config
Expand Up @@ -46,15 +46,6 @@
<add fileName="~/App_Data/Kentor.AuthServices.Tests.pfx" />
</serviceCertificates>
</kentor.authServices>
<system.identityModel>
<identityConfiguration>
<securityTokenHandlers>
<securityTokenHandlerConfiguration>
<audienceUris mode="Never" />
</securityTokenHandlerConfiguration>
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" name="SampleMvcAppilcationAuth" />
Expand Down

0 comments on commit 96480e4

Please sign in to comment.