Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions OrderCloud.Catalyst.TestApi/Controllers/DemoController.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using OrderCloud.SDK;
using Stripe;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using OrderCloud.SDK;
using OrderCloud.Catalyst;
using RequiredAttribute = System.ComponentModel.DataAnnotations.RequiredAttribute;

namespace OrderCloud.Catalyst.TestApi
{
[Route("demo")]
public class DemoController : CatalystController
{
private readonly RequestAuthenticationService _tokenProvider;
private readonly IRequestAuthenticationService _auth;
private readonly IOrderCloudClient _oc;

public DemoController(RequestAuthenticationService tokenProvider, IOrderCloudClient oc)
public DemoController(IRequestAuthenticationService auth, IOrderCloudClient oc)
{
_tokenProvider = tokenProvider;
_auth = auth;
_oc = oc;
}

Expand All @@ -28,7 +28,20 @@ public object Shop() {
return "hello shopper!";
}

[HttpGet("admin"), OrderCloudUserAuth(ApiRole.OrderAdmin)]
[HttpGet("simpleuserinfo"), OrderCloudUserInfoAuth]
public object SimpleUserInfo()
{
return "hello userinfo!";
}

[HttpGet("customuserinfo"), OrderCloudUserInfoAuth("CustomRole")]
public object CustomUserInfo()
{
return "hello custom userinfo!";
}


[HttpGet("admin"), OrderCloudUserAuth(ApiRole.OrderAdmin)]
public object Admin() => "hello admin!";

[HttpGet("either"), OrderCloudUserAuth("Shopper", "OrderAdmin")]
Expand Down Expand Up @@ -94,21 +107,49 @@ public SimplifiedUser Username()
};
}

[HttpGet("username"), OrderCloudUserAuth]
[HttpGet("userinfocontext"), OrderCloudUserInfoAuth]
public SimplifiedUser GetUserInfoContext()
{
return new SimplifiedUser()
{
AvailableRoles = UserInfoContext.Roles.ToList(),
Username = UserInfoContext.Username
};
}

[HttpPost("userinfocontext/{token}")]
public async Task<SimplifiedUser> SetUserInfoContext(string token)
{
var user = await _auth.VerifyUserInfoTokenAsync(token);
return new SimplifiedUser()
{
AvailableRoles = user.Roles.ToList(),
Username = user.Username
};
}

[HttpGet("username"), OrderCloudUserAuth]
public string GetUserName()
{
Thread.Sleep(1000); // pause for 1 sec
return UserContext.Username;
}

[HttpPost("usercontext/{token}")]
[HttpGet("userinfousername"), OrderCloudUserInfoAuth]
public string GetUserInfoUserName()
{
Thread.Sleep(1000); // pause for 1 sec
return UserInfoContext.Username;
}

[HttpPost("usercontext/{token}")]
public async Task<SimplifiedUser> SetUserContext(string token)
{
var opts = new OrderCloudUserAuthOptions()
{
AnyClientIDCanAccess = true
};
var user = await _tokenProvider.VerifyTokenAsync(token, opts);
var user = await _auth.VerifyTokenAsync(token, opts);
return new SimplifiedUser() {
AvailableRoles = user.Roles.ToList(),
Username = user.Username,
Expand Down
8 changes: 3 additions & 5 deletions OrderCloud.Catalyst.TestApi/Controllers/WebhookController.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using OrderCloud.Catalyst;
using Microsoft.AspNetCore.Mvc;
using OrderCloud.SDK;
using System.Threading.Tasks;

namespace OrderCloud.Catalyst.TestApi
{
public class WebhookController : CatalystController
{
private RequestAuthenticationService _service;
private IRequestAuthenticationService _service;
private TestSettings _settings;

public WebhookController(RequestAuthenticationService service, TestSettings settings)
public WebhookController(IRequestAuthenticationService service, TestSettings settings)
{
_service = service;
_settings = settings;
Expand Down
24 changes: 18 additions & 6 deletions OrderCloud.Catalyst.TestApi/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using OrderCloud.SDK;
using NSubstitute;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json.Converters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;
using NSubstitute;
using OrderCloud.SDK;
using OrderCloud.Catalyst;
using System.Threading.Tasks;

namespace OrderCloud.Catalyst.TestApi
{
Expand Down Expand Up @@ -45,6 +47,7 @@ public virtual void ConfigureServices(IServiceCollection services)
builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }));
services
.AddOrderCloudUserAuth(opts => opts.AddValidClientIDs(UnitTestClientID))
.AddOrderCloudUserInfoAuth()
.AddOrderCloudWebhookAuth(opts => opts.HashKey = _settings.OrderCloudSettings.WebhookHashKey)
.AddSingleton<ISimpleCache, LazyCacheService>() // Replace LazyCacheService with RedisService if you have multiple server instances.
.AddSingleton<IOrderCloudClient>(new OrderCloudClient(new OrderCloudClientConfig()
Expand Down Expand Up @@ -106,7 +109,16 @@ public override void ConfigureServices(IServiceCollection services)
AuthUrl = "mockdomain.com",
});
oc.Me.GetAsync(Arg.Any<string>()).Returns(new MeUser { Username = "joe", Active = true, AvailableRoles = new[] { "Shopper" } });
services.AddSingleton(oc);


oc
.GetPublicKeyAsync(Arg.Is<string>(k => k == TestRsaKeyProvider.AllowedKid))
.Returns(Task.FromResult(TestRsaKeyProvider.ToOrderCloudPublicKey(TestRsaKeyProvider.AllowedRsa)));
oc
.GetPublicKeyAsync(Arg.Is<string>(k => k == TestRsaKeyProvider.DeniedKid))
.Returns(Task.FromResult(TestRsaKeyProvider.ToOrderCloudPublicKey(TestRsaKeyProvider.DeniedRsa)));

services.AddSingleton(oc);
}

public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Expand Down
213 changes: 213 additions & 0 deletions OrderCloud.Catalyst.Tests/ApiIntegrationTests/UserInfoAuthTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
using AutoFixture;
using AutoFixture.NUnit3;
using FluentAssertions;
using Flurl.Http;
using NSubstitute;
using NUnit.Framework;
using OrderCloud.Catalyst;
using OrderCloud.Catalyst.TestApi;
using OrderCloud.SDK;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace OrderCloud.Catalyst.Tests
{
[TestFixture]
public class UserInfoAuthTests
{

[Test]
public async Task should_deny_access_without_oc_token()
{
var resp = await TestFramework.Client
.Request("demo/simpleuserinfo")
.GetAsync();

await resp.ShouldHaveFirstApiError("InvalidToken", 401, "Access token is invalid or expired.");
}

[Test]
public async Task can_auth_with_oc_token()
{
var token = FakeUserInfoToken.Create();
var result = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/simpleuserinfo")
.GetStringAsync();

result.Should().Be("\"hello userinfo!\"");
}

[Test]
public async Task should_succeed_with_custom_role()
{
var token = FakeUserInfoToken.Create(new List<string> { "CustomRole" });
var request = TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/customuserinfo");

var result = await request.GetStringAsync();

result.Should().Be("\"hello custom userinfo!\"");
}

[Test]
public async Task should_succeed_with_with_full_access()
{
var token = FakeUserInfoToken.Create(new List<string> { "FullAccess" });
var request = TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/customuserinfo");

var result = await request.GetStringAsync();

result.Should().Be("\"hello custom userinfo!\"");
}

[Test]
public async Task should_error_without_custom_role()
{
var token = FakeUserInfoToken.Create();
var result = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/customuserinfo")
.GetAsync();

Assert.AreEqual(403, result.StatusCode);
}

[Test]
public async Task can_get_user_info_context_from_auth()
{
var fixture = new Fixture();
var username = fixture.Create<string>();
var token = FakeUserInfoToken.Create(new List<string> { "Shopper" }, username: username);

var result = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/userinfocontext")
.GetJsonAsync<SimplifiedUser>();

Assert.AreEqual(username, result.Username);
Assert.AreEqual("Shopper", result.AvailableRoles[0]);
}

[Test]
public async Task can_get_user_context_from_setting_it()
{
var fixture = new Fixture();
var username = fixture.Create<string>();
var token = FakeUserInfoToken.Create(new List<string> { "Shopper" }, username: username);

var result = await TestFramework.Client
.Request($"demo/userinfocontext/{token}")
.PostAsync()
.ReceiveJson<SimplifiedUser>();

Assert.AreEqual(username, result.Username);
Assert.AreEqual("Shopper", result.AvailableRoles[0]);
}

[Test]
public async Task should_succeed_if_now_is_between_expiry_and_nvb()
{
var token = FakeUserInfoToken.Create(
roles: new List<string> { "Shopper" },
expiresUTC: DateTime.UtcNow + TimeSpan.FromHours(1),
notValidBeforeUTC: DateTime.UtcNow - TimeSpan.FromHours(1)
);

var resp = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/simpleuserinfo")
.GetStringAsync();

resp.Should().Be("\"hello userinfo!\"");
}

[Test]
public async Task should_deny_access_if_nvb_is_wrong()
{
var fixture = new Fixture();

var token = FakeUserInfoToken.Create(
roles: new List<string> { "Shopper" },
expiresUTC: DateTime.UtcNow + TimeSpan.FromHours(2),
notValidBeforeUTC: DateTime.UtcNow + TimeSpan.FromHours(1)
);

var resp = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/simpleuserinfo")
.GetAsync();

await resp.ShouldHaveFirstApiError("InvalidToken", 401, "Access token is invalid or expired.");
}

[Test]
public async Task should_deny_access_if_past_expiry()
{
var fixture = new Fixture();

var token = FakeUserInfoToken.Create(
roles: new List<string> { "Shopper" },
expiresUTC: DateTime.UtcNow,
notValidBeforeUTC: DateTime.UtcNow - TimeSpan.FromHours(1)
);

var resp = await TestFramework.Client
.WithOAuthBearerToken(token)
.Request("demo/simpleuserinfo")
.GetAsync();

await resp.ShouldHaveFirstApiError("InvalidToken", 401, "Access token is invalid or expired.");
}

[Test]
public async Task two_requests_with_the_same_kid_should_verify_both_tokens()
{
var fixture = new Fixture();
var keyID = fixture.Create<string>();

var token1 = FakeUserInfoToken.Create(new List<string> { "Shopper" }, keyID: keyID);
var token2 = token1 + "makethisinvalid";

var response1 = await TestFramework.Client.WithOAuthBearerToken(token1).Request("demo/simpleuserinfo").GetAsync();
var response2 = await TestFramework.Client.WithOAuthBearerToken(token2).Request("demo/simpleuserinfo").GetAsync();

await response2.ShouldHaveFirstApiError("InvalidToken", 401, "Access token is invalid or expired.");
}



[Test]
public async Task user_auth_provider_handles_mulitple_concurrent_requests()
{
var fixture = new Fixture();
var requestCount = 10;
var usernames = new List<string>();
var requests = new List<Task<string>>();

foreach (var i in Enumerable.Range(0, requestCount))
{
var username = fixture.Create<string>();
usernames.Add(username);
var token = FakeUserInfoToken.Create(username: username);
var request = TestFramework.Client.WithOAuthBearerToken(token).Request("demo/userinfousername").GetStringAsync();
requests.Add(request);
}

var results = await Task.WhenAll(requests);

foreach (var i in Enumerable.Range(0, requestCount))
{
Assert.AreEqual("\"" + usernames[i] + "\"", results[i]);
}
}
}
}
Loading
Loading