Skip to content

Commit

Permalink
Option to set public origin
Browse files Browse the repository at this point in the history
- Fixes #340
  • Loading branch information
AndersAbel committed Feb 8, 2016
2 parents 1a48b27 + 4462779 commit 61112c0
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 5 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ Stephen Patches
Ville Ruuskanen
Matthew Paul
Erik Dahl
Simon Hofer
1 change: 0 additions & 1 deletion Kentor.AuthServices.Owin/OwinContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public async static Task<HttpRequestData> ToHttpRequestData(this IOwinContext co
{
applicationRootPath = "/";
}

return new HttpRequestData(
context.Request.Method,
context.Request.Uri,
Expand Down
24 changes: 24 additions & 0 deletions Kentor.AuthServices.Tests/IdentityProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,30 @@ public void IdentityProvider_CreateAuthenticateRequest_BasicInfo()
subject.RelayState.Should().HaveLength(56);
}

[TestMethod]
public void IdentityProvider_CreateAuthenticateRequest_PublicOrigin()
{
var origin = new Uri("https://my.public.origin:8443/");
var options = StubFactory.CreateOptionsPublicOrigin(origin);

var idp = options.IdentityProviders.Default;

var urls = StubFactory.CreateAuthServicesUrlsPublicOrigin(origin);
var subject = idp.CreateAuthenticateRequest(null, urls);

var expected = new Saml2AuthenticationRequest()
{
AssertionConsumerServiceUrl = urls.AssertionConsumerServiceUrl,
DestinationUrl = idp.SingleSignOnServiceUrl,
Issuer = options.SPOptions.EntityId,
AttributeConsumingServiceIndex = 0
};

subject.ShouldBeEquivalentTo(expected, opt => opt
.Excluding(au => au.Id)
.Excluding(au => au.RelayState));
}

[TestMethod]
public void IdentityProvider_CreateAuthenticateRequest_NoAttributeIndex()
{
Expand Down
19 changes: 19 additions & 0 deletions Kentor.AuthServices.Tests/Mvc/AuthServicesControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,24 @@ public void AuthServicesController_Metadata()

xmlData.Root.Name.Should().Be(Saml2Namespaces.Saml2Metadata + "EntityDescriptor");
}

[TestMethod]
public void AuthServicesController_SignIn_Returns_Public_Origin()
{
AuthServicesController.Options = new Options(new SPOptions
{
DiscoveryServiceUrl = new Uri("http://ds.example.com"),
PublicOrigin = new Uri("https://my.public.origin:8443"),
EntityId = new EntityId("https://github.com/KentorIT/authservices")
});

var subject = CreateInstanceWithContext();

var result = subject.SignIn();

result.Should().BeOfType<RedirectResult>().And
.Subject.As<RedirectResult>().Url
.Should().StartWith("http://ds.example.com/?entityID=https%3A%2F%2Fgithub.com%2FKentorIT%2Fauthservices&return=https%3A%2F%2Fmy.public.origin%3A8443%2F");
}
}
}
53 changes: 53 additions & 0 deletions Kentor.AuthServices.Tests/StubFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,48 @@ internal static AuthServicesUrls CreateAuthServicesUrls()
return new AuthServicesUrls(new Uri("http://localhost"), "/AuthServices");
}

internal static AuthServicesUrls CreateAuthServicesUrlsPublicOrigin(Uri publicOrigin)
{
return new AuthServicesUrls(publicOrigin, "/AuthServices");
}

internal static SPOptions CreateSPOptions()
{
var org = new Organization();

org.Names.Add(new LocalizedName("Kentor.AuthServices", CultureInfo.InvariantCulture));
org.DisplayNames.Add(new LocalizedName("Kentor AuthServices", CultureInfo.InvariantCulture));
org.Urls.Add(new LocalizedUri(
new Uri("http://github.com/KentorIT/authservices"),
CultureInfo.InvariantCulture));

var options = new SPOptions
{
EntityId = new EntityId("https://github.com/KentorIT/authservices"),
MetadataCacheDuration = new TimeSpan(0, 0, 42),
MetadataValidDuration = TimeSpan.FromDays(24),
WantAssertionsSigned = true,
Organization = org,
DiscoveryServiceUrl = new Uri("https://ds.example.com"),
ReturnUrl = new Uri("https://localhost/returnUrl")
};

options.SystemIdentityModelIdentityConfiguration.ClaimsAuthenticationManager
= new ClaimsAuthenticationManagerStub();
options.SystemIdentityModelIdentityConfiguration.AudienceRestriction.AudienceMode
= AudienceUriMode.Never;

AddContacts(options);
AddAttributeConsumingServices(options);

return options;
}


internal static SPOptions CreateSPOptions(Uri publicOrigin)
{
var org = new Organization();

org.Names.Add(new LocalizedName("Kentor.AuthServices", CultureInfo.InvariantCulture));
org.DisplayNames.Add(new LocalizedName("Kentor AuthServices", CultureInfo.InvariantCulture));
org.Urls.Add(new LocalizedUri(
Expand All @@ -40,6 +78,7 @@ internal static SPOptions CreateSPOptions()
Organization = org,
DiscoveryServiceUrl = new Uri("https://ds.example.com"),
ReturnUrl = new Uri("https://localhost/returnUrl"),
PublicOrigin = publicOrigin
};

options.SystemIdentityModelIdentityConfiguration.ClaimsAuthenticationManager
Expand Down Expand Up @@ -110,6 +149,20 @@ internal static Options CreateOptions()
return (Options)CreateOptions(sp => new Options(sp));
}

private static IOptions CreateOptionsPublicOrigin(Func<ISPOptions, IOptions> factory, Uri publicOrigin)
{
var options = factory(CreateSPOptions(publicOrigin));

KentorAuthServicesSection.Current.IdentityProviders.RegisterIdentityProviders(options);
KentorAuthServicesSection.Current.Federations.RegisterFederations(options);

return options;
}
internal static Options CreateOptionsPublicOrigin(Uri publicOrigin)
{
return (Options)CreateOptionsPublicOrigin(sp => new Options(sp), publicOrigin);
}

internal static KentorAuthServicesAuthenticationOptions CreateOwinOptions()
{
return (KentorAuthServicesAuthenticationOptions)CreateOptions(
Expand Down
36 changes: 36 additions & 0 deletions Kentor.AuthServices.Tests/WebSSO/AuthServicesUrlsTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading.Tasks;
using System.Web;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Kentor.AuthServices.Configuration;
using FluentAssertions;
using Kentor.AuthServices.HttpModule;
using Kentor.AuthServices.Tests.Helpers;
using Kentor.AuthServices.WebSso;
using NSubstitute;
using Kentor.AuthServices.Owin;

namespace Kentor.AuthServices.Tests.WebSso
{
Expand Down Expand Up @@ -107,5 +115,33 @@ public void AuthServicesUrls_Ctor_NullCheckSignin()

a.ShouldThrow<ArgumentNullException>("signInUrl");
}

[TestMethod]
public void AuthServicesUrls_Ctor_FromHttpRequest_PublicOrigin()
{
var url = new Uri("http://example.com:42/ApplicationPath/Path?name=DROP%20TABLE%20STUDENTS");
string appPath = "/ApplicationPath";
var request = Substitute.For<HttpRequestBase>();
request.HttpMethod.Returns("GET");
request.Url.Returns(url);
request.Form.Returns(new NameValueCollection { { "Key", "Value" } });
request.ApplicationPath.Returns(appPath);
var options = StubFactory.CreateOptionsPublicOrigin(new Uri("https://my.public.origin:8443/OtherPath"));
var subject = request.ToHttpRequestData();
var urls = new AuthServicesUrls(subject, options.SPOptions);
urls.AssertionConsumerServiceUrl.ShouldBeEquivalentTo("https://my.public.origin:8443/OtherPath/AuthServices/Acs");
urls.SignInUrl.ShouldBeEquivalentTo("https://my.public.origin:8443/OtherPath/AuthServices/SignIn");
}

[TestMethod]
public async Task AuthServicesUrls_Ctor_FromOwinHttpRequestData_PublicOrigin()
{
var ctx = OwinTestHelpers.CreateOwinContext();
var options = StubFactory.CreateOptionsPublicOrigin(new Uri("https://my.public.origin:8443/"));
var subject = await ctx.ToHttpRequestData();
var urls = new AuthServicesUrls(subject, options.SPOptions);
urls.AssertionConsumerServiceUrl.ShouldBeEquivalentTo("https://my.public.origin:8443/AuthServices/Acs");
urls.SignInUrl.ShouldBeEquivalentTo("https://my.public.origin:8443/AuthServices/SignIn");
}
}
}
17 changes: 17 additions & 0 deletions Kentor.AuthServices.Tests/WebSSO/SignInCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FluentAssertions;
using System.Net;
Expand All @@ -11,6 +12,8 @@
using Kentor.AuthServices.Tests.Helpers;
using Kentor.AuthServices.Configuration;
using System.IdentityModel.Metadata;
using System.Xml;
using System.Xml.Schema;
using Kentor.AuthServices.Internal;
using Kentor.AuthServices.WebSso;
using System.IdentityModel.Tokens;
Expand Down Expand Up @@ -132,5 +135,19 @@ public void SignInCommand_Run_ReturnsRedirectToDiscoveryService()

result.Location.Should().Be(expectedLocation);
}

[TestMethod]
public void SignInCommand_Run_PublicOrigin()
{
var options = StubFactory.CreateOptionsPublicOrigin(new Uri("https://my.public.origin:8443"));
var idp = options.IdentityProviders.Default;

var request = new HttpRequestData("GET",
new Uri("http://sp.example.com?idp=" + Uri.EscapeDataString(idp.EntityId.Id)));

var subject = new SignInCommand().Run(request, Options.FromConfiguration);

subject.Location.Host.Should().Be(new Uri("https://idp.example.com").Host);
}
}
}
8 changes: 8 additions & 0 deletions Kentor.AuthServices/Configuration/ISPOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ public interface ISPOptions
/// </summary>
string ModulePath { get; }

/// <summary>
/// By default, the service provider uses the host, protocol, and port
/// from the HTTP request when creating links. This might not be
/// accurate in reverse proxy or load-balancing situations. You can
/// override the origin used for link generation using this property.
/// </summary>
Uri PublicOrigin { get; }

/// <summary>
/// Metadata describing the organization responsible for the SAML2 entity.
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions Kentor.AuthServices/Configuration/KentorAuthServicesSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ public Uri ReturnUrl
}
}

/// <summary>
/// By default, the service provider uses the host, protocol, and port
/// from the HTTP request when creating links. This might not be
/// accurate in reverse proxy or load-balancing situations. You can
/// override the origin used for link generation using this property.
/// </summary>
[ConfigurationProperty("publicOrigin", IsRequired = false)]
public Uri PublicOrigin
{
get
{
return (Uri)base["publicOrigin"];
}
}

/// <summary>
/// Set of identity providers known to the service provider.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions Kentor.AuthServices/Configuration/SPOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public SPOptions(KentorAuthServicesSection configSection)
DiscoveryServiceUrl = configSection.DiscoveryServiceUrl;
EntityId = configSection.EntityId;
ModulePath = configSection.ModulePath;
PublicOrigin = configSection.PublicOrigin;
Organization = configSection.Organization;
AuthenticateRequestSigningBehavior = configSection.AuthenticateRequestSigningBehavior;
NameIdPolicy = new Saml2NameIdPolicy(
Expand Down Expand Up @@ -167,6 +168,14 @@ public string ModulePath
}
}

/// <summary>
/// By default, the service provider uses the host, protocol, and port
/// from the HTTP request when creating links. This might not be
/// accurate in reverse proxy or load-balancing situations. You can
/// override the origin used for link generation using this property.
/// </summary>
public Uri PublicOrigin { get; set; }

/// <summary>
/// Metadata describing the organization responsible for the entity.
/// </summary>
Expand Down
13 changes: 10 additions & 3 deletions Kentor.AuthServices/WebSSO/AuthServicesUrls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public AuthServicesUrls(HttpRequestData request, ISPOptions spOptions)
throw new ArgumentNullException(nameof(spOptions));
}

Init(request.ApplicationUrl, spOptions.ModulePath);
Init(request.ApplicationUrl, spOptions);
}

/// <summary>
Expand Down Expand Up @@ -73,19 +73,26 @@ public AuthServicesUrls(Uri assertionConsumerServiceUrl, Uri signInUrl)
SignInUrl = signInUrl;
}

void Init(Uri applicationUrl, string modulePath)
void Init(Uri publicOrigin, string modulePath)
{
if (!modulePath.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("modulePath should start with /.");
}

var authServicesRoot = applicationUrl.AbsoluteUri.TrimEnd('/') + modulePath + "/";
var authServicesRoot = publicOrigin.AbsoluteUri.TrimEnd('/') + modulePath + "/";

AssertionConsumerServiceUrl = new Uri(authServicesRoot + CommandFactory.AcsCommandName);
SignInUrl = new Uri(authServicesRoot + CommandFactory.SignInCommandName);
}


void Init(Uri applicationUrl, ISPOptions spOptions)
{
var publicOrigin = spOptions.PublicOrigin ?? applicationUrl;
Init(publicOrigin, spOptions.ModulePath);
}

/// <summary>
/// The full url of the assertion consumer service.
/// </summary>
Expand Down
13 changes: 12 additions & 1 deletion doc/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Root element of the config section.
* [`modulePath`](#modulepath-attribute)
* [`authenticateRequestSigningBehavior`](#authenticaterequestsigningbehavior-attribute)
* [`validateCertificates`](#validatecertificates-attribute)
* [`publicOrigin`](#publicOrigin-attribute)

####Elements
* [`<nameIdPolicy>`](#nameidpolicy-element)
Expand Down Expand Up @@ -123,7 +124,7 @@ Defaults to `/AuthServices` if not specified. This can usually be left as the
default, but if several instances of AuthServices are loaded into the
same process they must each get a separate base path.

###`authenticateRequestSigningBehavior` Attribute
####`authenticateRequestSigningBehavior` Attribute
*Optional Attribute of the [`<kentor.AuthServices>`](#kentor-authservices-section) element.*

Optional attribute that sets the signing behavior for generated AuthnRequests.
Expand All @@ -144,6 +145,16 @@ extra security, you can enable certificate validation. Please note that the
SAML metadata specification explicitly places no requirements on certificate
validation, so don't be surprised if an Idp certificate doesn't pass validation.

####`publicOrigin` Attribute
*Optional Attribute of the [`<kentor.authServices>`](#kentor-authservices-section) element.*

Optional attribute that indicates the base url of the AuthServices endpoints.
Defaults to `Url` of the current request base `System.Web.HttpRequestBase` if
not specified. This can usually be left as the default, but if your internal
address of the application is diffrent the external address this can correct a
wrongly set `AssertionConsumerServiceURL` in the `saml2p:AuthnRequest`.
This might not be accurate in reverse proxy or load-balancing situations.

###`<nameIdPolicy>` Element
*Optional child element of the [`<kentor.authServices>`](#kentor-authservices-section) element.*

Expand Down

0 comments on commit 61112c0

Please sign in to comment.