The EncryptionService now loads a set of keys and uses the active one to encrypt and selects key based on keyid during decryption. Introduced EncryptedValue to hold keyId and encrypted data. (There are no intermeddiate keys yet)
169 lines
No EOL
6.3 KiB
C#
169 lines
No EOL
6.3 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json.Nodes;
|
|
using IdentityShroud.Core;
|
|
using IdentityShroud.Core.Contracts;
|
|
using IdentityShroud.Core.Model;
|
|
using IdentityShroud.Core.Tests.Fixtures;
|
|
using IdentityShroud.TestUtils.Asserts;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace IdentityShroud.Api.Tests.Apis;
|
|
|
|
public class RealmApisTests : IClassFixture<ApplicationFactory>
|
|
{
|
|
private readonly ApplicationFactory _factory;
|
|
|
|
public RealmApisTests(ApplicationFactory factory)
|
|
{
|
|
_factory = factory;
|
|
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<Db>();
|
|
if (!db.Database.EnsureCreated())
|
|
{
|
|
db.Database.ExecuteSqlRaw("TRUNCATE realm CASCADE;");
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null, null, null, false, "Name")]
|
|
[InlineData(null, null, "Foo", true, "")]
|
|
[InlineData(null, null, "", false, "Name")]
|
|
[InlineData(null, "foo", "Foo", true, "")]
|
|
[InlineData(null, "f/oo", "Foo", false, "Slug")]
|
|
[InlineData(null, "", "Foo", false, "Slug")]
|
|
[InlineData("0814934a-efe2-4784-ba84-a184c0b9de9e", "foo", "Foo", true, "")]
|
|
[InlineData("00000000-0000-0000-0000-000000000000", "foo", "Foo", false, "Id")]
|
|
public async Task Create(string? id, string? slug, string? name, bool succeeds, string fieldName)
|
|
{
|
|
var client = _factory.CreateClient();
|
|
|
|
Guid? inputId = id is null ? (Guid?)null : new Guid(id);
|
|
|
|
// act
|
|
var response = await client.PostAsync("/api/v1/realms", JsonContent.Create(new
|
|
{
|
|
Id = inputId,
|
|
Slug = slug,
|
|
Name = name,
|
|
}),
|
|
TestContext.Current.CancellationToken);
|
|
#if DEBUG
|
|
string contents = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
|
#endif
|
|
|
|
if (succeeds)
|
|
{
|
|
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
|
|
// await factory.RealmService.Received(1).Create(
|
|
// Arg.Is<RealmCreateRequest>(r => r.Id == inputId && r.Slug == slug && r.Name == name),
|
|
// Arg.Any<CancellationToken>());
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
var problemDetails =
|
|
await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(
|
|
TestContext.Current.CancellationToken);
|
|
|
|
Assert.Contains(problemDetails!.Errors, e => e.Key == fieldName);
|
|
// await factory.RealmService.DidNotReceive().Create(
|
|
// Arg.Any<RealmCreateRequest>(),
|
|
// Arg.Any<CancellationToken>());
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetOpenIdConfiguration_Success()
|
|
{
|
|
// setup
|
|
await ScopedContextAsync(async db =>
|
|
{
|
|
db.Realms.Add(new Realm() { Slug = "foo", Name = "Foo" });
|
|
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
|
});
|
|
|
|
// act
|
|
var client = _factory.CreateClient();
|
|
var response = await client.GetAsync("auth/realms/foo/.well-known/openid-configuration",
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// verify
|
|
#if DEBUG
|
|
string contents = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
|
#endif
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<JsonObject>(TestContext.Current.CancellationToken);
|
|
Assert.NotNull(result);
|
|
JsonObjectAssert.Equal("http://localhost/auth/realms/foo/openid-connect/auth", result, "authorization_endpoint");
|
|
JsonObjectAssert.Equal("http://localhost/auth/realms/foo", result, "issuer");
|
|
JsonObjectAssert.Equal("http://localhost/auth/realms/foo/openid-connect/token", result, "token_endpoint");
|
|
JsonObjectAssert.Equal("http://localhost/auth/realms/foo/openid-connect/jwks", result, "jwks_uri");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("")]
|
|
[InlineData("bar")]
|
|
public async Task GetOpenIdConfiguration_NotFound(string slug)
|
|
{
|
|
// act
|
|
var client = _factory.CreateClient();
|
|
var response = await client.GetAsync($"/realms/{slug}/.well-known/openid-configuration",
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// verify
|
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetJwks()
|
|
{
|
|
// setup
|
|
IEncryptionService encryptionService = _factory.Services.GetRequiredService<IEncryptionService>();
|
|
|
|
using var rsa = RSA.Create(2048);
|
|
RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false);
|
|
|
|
RealmKey realmKey = new()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
KeyType = "RSA",
|
|
Key = encryptionService.Encrypt(rsa.ExportPkcs8PrivateKey()),
|
|
CreatedAt = DateTime.UtcNow,
|
|
};
|
|
|
|
await ScopedContextAsync(async db =>
|
|
{
|
|
db.Realms.Add(new Realm() { Slug = "foo", Name = "Foo", Keys = [ realmKey ]});
|
|
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
|
});
|
|
|
|
// act
|
|
var client = _factory.CreateClient();
|
|
var response = await client.GetAsync("/auth/realms/foo/openid-connect/jwks",
|
|
TestContext.Current.CancellationToken);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
JsonObject? payload = await response.Content.ReadFromJsonAsync<JsonObject>(TestContext.Current.CancellationToken);
|
|
|
|
Assert.NotNull(payload);
|
|
JsonObjectAssert.Equal(realmKey.Id.ToString(), payload, "keys[0].kid");
|
|
JsonObjectAssert.Equal(WebEncoders.Base64UrlEncode(parameters.Modulus!), payload, "keys[0].n");
|
|
JsonObjectAssert.Equal(WebEncoders.Base64UrlEncode(parameters.Exponent!), payload, "keys[0].e");
|
|
}
|
|
|
|
private async Task ScopedContextAsync(
|
|
Func<Db, Task> action
|
|
)
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<Db>();
|
|
await action(db);
|
|
}
|
|
} |