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
9 changes: 9 additions & 0 deletions src/Ignis.Auth/AuthConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Ignis.Auth;

public static class AuthConstants
{
/// <summary>
/// Authentication scheme used for the user session cookie during the authorization code flow.
/// </summary>
public const string SessionScheme = "IgnisAuth.Session";
}
6 changes: 6 additions & 0 deletions tests/Ignis.Api.Tests/AuthConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public async Task TokenEndpoint_Available_WhenAuthEnabled()
["AuthSettings__Clients__0__ClientId"] = "config-client",
["AuthSettings__Clients__0__ClientSecret"] = "config-secret",
["AuthSettings__Clients__0__DisplayName"] = "Config Client",
["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["StoreSettings__ConnectionString"] = _connectionString,
};
SetEnvVars(envVars);
Expand All @@ -139,6 +140,7 @@ public async Task TokenEndpoint_Available_WhenAuthEnabled()
["AuthSettings:Clients:0:ClientId"] = "config-client",
["AuthSettings:Clients:0:ClientSecret"] = "config-secret",
["AuthSettings:Clients:0:DisplayName"] = "Config Client",
["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
});
using var client = factory.CreateClient();

Expand Down Expand Up @@ -174,6 +176,7 @@ public async Task TokenEndpoint_Works_WithCertificatesInProduction()
["AuthSettings__Clients__0__ClientId"] = "cert-client",
["AuthSettings__Clients__0__ClientSecret"] = "cert-secret",
["AuthSettings__Clients__0__DisplayName"] = "Cert Client",
["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["AuthSettings__Certificates__SigningCertificatePath"] = signingCertPath,
["AuthSettings__Certificates__SigningCertificatePassword"] = signingCertPassword,
["AuthSettings__Certificates__EncryptionCertificatePath"] = encryptionCertPath,
Expand All @@ -194,6 +197,7 @@ public async Task TokenEndpoint_Works_WithCertificatesInProduction()
["AuthSettings:Clients:0:ClientId"] = "cert-client",
["AuthSettings:Clients:0:ClientSecret"] = "cert-secret",
["AuthSettings:Clients:0:DisplayName"] = "Cert Client",
["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
["AuthSettings:Certificates:SigningCertificatePath"] = signingCertPath,
["AuthSettings:Certificates:SigningCertificatePassword"] = signingCertPassword,
["AuthSettings:Certificates:EncryptionCertificatePath"] = encryptionCertPath,
Expand Down Expand Up @@ -235,6 +239,7 @@ public void Startup_Fails_WhenCertificatesMissing_InProduction()
["AuthSettings__ConnectionString"] = _connectionString,
["AuthSettings__Clients__0__ClientId"] = "cert-client",
["AuthSettings__Clients__0__ClientSecret"] = "cert-secret",
["AuthSettings__Clients__0__AllowedGrantTypes__0"] = "client_credentials",
["StoreSettings__ConnectionString"] = _connectionString,
};
SetEnvVars(envVars);
Expand All @@ -252,6 +257,7 @@ public void Startup_Fails_WhenCertificatesMissing_InProduction()
["AuthSettings:ConnectionString"] = _connectionString,
["AuthSettings:Clients:0:ClientId"] = "cert-client",
["AuthSettings:Clients:0:ClientSecret"] = "cert-secret",
["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
}, environment: "Production");
factory.CreateClient();
};
Expand Down
20 changes: 14 additions & 6 deletions tests/Ignis.Api.Tests/AuthorizationControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ namespace Ignis.Api.Tests;
[Collection("IntegrationTests")]
public class AuthorizationControllerTests : IClassFixture<IntegrationFixture>
{
private readonly HttpClient _client;
private readonly IntegrationFixture _fixture;

public AuthorizationControllerTests(IntegrationFixture fixture)
{
_client = fixture.Factory.CreateClient();
_fixture = fixture;
}

private static CancellationToken CT => TestContext.Current.CancellationToken;

[Fact]
public async Task Token_WithValidClientCredentials_ReturnsAccessToken()
{
var response = await _client.PostAsync("/connect/token",
using var client = _fixture.Factory.CreateClient();

var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
Expand All @@ -38,7 +40,9 @@ public async Task Token_WithValidClientCredentials_ReturnsAccessToken()
[Fact]
public async Task Token_WithInvalidClient_ReturnsUnauthorized()
{
var response = await _client.PostAsync("/connect/token",
using var client = _fixture.Factory.CreateClient();

var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
Expand All @@ -52,7 +56,9 @@ public async Task Token_WithInvalidClient_ReturnsUnauthorized()
[Fact]
public async Task Token_WithWrongSecret_ReturnsUnauthorized()
{
var response = await _client.PostAsync("/connect/token",
using var client = _fixture.Factory.CreateClient();

var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
Expand All @@ -66,7 +72,9 @@ public async Task Token_WithWrongSecret_ReturnsUnauthorized()
[Fact]
public async Task Token_WithUnsupportedGrantType_ReturnsBadRequest()
{
var response = await _client.PostAsync("/connect/token",
using var client = _fixture.Factory.CreateClient();

var response = await client.PostAsync("/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
Expand Down
8 changes: 8 additions & 0 deletions tests/Ignis.Api.Tests/IgnisApiFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Ignis.Api.Tests;

Expand Down Expand Up @@ -28,9 +29,16 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
["AuthSettings:Clients:0:ClientId"] = "test-client",
["AuthSettings:Clients:0:ClientSecret"] = "test-secret",
["AuthSettings:Clients:0:DisplayName"] = "Test Client",
["AuthSettings:Clients:0:AllowedGrantTypes:0"] = "client_credentials",
["AuthSettings:Clients:0:RedirectUris:0"] = "http://localhost/callback",
});
});

builder.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, TestLoginStartupFilter>();
});

builder.UseEnvironment("Development");
}
}
23 changes: 17 additions & 6 deletions tests/Ignis.Api.Tests/IntegrationFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ private static string BuildConnectionString(string raw)
return mongoUrl.ToString();
}

private static readonly string[] EnvVarKeys =
[
"StoreSettings__ConnectionString",
"AuthSettings__Enabled",
"AuthSettings__ConnectionString",
"AuthSettings__Clients__0__ClientId",
"AuthSettings__Clients__0__ClientSecret",
"AuthSettings__Clients__0__DisplayName",
"AuthSettings__Clients__0__AllowedGrantTypes__0",
"AuthSettings__Clients__0__AllowedGrantTypes__1",
"AuthSettings__Clients__0__RedirectUris__0",
];

public async ValueTask InitializeAsync()
{
await _mongo.StartAsync();
Expand All @@ -41,18 +54,16 @@ public async ValueTask InitializeAsync()
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientId", "test-client");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientSecret", "test-secret");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__DisplayName", "Test Client");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__AllowedGrantTypes__0", "client_credentials");
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__RedirectUris__0", "http://localhost/callback");

Factory = new IgnisApiFactory(connectionString);
}

public async ValueTask DisposeAsync()
{
Environment.SetEnvironmentVariable("StoreSettings__ConnectionString", null);
Environment.SetEnvironmentVariable("AuthSettings__Enabled", null);
Environment.SetEnvironmentVariable("AuthSettings__ConnectionString", null);
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientId", null);
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__ClientSecret", null);
Environment.SetEnvironmentVariable("AuthSettings__Clients__0__DisplayName", null);
foreach (var key in EnvVarKeys)
Environment.SetEnvironmentVariable(key, null);
Factory.Dispose();
await _mongo.DisposeAsync();
}
Expand Down
43 changes: 43 additions & 0 deletions tests/Ignis.Api.Tests/TestLoginStartupFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Security.Claims;

using Ignis.Auth;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

namespace Ignis.Api.Tests;

/// <summary>
/// Adds a <c>/test-login</c> endpoint that signs in as a test user
/// using the <see cref="AuthConstants.SessionScheme"/> cookie scheme.
/// </summary>
internal sealed class TestLoginStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.Use(async (context, nextMiddleware) =>
{
if (context.Request.Path != "/test-login")
{
await nextMiddleware();
return;
}

var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, "test-user-id"),
new(ClaimTypes.Name, "Test User"),
};
var identity = new ClaimsIdentity(claims, AuthConstants.SessionScheme);
await context.SignInAsync(
AuthConstants.SessionScheme, new ClaimsPrincipal(identity));
context.Response.StatusCode = StatusCodes.Status200OK;
});
next(app);
};
}
}