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 { private readonly ApplicationFactory _factory; public RealmApisTests(ApplicationFactory factory) { _factory = factory; using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); 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); var response = await client.PostAsync("/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(r => r.Id == inputId && r.Slug == slug && r.Name == name), // Arg.Any()); } else { Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var problemDetails = await response.Content.ReadFromJsonAsync( TestContext.Current.CancellationToken); Assert.Contains(problemDetails!.Errors, e => e.Key == fieldName); // await factory.RealmService.DidNotReceive().Create( // Arg.Any(), // Arg.Any()); } } [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("/realms/foo/.well-known/openid-configuration", TestContext.Current.CancellationToken); // verify var result = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(result); JsonObjectAssert.Equal("http://localhost/realms/foo/openid-connect/auth", result, "authorization_endpoint"); JsonObjectAssert.Equal("http://localhost/realms/foo", result, "issuer"); JsonObjectAssert.Equal("http://localhost/realms/foo/openid-connect/token", result, "token_endpoint"); JsonObjectAssert.Equal("http://localhost/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/bar/.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(); using var rsa = RSA.Create(2048); RSAParameters parameters = rsa.ExportParameters(includePrivateParameters: false); Key key = new() { Id = Guid.NewGuid(), CreatedAt = DateTime.UtcNow, }; key.SetPrivateKey(encryptionService, rsa.ExportPkcs8PrivateKey()); await ScopedContextAsync(async db => { db.Realms.Add(new Realm() { Slug = "foo", Name = "Foo", Keys = [ key ]}); await db.SaveChangesAsync(TestContext.Current.CancellationToken); }); // act var client = _factory.CreateClient(); var response = await client.GetAsync("/realms/foo/openid-connect/jwks", TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.OK, response.StatusCode); JsonObject? payload = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); Assert.NotNull(payload); JsonObjectAssert.Equal(key.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 action ) { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await action(db); } }