Skip to content

Commit

Permalink
Dynamic API client for tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gunndabad committed Nov 15, 2023
1 parent 4abe3a0 commit 87d9f6d
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
return Task.FromResult(AuthenticateResult.Fail($"No client found with specified API key."));
}

var principal = CreatePrincipal(client);
var principal = CreatePrincipal(client.ClientId);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

LogContext.PushProperty("ClientId", client.ClientId);

return Task.FromResult(AuthenticateResult.Success(ticket));
}

private static ClaimsPrincipal CreatePrincipal(ApiClient client)
public static ClaimsPrincipal CreatePrincipal(string clientId)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, client.ClientId)
new Claim(ClaimTypes.Name, clientId)
});

return new ClaimsPrincipal(identity);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
{
"Platform": "Local",
"ApiClients": {
"tests": {
"ApiKey": [ "tests" ]
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Error",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Security.Cryptography;
using JustEat.HttpClientInterception;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.Tests.Infrastructure.Security;
using TeachingRecordSystem.Core.Dqt;
using TeachingRecordSystem.Core.Services.AccessYourQualifications;
using TeachingRecordSystem.Core.Services.Certificates;
Expand Down Expand Up @@ -41,9 +44,18 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
DbHelper.ConfigureDbServices(services, context.Configuration.GetRequiredConnectionString("DefaultConnection"));

// Replace ApiKeyAuthenticationHandler with a mechanism we can control from tests
//services.AddAuthentication()
// .AddScheme<TestAuthenticationOptions, TestAuthenticationHandler>(ApiKeyAuthenticationHandler.AuthenticationScheme, options => { });
services.Configure<AuthenticationOptions>(options =>
{
options.SchemeMap[ApiKeyAuthenticationHandler.AuthenticationScheme].HandlerType = typeof(TestAuthenticationHandler);
});

// Add controllers defined in this test assembly
services.AddMvc().AddApplicationPart(typeof(ApiFixture).Assembly);

services.AddSingleton<CurrentApiClientProvider>();
services.AddTestScoped<IClock>(tss => tss.Clock);
services.AddTestScoped<IDataverseAdapter>(tss => tss.DataverseAdapterMock.Object);
services.AddTestScoped<IGetAnIdentityApiClient>(tss => tss.GetAnIdentityApiClientMock.Object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using TeachingRecordSystem.Api.Infrastructure.Json;
using TeachingRecordSystem.Api.Tests.Infrastructure.Security;
using TeachingRecordSystem.Core.DataStore.Postgres;
using TeachingRecordSystem.Core.Dqt;
using TeachingRecordSystem.Core.Services.Certificates;
Expand All @@ -20,11 +21,11 @@ protected ApiTestBase(ApiFixture apiFixture)
{
ApiFixture = apiFixture;
_testServices = TestScopedServices.Reset();
SetCurrentApiClient("tests");

{
var key = apiFixture.Services.GetRequiredService<IConfiguration>()["ApiClients:tests:ApiKey:0"];
HttpClientWithApiKey = apiFixture.CreateClient();
HttpClientWithApiKey.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", key);
HttpClientWithApiKey.DefaultRequestHeaders.Add("X-Use-CurrentClientIdProvider", "true"); // Signal for TestAuthenticationHandler to run
}
}

Expand Down Expand Up @@ -79,6 +80,12 @@ public HttpClient GetHttpClientWithIdentityAccessToken(string trn, string scope
return httpClient;
}

protected void SetCurrentApiClient(string clientId)
{
var currentUserProvider = ApiFixture.Services.GetRequiredService<CurrentApiClientProvider>();
currentUserProvider.CurrentApiClientId = clientId;
}

public virtual async Task<T> WithDbContext<T>(Func<TrsDbContext, Task<T>> action)
{
var dbContextFactory = ApiFixture.Services.GetRequiredService<IDbContextFactory<TrsDbContext>>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using TeachingRecordSystem.Api.Infrastructure.Security;

namespace TeachingRecordSystem.Api.Tests.Infrastructure.Security;

public class TestAuthenticationHandler : AuthenticationHandler<TestAuthenticationOptions>
{
private readonly CurrentApiClientProvider _currentApiClientProvider;

public TestAuthenticationHandler(
CurrentApiClientProvider currentApiClientProvider,
IOptionsMonitor<TestAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) :
base(options, logger, encoder, clock)
{
_currentApiClientProvider = currentApiClientProvider;
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("X-Use-CurrentClientIdProvider", out var useCurrentClientIdProvider) ||
useCurrentClientIdProvider != "true")
{
return Task.FromResult(AuthenticateResult.NoResult());
}

var currentApiClientId = _currentApiClientProvider.CurrentApiClientId;

if (currentApiClientId is not null)
{
var principal = ApiKeyAuthenticationHandler.CreatePrincipal(currentApiClientId);

var ticket = new AuthenticationTicket(principal, Scheme.Name);

return Task.FromResult(AuthenticateResult.Success(ticket));
}
else
{
return Task.FromResult(AuthenticateResult.NoResult());
}
}
}

public class TestAuthenticationOptions : AuthenticationSchemeOptions { }

public class CurrentApiClientProvider
{
private readonly AsyncLocal<string> _currentApiClientId = new();

[DisallowNull]
public string? CurrentApiClientId
{
get => _currentApiClientId.Value;
set => _currentApiClientId.Value = value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"parallelizeTestCollections": false
}

0 comments on commit 87d9f6d

Please sign in to comment.