Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Commit

Permalink
Fix ID Site URL generation to include missing signature
Browse files Browse the repository at this point in the history
  • Loading branch information
nbarbettini committed Jan 8, 2016
1 parent 06db49f commit 1d79db0
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// <copyright file="DefaultIdSiteUrlBuilder_tests.cs" company="Stormpath, Inc.">
// Copyright (c) 2015 Stormpath, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Collections.Generic;
using NSubstitute;
using Shouldly;
using Stormpath.SDK.Api;
using Stormpath.SDK.Client;
using Stormpath.SDK.IdSite;
using Stormpath.SDK.Impl.Client;
using Stormpath.SDK.Impl.IdSite;
using Stormpath.SDK.Impl.Utility;
using Stormpath.SDK.Jwt;
using Xunit;

namespace Stormpath.SDK.Tests.Impl.IdSite
{
public class DefaultIdSiteUrlBuilder_tests
{
private static readonly string FakeApiKeyId = "foobar";
private static readonly string FakeApiKeySecret = "veryLONGsupersecret_string123";

private static readonly string FakeApplicationHref = "https://api.stormpath.com/v1/applications/foobarApplication";
private static readonly string FakeCallbackUri = "http://my.app/login";

private static readonly string SsoUrl = "https://api.stormpath.com/sso";
private static readonly string JwtArg = "?jwtRequest=";

private readonly string fakeJti = "12345";
private readonly IIdSiteJtiProvider fakeJtiProvider;

private readonly DateTimeOffset fakeNow = new DateTimeOffset(2016, 01, 01, 00, 00, 00, TimeSpan.Zero);
private readonly IClock fakeClock;

private readonly IClient fakeClient;

public DefaultIdSiteUrlBuilder_tests()
{
this.fakeJtiProvider = Substitute.For<IIdSiteJtiProvider>();
this.fakeJtiProvider.NewJti().Returns(this.fakeJti);

this.fakeClock = Substitute.For<IClock>();
this.fakeClock.Now.Returns(this.fakeNow);

var testApiKey = ClientApiKeys.Builder()
.SetId(FakeApiKeyId)
.SetSecret(FakeApiKeySecret)
.Build();

this.fakeClient = Clients.Builder()
.SetApiKey(testApiKey)
.Build();
}

private static readonly string PlainRequest = $"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjM0NSIsImlhdCI6MTQ1MTYwNjQwMCwiaXNzIjoiZm9vYmFyIiwic3ViIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hcHBsaWNhdGlvbnMvZm9vYmFyQXBwbGljYXRpb24iLCJjYl91cmkiOiJodHRwOi8vbXkuYXBwL2xvZ2luIn0.s5tkKAJX3SzAey9fvaG_MGJCESpcLfWdM8PVtrwx5KI";

[Fact]
public void Generates_url()
{
var builder = this.GetBuilder();

var url = builder.Build();
url.ShouldBe($"{SsoUrl}{JwtArg}{PlainRequest}");

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
}

[Fact]
public void Generates_url_for_logout()
{
var builder = this.GetBuilder();
builder.ForLogout();

var url = builder.Build();
url.ShouldBe($"{SsoUrl}/logout{JwtArg}{PlainRequest}");
}

[Fact]
public void Generates_url_with_path()
{
var builder = this.GetBuilder();
builder.SetPath("/foo");

var url = builder.Build();

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("path").ShouldBe("/foo");
}

[Fact]
public void Generates_url_with_organization_nameKey()
{
var builder = this.GetBuilder();
builder.SetOrganizationNameKey("gonk");

var url = builder.Build();

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("onk").ShouldBe("gonk");
}

[Fact]
public void Generates_url_with_subdomain_flag()
{
var builder = this.GetBuilder();
builder.SetUseSubdomain(true);

var url = builder.Build();

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("usd").ShouldBe(true);
}

[Fact]
public void Generates_url_with_organization_field_flag()
{
var builder = this.GetBuilder();
builder.SetShowOrganizationField(true);

var url = builder.Build();

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("sof").ShouldBe(true);
}

[Fact]
public void Generates_url_with_state()
{
var builder = this.GetBuilder();
builder.SetState("foobar123!");

var url = builder.Build();

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("state").ShouldBe("foobar123!");
}

[Fact]
public void Generates_url_with_all_the_things()
{
var builder = this.GetBuilder();
builder.ForLogout();
builder.SetOrganizationNameKey("FirstOrder");
builder.SetPath("/blah");
builder.SetShowOrganizationField(false);
builder.SetState("123");
builder.SetUseSubdomain(false);

var url = builder.Build();
url.ShouldStartWith($"{SsoUrl}/logout{JwtArg}");

var jwt = this.ParseUrl(url);
this.VerifyCommon(jwt);
jwt.Body.GetClaim("onk").ShouldBe("FirstOrder");
jwt.Body.GetClaim("path").ShouldBe("/blah");
jwt.Body.GetClaim("sof").ShouldBe(false);
jwt.Body.GetClaim("state").ShouldBe("123");
jwt.Body.GetClaim("usd").ShouldBe(false);
}

private IIdSiteUrlBuilder GetBuilder()
{
var client = this.fakeClient as DefaultClient;

IIdSiteUrlBuilder builder = new DefaultIdSiteUrlBuilder(
client.DataStore, FakeApplicationHref, this.fakeJtiProvider, this.fakeClock);
builder.SetCallbackUri(FakeCallbackUri);

return builder;
}

private IJwt ParseUrl(string url)
=> this.fakeClient.NewJwtParser().Parse(url.Split('=')[1]);

private void VerifyCommon(IJwt jwt)
{
jwt.Body.Issuer.ShouldBe(FakeApiKeyId);
jwt.Body.Subject.ShouldBe(FakeApplicationHref);
jwt.Body.GetClaim("cb_uri").ShouldBe(FakeCallbackUri);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
<Compile Include="Impl\IdSite\DefaultIdSiteAsyncCallbackHandler_exception_tests.cs" />
<Compile Include="Impl\IdSite\DefaultIdSiteSyncCallbackHandler_tests.cs" />
<Compile Include="Impl\IdSite\DefaultIdSiteAsyncCallbackHandler_tests.cs" />
<Compile Include="Impl\IdSite\DefaultIdSiteUrlBuilder_tests.cs" />
<Compile Include="Impl\IdSite\InlineIdSiteSyncResultListener_tests.cs" />
<Compile Include="Impl\IdSite\InlineIdSiteAsyncResultListener_tests.cs" />
<Compile Include="Impl\Jwt\DefaultJwtParser_tests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

using Stormpath.SDK.Application;
using Stormpath.SDK.Impl.IdSite;
using Stormpath.SDK.Impl.Utility;

namespace Stormpath.SDK.Impl.Application
{
internal sealed partial class DefaultApplication
{
SDK.IdSite.IIdSiteUrlBuilder IApplication.NewIdSiteUrlBuilder()
=> new DefaultIdSiteUrlBuilder(this.GetInternalDataStore(), this.AsInterface.Href);
=> new DefaultIdSiteUrlBuilder(this.GetInternalDataStore(), this.AsInterface.Href, new DefaultIdSiteJtiProvider(), new DefaultClock());

SDK.IdSite.IIdSiteAsyncCallbackHandler IApplication.NewIdSiteAsyncCallbackHandler(SDK.Http.IHttpRequest request)
=> new DefaultIdSiteAsyncCallbackHandler(this.GetInternalDataStore(), request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// <copyright file="DefaultIdSiteJtiProvider.cs" company="Stormpath, Inc.">
// Copyright (c) 2015 Stormpath, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;

namespace Stormpath.SDK.Impl.IdSite
{
internal sealed class DefaultIdSiteJtiProvider : IIdSiteJtiProvider
{
string IIdSiteJtiProvider.NewJti() => Guid.NewGuid().ToString();
}
}
30 changes: 23 additions & 7 deletions Stormpath.SDK/Stormpath.SDK/Impl/IdSite/DefaultIdSiteUrlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Stormpath.SDK.IdSite;
using Stormpath.SDK.Impl.DataStore;
using Stormpath.SDK.Impl.Jwt;
using Stormpath.SDK.Impl.Utility;
using Stormpath.SDK.Jwt;

namespace Stormpath.SDK.Impl.IdSite
Expand All @@ -31,6 +32,9 @@ internal sealed class DefaultIdSiteUrlBuilder : IIdSiteUrlBuilder
private readonly string ssoEndpoint;
private readonly string applicationHref;

private readonly IIdSiteJtiProvider jtiProvider;
private readonly IClock clock;

private string callbackUri;
private string state;
private string path;
Expand All @@ -39,7 +43,7 @@ internal sealed class DefaultIdSiteUrlBuilder : IIdSiteUrlBuilder
private bool? useSubdomain;
private bool? showOrganizationField;

public DefaultIdSiteUrlBuilder(IInternalDataStore internalDataStore, string applicationHref)
public DefaultIdSiteUrlBuilder(IInternalDataStore internalDataStore, string applicationHref, IIdSiteJtiProvider jtiProvider, IClock clock)
{
if (internalDataStore == null)
{
Expand All @@ -51,7 +55,19 @@ public DefaultIdSiteUrlBuilder(IInternalDataStore internalDataStore, string appl
throw new ArgumentNullException(nameof(applicationHref));
}

if (jtiProvider == null)
{
throw new ArgumentNullException(nameof(jtiProvider));
}

if (clock == null)
{
throw new ArgumentNullException(nameof(clock));
}

this.internalDataStore = internalDataStore;
this.jtiProvider = jtiProvider;
this.clock = clock;
this.applicationHref = applicationHref;
this.ssoEndpoint = GetBaseUrl(applicationHref) + "/sso";
}
Expand Down Expand Up @@ -144,17 +160,17 @@ string IIdSiteUrlBuilder.Build()
throw new ApplicationException($"{nameof(this.callbackUri)} cannot be null or empty.");
}

var jti = Guid.NewGuid().ToString();
var now = DateTimeOffset.UtcNow;
var jti = this.jtiProvider.NewJti();
var apiKey = this.internalDataStore.ApiKey;

IJwtBuilder jwtBuilder = new DefaultJwtBuilder(this.internalDataStore.Serializer);
jwtBuilder
.SetId(jti)
.SetIssuedAt(DateTimeOffset.Now)
.SetIssuedAt(this.clock.Now)
.SetIssuer(apiKey.GetId())
.SetSubject(this.applicationHref)
.SetClaim(IdSiteClaims.RedirectUri, this.callbackUri);
.SetClaim(IdSiteClaims.RedirectUri, this.callbackUri)
.SignWith(apiKey.GetSecret(), Encoding.UTF8);

if (!string.IsNullOrEmpty(this.path))
{
Expand All @@ -171,12 +187,12 @@ string IIdSiteUrlBuilder.Build()
jwtBuilder.SetClaim(IdSiteClaims.OrganizationNameKey, this.organizationNameKey);
}

if (this.useSubdomain.HasValue)
if (this.useSubdomain != null)
{
jwtBuilder.SetClaim(IdSiteClaims.UseSubdomain, this.useSubdomain.Value);
}

if (this.showOrganizationField.HasValue)
if (this.showOrganizationField != null)
{
jwtBuilder.SetClaim(IdSiteClaims.ShowOrganizationField, this.showOrganizationField.Value);
}
Expand Down
33 changes: 33 additions & 0 deletions Stormpath.SDK/Stormpath.SDK/Impl/IdSite/IIdSiteJtiProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// <copyright file="IIdSiteJtiProvider.cs" company="Stormpath, Inc.">
// Copyright (c) 2015 Stormpath, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace Stormpath.SDK.Impl.IdSite
{
/// <summary>
/// Represents a JWT ID (jti) provider used for ID Site.
/// </summary>
/// <remarks>
/// This is abstracted so it can be injectable during testing.
/// </remarks>
internal interface IIdSiteJtiProvider
{
/// <summary>
/// Generates a new JWT ID (jti) string.
/// </summary>
/// <returns>A JWT ID string.</returns>
string NewJti();
}
}
25 changes: 25 additions & 0 deletions Stormpath.SDK/Stormpath.SDK/Impl/Utility/DefaultClock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// <copyright file="DefaultClock.cs" company="Stormpath, Inc.">
// Copyright (c) 2015 Stormpath, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;

namespace Stormpath.SDK.Impl.Utility
{
internal sealed class DefaultClock : IClock
{
DateTimeOffset IClock.Now => DateTimeOffset.Now;
}
}
Loading

0 comments on commit 1d79db0

Please sign in to comment.