diff --git a/Directory.Packages.props b/Directory.Packages.props index e67d992..4977bbd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -87,6 +87,13 @@ + + + diff --git a/DodoSSH.slnx b/DodoSSH.slnx index 5211eb3..d685d4c 100644 --- a/DodoSSH.slnx +++ b/DodoSSH.slnx @@ -19,6 +19,7 @@ + diff --git a/src/DodoSSH.Api/appsettings.json b/src/DodoSSH.Api/appsettings.json index b0e8796..a40458d 100644 --- a/src/DodoSSH.Api/appsettings.json +++ b/src/DodoSSH.Api/appsettings.json @@ -7,14 +7,10 @@ } }, "AllowedHosts": "*", - "ConnectionStrings": { - "Postgres": "" - }, "Server": { "PublicBaseUrl": "http://localhost:5233" }, "Oidc": { - "Authority": "", "Audience": "dodossh-api", "ClientId": "dodossh-desktop", "LoopbackRedirectPattern": "http://127.0.0.1:*/callback", diff --git a/tests/DodoSSH.Api.Tests/ApiFixture.cs b/tests/DodoSSH.Api.Tests/ApiFixture.cs new file mode 100644 index 0000000..ccf1944 --- /dev/null +++ b/tests/DodoSSH.Api.Tests/ApiFixture.cs @@ -0,0 +1,112 @@ +using System.Net.Http.Headers; +using DodoSSH.Infrastructure; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Testcontainers.PostgreSql; +using Xunit; + +namespace DodoSSH.Api.Tests; + +/// +/// Hosts the API in-process against a real PostgreSQL container and a stubbed identity provider. +/// +/// +/// One container and one host per assembly. Tests therefore use distinct users and vaults rather +/// than assuming an empty database. +/// +public sealed class ApiFixture : WebApplicationFactory, IAsyncLifetime +{ + private readonly PostgreSqlContainer container = new PostgreSqlBuilder("postgres:18-alpine") + .WithDatabase("dodossh") + .WithUsername("postgres") + .WithPassword("test") + .Build(); + + /// The stubbed identity provider. + public StubIdentityProvider IdentityProvider { get; } = new(); + + /// + public async ValueTask InitializeAsync() + { + await container.StartAsync(); + + await using var scope = Services.CreateAsyncScope(); + var database = scope.ServiceProvider.GetRequiredService(); + await database.Database.MigrateAsync(); + } + + /// + public override async ValueTask DisposeAsync() + { + await base.DisposeAsync(); + await container.DisposeAsync(); + IdentityProvider.Dispose(); + } + + /// + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + builder.UseEnvironment("Testing"); + + // Environment variables rather than ConfigureAppConfiguration. Under minimal hosting the + // application's own configuration sources are added inside Program.cs after this callback + // runs, so an appsettings.json value would win over anything registered here. Program.cs + // adds AddEnvironmentVariables("DODOSSH_") last, which makes these authoritative. + var settings = new Dictionary(StringComparer.Ordinal) + { + ["DODOSSH_ConnectionStrings__Postgres"] = container.GetConnectionString(), + ["DODOSSH_Server__PublicBaseUrl"] = "http://localhost", + ["DODOSSH_Oidc__Authority"] = IdentityProvider.Authority, + ["DODOSSH_Oidc__Audience"] = StubIdentityProvider.Audience, + ["DODOSSH_Oidc__ClientId"] = "dodossh-desktop", + + // The stub serves plaintext HTTP on a loopback port. + ["DODOSSH_Oidc__RequireHttpsMetadata"] = "false", + + ["DODOSSH_Relay__Enabled"] = "false", + + // Fixed so cursors stay valid for the lifetime of the test host. + ["DODOSSH_Sync__CursorSigningKey"] = Convert.ToBase64String(new byte[32]), + }; + + foreach (var (name, value) in settings) + { + Environment.SetEnvironmentVariable(name, value); + } + } + + /// Creates a client carrying a valid token for the given subject. + public HttpClient CreateClientFor(string subject, string? email = null) + { + var client = CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + "Bearer", + IdentityProvider.MintToken(subject, email)); + + return client; + } + + /// Creates a client carrying the supplied raw token. + public HttpClient CreateClientWithToken(string token) + { + var client = CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + return client; + } + + /// Opens a database scope for arranging state and asserting on it. + public AsyncServiceScope CreateScope() => Services.CreateAsyncScope(); +} + +/// Shares one host and container across every test class in the assembly. +[CollectionDefinition(Name)] +public sealed class ApiCollection : ICollectionFixture +{ + /// Collection name. + public const string Name = "api"; +} diff --git a/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj b/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj new file mode 100644 index 0000000..7a26fd2 --- /dev/null +++ b/tests/DodoSSH.Api.Tests/DodoSSH.Api.Tests.csproj @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs b/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs new file mode 100644 index 0000000..04ef1b6 --- /dev/null +++ b/tests/DodoSSH.Api.Tests/StubIdentityProvider.cs @@ -0,0 +1,171 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Cryptography; +using System.Text.Json; +using Microsoft.IdentityModel.Tokens; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; + +namespace DodoSSH.Api.Tests; + +/// +/// A stand-in identity provider: OIDC discovery, JWKS, and token minting. +/// +/// +/// Deliberately not a TestAuthHandler that short-circuits authentication. Tokens are signed +/// with a real RSA key and validated by the application's own JwtBearer pipeline, so issuer, +/// audience, signature, lifetime and claim handling are all genuinely exercised. Bypassing that +/// would hide precisely the claim-mapping mistakes that cause real authorization holes. +/// +public sealed class StubIdentityProvider : IDisposable +{ + private const string KeyId = "dodossh-test-key"; + + private readonly WireMockServer server; + private readonly RsaSecurityKey signingKey; + + public StubIdentityProvider() + { + var rsa = RSA.Create(2048); + signingKey = new RsaSecurityKey(rsa) { KeyId = KeyId }; + + server = WireMockServer.Start(); + Authority = server.Url!.TrimEnd('/'); + + StubDiscovery(); + StubJwks(); + } + + /// Issuer URL, matching what the tokens claim. + public string Authority { get; } + + /// Audience the API is configured to expect. + public static string Audience => "dodossh-api"; + + /// + /// Mints a signed access token. + /// + /// The sub claim — the stable user identifier. + /// Optional email claim. + /// Optional display name claim. + /// Override the audience, to test rejection. + /// Override the issuer, to test rejection. + /// Override expiry, to test rejection. + public string MintToken( + string subject, + string? email = null, + string? name = null, + string? audience = null, + string? issuer = null, + DateTime? expires = null) + { + var now = TimeProvider.System.GetUtcNow().UtcDateTime; + + var claims = new List + { + new("sub", subject), + }; + + if (email is not null) + { + claims.Add(new System.Security.Claims.Claim("email", email)); + } + + if (name is not null) + { + claims.Add(new System.Security.Claims.Claim("name", name)); + } + + var expiry = expires ?? now.AddMinutes(15); + + // The earlier of now-1min and exp-1min. A fixed now-1min would sit after the expiry of a + // deliberately-expired test token (rejected at construction), while exp-1min alone would + // put nbf in the future for a normal token and make every valid token unauthorized. + var notBefore = expiry < now ? expiry.AddMinutes(-1) : now.AddMinutes(-1); + + var token = new JwtSecurityToken( + issuer: issuer ?? Authority, + audience: audience ?? Audience, + claims: claims, + notBefore: notBefore, + expires: expiry, + signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256)); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + /// Mints a token signed by a different key, which must be rejected. + public string MintTokenWithForeignKey(string subject) + { + var foreign = new RsaSecurityKey(RSA.Create(2048)) { KeyId = KeyId }; + var now = TimeProvider.System.GetUtcNow().UtcDateTime; + + var token = new JwtSecurityToken( + issuer: Authority, + audience: Audience, + claims: [new System.Security.Claims.Claim("sub", subject)], + notBefore: now.AddMinutes(-1), + expires: now.AddMinutes(15), + signingCredentials: new SigningCredentials(foreign, SecurityAlgorithms.RsaSha256)); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + private void StubDiscovery() + { + var document = new Dictionary(StringComparer.Ordinal) + { + ["issuer"] = Authority, + ["jwks_uri"] = $"{Authority}/.well-known/jwks.json", + ["authorization_endpoint"] = $"{Authority}/connect/authorize", + ["token_endpoint"] = $"{Authority}/connect/token", + ["response_types_supported"] = new[] { "code" }, + ["subject_types_supported"] = new[] { "public" }, + ["id_token_signing_alg_values_supported"] = new[] { "RS256" }, + ["code_challenge_methods_supported"] = new[] { "S256" }, + }; + + server + .Given(Request.Create().WithPath("/.well-known/openid-configuration").UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(JsonSerializer.Serialize(document))); + } + + private void StubJwks() + { + var parameters = signingKey.Rsa.ExportParameters(includePrivateParameters: false); + + var jwks = new + { + keys = new[] + { + new + { + kty = "RSA", + use = "sig", + kid = KeyId, + alg = "RS256", + n = Base64UrlEncoder.Encode(parameters.Modulus), + e = Base64UrlEncoder.Encode(parameters.Exponent), + }, + }, + }; + + server + .Given(Request.Create().WithPath("/.well-known/jwks.json").UsingGet()) + .RespondWith(Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(JsonSerializer.Serialize(jwks))); + } + + /// + public void Dispose() + { + server.Stop(); + server.Dispose(); + signingKey.Rsa.Dispose(); + } +} diff --git a/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs b/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs new file mode 100644 index 0000000..837b221 --- /dev/null +++ b/tests/DodoSSH.Api.Tests/SyncEndpointTests.cs @@ -0,0 +1,716 @@ +using System.Net; +using System.Net.Http.Json; +using DodoSSH.Contracts; +using DodoSSH.Domain; +using DodoSSH.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DodoSSH.Api.Tests; + +/// +/// End-to-end sync behaviour over HTTP, and the authorization denials. +/// +/// +/// The denial tests are the most important thing here. Every one of them asserts that a caller who +/// should not reach a vault does not, through the real authentication pipeline rather than a +/// bypassed one. +/// +[Collection(ApiCollection.Name)] +public sealed class SyncEndpointTests(ApiFixture fixture) +{ + private static readonly DateTimeOffset Now = new(2026, 7, 28, 12, 0, 0, TimeSpan.Zero); + + // ---- Authentication ---- + + [Fact] + public async Task Pull_WithoutAToken_Is401() + { + var client = fixture.CreateClient(); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task Push_WithoutAToken_Is401() + { + var client = fixture.CreateClient(); + + var response = await client.PostAsJsonAsync( + PushUrl(Guid.CreateVersion7()), + new SyncPushRequest([])); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task ATokenSignedByAnotherKey_Is401() + { + // Proves signature validation is genuinely running, not stubbed out. + var client = fixture.CreateClientWithToken( + fixture.IdentityProvider.MintTokenWithForeignKey(NewSubject())); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task ATokenForAnotherAudience_Is401() + { + var client = fixture.CreateClientWithToken( + fixture.IdentityProvider.MintToken(NewSubject(), audience: "some-other-api")); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task ATokenFromAnotherIssuer_Is401() + { + var client = fixture.CreateClientWithToken( + fixture.IdentityProvider.MintToken(NewSubject(), issuer: "https://evil.example")); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task AnExpiredToken_Is401() + { + var client = fixture.CreateClientWithToken(fixture.IdentityProvider.MintToken( + NewSubject(), + expires: TimeProvider.System.GetUtcNow().UtcDateTime.AddMinutes(-10))); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.Unauthorized); + } + + // ---- Authorization: the wrong user must be denied ---- + + [Fact] + public async Task Pull_AnotherUsersVault_Is404() + { + // 404 rather than 403: a distinct "exists but forbidden" answer would let a caller + // enumerate other tenants' vault ids. + var (_, vaultId) = await SeedUserWithVaultAsync(); + var intruder = fixture.CreateClientFor(NewSubject()); + + var response = await intruder.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Push_AnotherUsersVault_Is404() + { + var (_, vaultId) = await SeedUserWithVaultAsync(); + var intruder = fixture.CreateClientFor(NewSubject()); + + var response = await intruder.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch()); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Push_AnotherUsersVault_WritesNothing() + { + // A denial that still mutated state would be worse than no check at all. + var (_, vaultId) = await SeedUserWithVaultAsync(); + var intruder = fixture.CreateClientFor(NewSubject()); + var batch = NewCreateBatch(); + + await intruder.PostAsJsonAsync(PushUrl(vaultId), batch); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + (await database.Hosts.AnyAsync(h => h.VaultId == vaultId)).ShouldBeFalse(); + (await database.VaultChanges.AnyAsync(c => c.VaultId == vaultId)).ShouldBeFalse(); + } + + [Fact] + public async Task Pull_ANonexistentVault_Is404() + { + var client = fixture.CreateClientFor(NewSubject()); + + var response = await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact] + public async Task ATeamVault_IsDeniedUntilTeamsShip() + { + // Failing closed on an unimplemented path, rather than falling through to a default. + var vaultId = await SeedTeamVaultAsync(); + var client = fixture.CreateClientFor(NewSubject()); + + var response = await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(null, null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + // ---- Round trip ---- + + [Fact] + public async Task Push_ThenPull_ReturnsTheItem() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var batch = NewCreateBatch(); + var push = await client.PostAsJsonAsync(PushUrl(vaultId), batch); + push.EnsureSuccessStatusCode(); + + var pushed = await push.Content.ReadFromJsonAsync(); + pushed.ShouldNotBeNull(); + pushed.Results.Count.ShouldBe(1); + pushed.Results[0].Status.ShouldBe(SyncOperationStatus.Applied); + pushed.Results[0].Version.ShouldBe(1); + + var pull = await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(null, null, null)); + pull.EnsureSuccessStatusCode(); + + var pulled = await pull.Content.ReadFromJsonAsync(); + pulled.ShouldNotBeNull(); + pulled.Changes.Count.ShouldBe(1); + + var change = pulled.Changes[0]; + change.EntityId.ShouldBe(batch.Operations[0].EntityId); + change.Operation.ShouldBe(SyncOperation.Upsert); + change.Payload.ShouldNotBeNull(); + change.Payload.Envelope.ShouldBe(batch.Operations[0].Payload!.Envelope); + } + + [Fact] + public async Task Pull_WithACursor_ReturnsOnlyNewerChanges() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + await client.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch()); + + var first = await (await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(null, null, null))).Content.ReadFromJsonAsync(); + first.ShouldNotBeNull(); + + // Nothing new since that cursor. + var empty = await (await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(first.NextCursor, null, null))) + .Content.ReadFromJsonAsync(); + empty.ShouldNotBeNull(); + empty.Changes.ShouldBeEmpty(); + + // The cursor must not have rewound, or the next poll would replay history. + empty.NextCursor.ShouldBe(first.NextCursor); + + await client.PostAsJsonAsync(PushUrl(vaultId), NewCreateBatch()); + + var second = await (await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(first.NextCursor, null, null))) + .Content.ReadFromJsonAsync(); + second.ShouldNotBeNull(); + second.Changes.Count.ShouldBe(1); + } + + [Fact] + public async Task Pull_WithACursorFromAnotherVault_Is400() + { + var (subject, firstVault) = await SeedUserWithVaultAsync(); + var (_, otherVault) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var pull = await client.PostAsJsonAsync( + PullUrl(firstVault), + new SyncPullRequest(null, null, null)); + var cursor = (await pull.Content.ReadFromJsonAsync())!.NextCursor; + + // Correctly signed, but issued for a different vault. + var response = await client.PostAsJsonAsync( + PullUrl(otherVault), + new SyncPullRequest(cursor, null, null)); + + // 404 first, because this caller cannot see the other vault at all. + response.StatusCode.ShouldBe(HttpStatusCode.NotFound); + } + + [Fact] + public async Task Pull_WithATamperedCursor_Is400() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var response = await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest("bm90LWEtcmVhbC1jdXJzb3I", null, null)); + + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + + var problem = await response.Content.ReadFromJsonAsync(); + problem.ShouldNotBeNull(); + problem.Code.ShouldBe(ProblemCodes.InvalidCursor); + } + + // ---- Conflict and idempotency ---- + + [Fact] + public async Task Push_WithAStaleVersion_ReportsConflictAndReturnsServerState() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var create = NewCreateBatch(); + var entityId = create.Operations[0].EntityId; + await client.PostAsJsonAsync(PushUrl(vaultId), create); + + // Update to version 2. + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + NewOperation(entityId, expectedVersion: 1, envelope: [9, 9, 9]), + ])); + + // A second client still believes it is on version 1. + var stale = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + NewOperation(entityId, expectedVersion: 1, envelope: [7, 7, 7]), + ])); + + stale.EnsureSuccessStatusCode(); + + var body = await stale.Content.ReadFromJsonAsync(); + body.ShouldNotBeNull(); + body.Results[0].Status.ShouldBe(SyncOperationStatus.Conflict); + body.Results[0].Version.ShouldBe(2); + + // The server's current state comes back so the client can merge rather than guess. + body.Results[0].ServerEntity.ShouldNotBeNull(); + body.Results[0].ServerEntity!.Payload!.Envelope.ShouldBe([9, 9, 9]); + } + + [Fact] + public async Task Push_WithAConflict_DoesNotOverwrite() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var create = NewCreateBatch(); + var entityId = create.Operations[0].EntityId; + await client.PostAsJsonAsync(PushUrl(vaultId), create); + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + NewOperation(entityId, expectedVersion: 1, envelope: [9, 9, 9]), + ])); + + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + NewOperation(entityId, expectedVersion: 1, envelope: [7, 7, 7]), + ])); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + var stored = await database.Hosts.SingleAsync(h => h.Id == entityId); + + // Never last-writer-wins. + stored.Payload.ShouldBe([9, 9, 9]); + stored.Version.ShouldBe(2); + } + + [Fact] + public async Task Push_ReplayingAnOperationId_IsReportedDuplicateAndAppliedOnce() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var batch = NewCreateBatch(); + + var first = await client.PostAsJsonAsync(PushUrl(vaultId), batch); + first.EnsureSuccessStatusCode(); + + // Exactly the same batch again, as a retry after a timeout would be. + var replay = await client.PostAsJsonAsync(PushUrl(vaultId), batch); + replay.EnsureSuccessStatusCode(); + + var body = await replay.Content.ReadFromJsonAsync(); + body.ShouldNotBeNull(); + body.Results[0].Status.ShouldBe(SyncOperationStatus.Duplicate); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + var stored = await database.Hosts.SingleAsync(h => h.Id == batch.Operations[0].EntityId); + stored.Version.ShouldBe(1); + + (await database.VaultChanges.CountAsync(c => c.EntityId == stored.Id)).ShouldBe(1); + } + + [Fact] + public async Task Push_AMixedBatch_AppliesTheGoodAndReportsTheBad() + { + // One stale item must not block everything else a client queued while offline. + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var existing = NewCreateBatch(); + await client.PostAsJsonAsync(PushUrl(vaultId), existing); + + var goodId = Guid.CreateVersion7(); + var mixed = new SyncPushRequest( + [ + NewOperation(existing.Operations[0].EntityId, expectedVersion: 99, envelope: [1]), + NewOperation(goodId, expectedVersion: null, envelope: [2, 2]), + ]); + + var response = await client.PostAsJsonAsync(PushUrl(vaultId), mixed); + + // 200 despite a failed operation: per-operation status carries the detail. + response.StatusCode.ShouldBe(HttpStatusCode.OK); + + var body = await response.Content.ReadFromJsonAsync(); + body.ShouldNotBeNull(); + body.Results[0].Status.ShouldBe(SyncOperationStatus.Conflict); + body.Results[1].Status.ShouldBe(SyncOperationStatus.Applied); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + (await database.Hosts.AnyAsync(h => h.Id == goodId)).ShouldBeTrue(); + } + + // ---- Relay field enforcement, ADR 0004 ---- + + [Fact] + public async Task Push_ARelayAddressWithoutEnablingRelay_IsRejected() + { + // Prevents the server quietly learning an address the user never opted into exposing. + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Host, + Guid.CreateVersion7(), + SyncOperation.Upsert, + null, + new EncryptedPayload([1, 2, 3], 1, 1), + new SyncPlaintextFields(RelayEnabled: false, Hostname: "secret.internal", Port: 22)), + ])); + + response.EnsureSuccessStatusCode(); + + var body = await response.Content.ReadFromJsonAsync(); + body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid); + } + + [Fact] + public async Task Push_RelayEnabledWithoutAnAddress_IsRejected() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Host, + Guid.CreateVersion7(), + SyncOperation.Upsert, + null, + new EncryptedPayload([1, 2, 3], 1, 1), + new SyncPlaintextFields(RelayEnabled: true)), + ])); + + response.EnsureSuccessStatusCode(); + + var body = await response.Content.ReadFromJsonAsync(); + body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid); + } + + [Fact] + public async Task Delete_ClearsTheRelayAddress() + { + // Leaving it would keep the server able to resolve a host the user believes is gone. + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var entityId = Guid.CreateVersion7(); + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Host, + entityId, + SyncOperation.Upsert, + null, + new EncryptedPayload([1, 2, 3], 1, 1), + new SyncPlaintextFields(RelayEnabled: true, Hostname: "bastion.internal", Port: 22)), + ])); + + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Host, + entityId, + SyncOperation.Delete, + ExpectedVersion: 1, + Payload: null, + PlaintextFields: null), + ])); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + var stored = await database.Hosts.SingleAsync(h => h.Id == entityId); + + stored.DeletedAtUtc.ShouldNotBeNull(); + stored.RelayEnabled.ShouldBeFalse(); + stored.Hostname.ShouldBeNull(); + stored.Port.ShouldBeNull(); + } + + [Fact] + public async Task Pull_ADeletedItem_ReturnsATombstoneWithNoPayload() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var create = NewCreateBatch(); + var entityId = create.Operations[0].EntityId; + await client.PostAsJsonAsync(PushUrl(vaultId), create); + + await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Host, + entityId, + SyncOperation.Delete, + ExpectedVersion: 1, + Payload: null, + PlaintextFields: null), + ])); + + var pull = await client.PostAsJsonAsync( + PullUrl(vaultId), + new SyncPullRequest(null, null, null)); + + var body = await pull.Content.ReadFromJsonAsync(); + body.ShouldNotBeNull(); + + var tombstone = body.Changes.Last(c => c.EntityId == entityId); + tombstone.Operation.ShouldBe(SyncOperation.Delete); + tombstone.Payload.ShouldBeNull(); + tombstone.PlaintextFields.ShouldBeNull(); + } + + // ---- Batch limits ---- + + [Fact] + public async Task Push_AnEmptyBatch_Is400() + { + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest([])); + + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task Push_AnUnsupportedEntityType_IsInvalidNotAFailedBatch() + { + // A newer client asking for something this server does not do yet gets a precise + // per-operation answer rather than a whole-batch rejection. + var (subject, vaultId) = await SeedUserWithVaultAsync(); + var client = fixture.CreateClientFor(subject); + + var response = await client.PostAsJsonAsync(PushUrl(vaultId), new SyncPushRequest( + [ + new SyncPushOperation( + Guid.CreateVersion7(), + SyncEntityType.Credential, + Guid.CreateVersion7(), + SyncOperation.Upsert, + null, + new EncryptedPayload([1], 1, 1), + null), + ])); + + response.EnsureSuccessStatusCode(); + + var body = await response.Content.ReadFromJsonAsync(); + body!.Results[0].Status.ShouldBe(SyncOperationStatus.Invalid); + } + + // ---- JIT provisioning ---- + + [Fact] + public async Task AFirstRequest_ProvisionsTheUser() + { + var subject = NewSubject(); + var email = $"{subject}@example.com"; + var client = fixture.CreateClientFor(subject, email); + + // Any authenticated call is enough to trigger provisioning. + await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + var user = await database.Users.SingleOrDefaultAsync(u => u.Subject == subject); + user.ShouldNotBeNull(); + user.Issuer.ShouldBe(fixture.IdentityProvider.Authority); + user.Email.ShouldBe(email); + user.Status.ShouldBe(UserStatus.Active); + } + + [Fact] + public async Task RepeatedRequests_ProvisionOnlyOnce() + { + var subject = NewSubject(); + var client = fixture.CreateClientFor(subject); + + for (var i = 0; i < 3; i++) + { + await client.PostAsJsonAsync( + PullUrl(Guid.CreateVersion7()), + new SyncPullRequest(null, null, null)); + } + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + (await database.Users.CountAsync(u => u.Subject == subject)).ShouldBe(1); + } + + // ---- Helpers ---- + + private static string PullUrl(Guid vaultId) => $"/api/v1/vaults/{vaultId}/sync/pull"; + + private static string PushUrl(Guid vaultId) => $"/api/v1/vaults/{vaultId}/sync/push"; + + private static string NewSubject() => $"user-{Guid.CreateVersion7():N}"; + + private static SyncPushOperation NewOperation(Guid entityId, int? expectedVersion, byte[] envelope) => + new( + Guid.CreateVersion7(), + SyncEntityType.Host, + entityId, + SyncOperation.Upsert, + expectedVersion, + new EncryptedPayload(envelope, 1, 1), + new SyncPlaintextFields()); + + private static SyncPushRequest NewCreateBatch() => + new([NewOperation(Guid.CreateVersion7(), expectedVersion: null, envelope: [1, 2, 3, 4])]); + + private async Task<(string Subject, Guid VaultId)> SeedUserWithVaultAsync() + { + var subject = NewSubject(); + + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + var user = new UserAccount + { + Id = Guid.CreateVersion7(), + Issuer = fixture.IdentityProvider.Authority, + Subject = subject, + Status = UserStatus.Active, + CreatedAtUtc = Now, + UpdatedAtUtc = Now, + }; + + var vault = new Vault + { + Id = Guid.CreateVersion7(), + Name = "Personal", + OwnerKind = VaultOwnerKind.Personal, + OwnerUserId = user.Id, + KeyGeneration = 1, + CreatedAtUtc = Now, + UpdatedAtUtc = Now, + }; + + database.Users.Add(user); + database.Vaults.Add(vault); + await database.SaveChangesAsync(); + + return (subject, vault.Id); + } + + private async Task SeedTeamVaultAsync() + { + await using var scope = fixture.CreateScope(); + var database = scope.ServiceProvider.GetRequiredService(); + + var owner = new UserAccount + { + Id = Guid.CreateVersion7(), + Issuer = fixture.IdentityProvider.Authority, + Subject = NewSubject(), + Status = UserStatus.Active, + CreatedAtUtc = Now, + UpdatedAtUtc = Now, + }; + + var team = new Team + { + Id = Guid.CreateVersion7(), + Name = "Team", + Slug = $"team-{Guid.CreateVersion7():N}", + CreatedByUserId = owner.Id, + CreatedAtUtc = Now, + }; + + var vault = new Vault + { + Id = Guid.CreateVersion7(), + Name = "Shared", + OwnerKind = VaultOwnerKind.Team, + TeamId = team.Id, + KeyGeneration = 1, + CreatedAtUtc = Now, + UpdatedAtUtc = Now, + }; + + database.Users.Add(owner); + database.Teams.Add(team); + database.Vaults.Add(vault); + await database.SaveChangesAsync(); + + return vault.Id; + } + + /// Minimal ProblemDetails shape, for asserting on the code extension. + private sealed record JsonProblem(string? Type, string? Detail, string? Code); +} diff --git a/tests/DodoSSH.Api.Tests/packages.lock.json b/tests/DodoSSH.Api.Tests/packages.lock.json new file mode 100644 index 0000000..ed006cb --- /dev/null +++ b/tests/DodoSSH.Api.Tests/packages.lock.json @@ -0,0 +1,1809 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Meziantou.Analyzer": { + "type": "Direct", + "requested": "[3.0.134, )", + "resolved": "3.0.134", + "contentHash": "tTYCcYKyOko3TMNxmxmA9nakbcHVUgglENmCMIhzIjl9y9FBZO/0tWSxTGC74Sp198FmWih5S5KkjQRBg5ePkQ==" + }, + "Microsoft.AspNetCore.Mvc.Testing": { + "type": "Direct", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "pTWE4RtRbb9sl/U9QjZA5oapEZ01ZEMfRilZvvh55ZW97caTQ2XuAF6sgc+7ojKWBbR2qrcWVo5P80gMPuW/tQ==", + "dependencies": { + "Microsoft.AspNetCore.TestHost": "10.0.10", + "Microsoft.Extensions.DependencyModel": "10.0.10", + "Microsoft.Extensions.Hosting": "10.0.10" + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers": { + "type": "Direct", + "requested": "[5.6.0, )", + "resolved": "5.6.0", + "contentHash": "Kcobt3pnOdO0A+6CKiMHZdTEluJpsfxiV20axtZdmfBQnDmiWTKPJADlgAfdTuKNAnVarrkJa0UEGwuOo91muw==" + }, + "NSubstitute": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "0gvKMbiJ+/WrfbcfBfqRZZrvfLJcd3rqkqVMjjlY5dtmLRVzMY+o/K/rJUStofQ2haSr9Vd04YDfvZtVVGS3/A==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.3.0, )", + "resolved": "4.3.0", + "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "Testcontainers.PostgreSql": { + "type": "Direct", + "requested": "[4.13.0, )", + "resolved": "4.13.0", + "contentHash": "2ow4AE8drI9iA9Fr4ycAPusXPB1lJfQyyNONSMLE/XqLpm8VuNAh3pK38fOjkOWtTrnD03s4hAIdl4036Ik69A==", + "dependencies": { + "Testcontainers": "4.13.0" + } + }, + "WireMock.Net": { + "type": "Direct", + "requested": "[2.13.0, )", + "resolved": "2.13.0", + "contentHash": "msedNpcc2vBHSpmdpRmKSxJUsMIIA/MgeJw1OfiOnHEbpxiXDKWNe/LSOGfyMtzzoeWmbQ4XDu1FpwyBG+8JMQ==", + "dependencies": { + "WireMock.Net.GraphQL": "2.13.0", + "WireMock.Net.Matchers.SystemTextJsonPath": "2.13.0", + "WireMock.Net.MimePart": "2.13.0", + "WireMock.Net.Minimal": "2.13.0", + "WireMock.Net.OpenTelemetry": "2.13.0", + "WireMock.Net.ProtoBuf": "2.13.0" + } + }, + "xunit.v3": { + "type": "Direct", + "requested": "[3.2.2, )", + "resolved": "3.2.2", + "contentHash": "L+4/4y0Uqcg8/d6hfnxhnwh4j9FaeULvefTwrk30rr1o4n/vdPfyUQ8k0yzH8VJx7bmFEkDdcRfbtbjEHlaYcA==", + "dependencies": { + "xunit.v3.mtp-v1": "[3.2.2]" + } + }, + "AnyOf": { + "type": "Transitive", + "resolved": "0.5.0.1", + "contentHash": "WDQw5Qos3mhCumSCgKD70TM1dmqBAuJFGv1cFtNTwTaDLZR7kGy33M5C+L0vZV/bNRNwyi5ABvRGPWHL17rkNw==" + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "Docker.DotNet.Enhanced": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "nGicLwvd42FhRk+khY5uS6cx49ErNdwYKnYBg0F4m4BDKLp/R77AVmmN9xAiqI3W/wN5ZCHkdUhgxf5ORkZuFQ==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3", + "Docker.DotNet.Enhanced.LegacyHttp": "4.3.3", + "Docker.DotNet.Enhanced.NPipe": "4.3.3", + "Docker.DotNet.Enhanced.NativeHttp": "4.3.3", + "Docker.DotNet.Enhanced.Unix": "4.3.3", + "Microsoft.Extensions.Logging.Abstractions": "8.0.3" + } + }, + "Docker.DotNet.Enhanced.Handler.Abstractions": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "9Cp8hOgtynixcDoAs9lnEaQosluojSYmiW3fsLsLIVfZjlq/fznSIZNUhnmyT4Xo1Iyuok/y49WL/25O47u0Pw==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "8.0.3" + } + }, + "Docker.DotNet.Enhanced.LegacyHttp": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "7j3M16emv9PAQN7VwFn23xLYNj8GJmwPOcogveHkaWnOCqiC+anRaNKQwqIBNApM1AuwZKivehTKTPmmrjUUnw==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3" + } + }, + "Docker.DotNet.Enhanced.NativeHttp": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "iNzK+jRFeEMobSA7l/h4ARwCKOOefOWtVN5/RB0ft6/6H6IQXvVUuOgGyZAjYLBT7TsyClRYno2B904f3dtBuQ==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3" + } + }, + "Docker.DotNet.Enhanced.NPipe": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "ZTLYufuEfY0e6qLOgeH9QgXx2KYuoABRVaY5A8rsggyLgYqbDj9rCRfVAhHPCUv83S7pVxDHy+Tvm/BnxjWVpg==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3" + } + }, + "Docker.DotNet.Enhanced.Unix": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "ypo8qNbmvHw1t9VfpRTMogCw2vht6VjkXzlGYUUeP2H2bf83USURdla1maW1njn2oq2rfLUFOGMfmt+A37QU2w==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3" + } + }, + "Docker.DotNet.Enhanced.X509": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "oBDibWezEv4hgj3RIQxI3DVcxkNV1MdrD0d/jhjUu+h3DL+qc0wlkQva15kkwMatXmC/hWp1VP0DMoFXe+BmEw==", + "dependencies": { + "Docker.DotNet.Enhanced.Handler.Abstractions": "4.3.3" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Fare": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "21XZo/yuXK1k0EUhdLnjgRD4n0HQYmPFchV6uaORcRc65rasZ1vdm2dmJXPBKZiIBztRRYRmmg/B76W721VWkA==" + }, + "GraphQL": { + "type": "Transitive", + "resolved": "8.5.0", + "contentHash": "BZkfH7GVacTZEkyqa4XN9mW12UA/0XYrpEkkrJnNBf0Pqw8CZWQfItLaWgG2C1Ju9YHH1UUG+LmIpo8iMP6pKA==", + "dependencies": { + "GraphQL-Parser": "9.5.0", + "GraphQL.Analyzers": "8.5.0" + } + }, + "GraphQL-Parser": { + "type": "Transitive", + "resolved": "9.5.0", + "contentHash": "5XWJGKHdVi8pyD4P0EglmJmlXEGs0HzvGlEBf3+/Ve1jLYBBKIOkKvY0Ej17b9Kn1bbBxkrmghqbmsMbkLL1nQ==" + }, + "GraphQL.Analyzers": { + "type": "Transitive", + "resolved": "8.5.0", + "contentHash": "jwfvZD5agmw9J8iZEe6BUfKAY+/lC7EqDQg+6JRwXaQ6G/MCLy8jyBc2SHF/2JdAtrkygc1bVyCUP5mR2PHzVA==" + }, + "GraphQL.NewtonsoftJson": { + "type": "Transitive", + "resolved": "8.5.0", + "contentHash": "tAeUoUhJih5fdZRCV0ue3G/gsu8YBiyNZkgLVFyk0wTk8vJGLTBDJaP5o5LVo1edVnk0bR+0/PaNXAEJxkVrTw==", + "dependencies": { + "GraphQL": "[8.5.0, 9.0.0)", + "Newtonsoft.Json": "13.0.3" + } + }, + "Handlebars.Net": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "WsYWCEXsIM6hEOSOSRHtIYLjC8BnbT5MVmqhNKRqUI7qiv0t8x3nJiBTEv0ZZfvUAMAFnadGIzSsS/U2anVG1Q==" + }, + "Handlebars.Net.Helpers": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "MZ0/Nvy3XdEy/igZD4fJy5HiUKcKUA170Sq+2RmJFsGiWSqlroXzp8TQqJTDCi1aBtETfQOVdBG0YNvuDs9+uQ==", + "dependencies": { + "Handlebars.Net.Helpers.Core": "2.5.5" + } + }, + "Handlebars.Net.Helpers.Core": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "vLTL6UrLUPPiWDCKig8FLhSU+i9J4n/8RfrhadvnvxqziyK0ArxKMT2gLqQ+X/8vJaRcI9zvD5HxA8KjWbq3Dw==", + "dependencies": { + "Handlebars.Net": "2.1.6", + "Stef.Validation": "0.1.1" + } + }, + "Handlebars.Net.Helpers.Humanizer": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "A7TmfLtv7x8HiVckXBmKmOAsO5GKxjSOjxymXS70upqzLLH8BjrhFl+QIGFCdVIWQRx3+yNjGcsz/JXNwt9YZg==", + "dependencies": { + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5", + "Humanizer": "[2.14.1, 4.0.0)" + } + }, + "Handlebars.Net.Helpers.Json": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "iRBo/ik0M8M6ezJt4QzZm5KQptEdeh6bVtnDbieuxh5YPTUsPMFvtoq0gg426PwrahE+5rXoFZmIM11Oy5GwTg==", + "dependencies": { + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5", + "Newtonsoft.Json": "13.0.3" + } + }, + "Handlebars.Net.Helpers.Random": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "zKcfFDN4QxgEjk4Em9yz/PQu0mBpIgEaqjhacg2Fl6M0oSsF7VBVflae2WRM9MtiVeRTwLkVwcy7TvJ6iqFuVQ==", + "dependencies": { + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5", + "RandomDataGenerator.Net": "1.0.19" + } + }, + "Handlebars.Net.Helpers.Xeger": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "J+w9KalIuYlTKMeIv8eoisdoMEz44elri0UOLtfTAuDbADwnBBsJGp4kAQI107+hBcqery9OCRCXm8fvH4eCxQ==", + "dependencies": { + "Fare": "2.2.1", + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5" + } + }, + "Handlebars.Net.Helpers.XPath": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "uUGzjR5w5YCv+BdWQ4RpWAho0tUG0zfAKG5v+abXS6+E+fjbfSshOg7LyoWTVcGTWO0PouukhSMUFaumB2K4tg==", + "dependencies": { + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5", + "XPath2.Extensions": "1.1.5" + } + }, + "Handlebars.Net.Helpers.Xslt": { + "type": "Transitive", + "resolved": "2.5.5", + "contentHash": "bOaX47avO4Uja6jTZcBAgS5KjL/2ZaewCpB0Oy7cVegctPyxiiRx/T44XGSt0133hHry9f5nJVsjFKNLrYq0Pg==", + "dependencies": { + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Core": "2.5.5" + } + }, + "Humanizer": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/FUTD3cEceAAmJSCPN9+J+VhGwmL/C12jvwlyM1DFXShEMsBzvLzLqSrJ2rb+k/W2znKw7JyflZgZpyE+tI7lA==", + "dependencies": { + "Humanizer.Core.af": "2.14.1", + "Humanizer.Core.ar": "2.14.1", + "Humanizer.Core.az": "2.14.1", + "Humanizer.Core.bg": "2.14.1", + "Humanizer.Core.bn-BD": "2.14.1", + "Humanizer.Core.cs": "2.14.1", + "Humanizer.Core.da": "2.14.1", + "Humanizer.Core.de": "2.14.1", + "Humanizer.Core.el": "2.14.1", + "Humanizer.Core.es": "2.14.1", + "Humanizer.Core.fa": "2.14.1", + "Humanizer.Core.fi-FI": "2.14.1", + "Humanizer.Core.fr": "2.14.1", + "Humanizer.Core.fr-BE": "2.14.1", + "Humanizer.Core.he": "2.14.1", + "Humanizer.Core.hr": "2.14.1", + "Humanizer.Core.hu": "2.14.1", + "Humanizer.Core.hy": "2.14.1", + "Humanizer.Core.id": "2.14.1", + "Humanizer.Core.is": "2.14.1", + "Humanizer.Core.it": "2.14.1", + "Humanizer.Core.ja": "2.14.1", + "Humanizer.Core.ko-KR": "2.14.1", + "Humanizer.Core.ku": "2.14.1", + "Humanizer.Core.lv": "2.14.1", + "Humanizer.Core.ms-MY": "2.14.1", + "Humanizer.Core.mt": "2.14.1", + "Humanizer.Core.nb": "2.14.1", + "Humanizer.Core.nb-NO": "2.14.1", + "Humanizer.Core.nl": "2.14.1", + "Humanizer.Core.pl": "2.14.1", + "Humanizer.Core.pt": "2.14.1", + "Humanizer.Core.ro": "2.14.1", + "Humanizer.Core.ru": "2.14.1", + "Humanizer.Core.sk": "2.14.1", + "Humanizer.Core.sl": "2.14.1", + "Humanizer.Core.sr": "2.14.1", + "Humanizer.Core.sr-Latn": "2.14.1", + "Humanizer.Core.sv": "2.14.1", + "Humanizer.Core.th-TH": "2.14.1", + "Humanizer.Core.tr": "2.14.1", + "Humanizer.Core.uk": "2.14.1", + "Humanizer.Core.uz-Cyrl-UZ": "2.14.1", + "Humanizer.Core.uz-Latn-UZ": "2.14.1", + "Humanizer.Core.vi": "2.14.1", + "Humanizer.Core.zh-CN": "2.14.1", + "Humanizer.Core.zh-Hans": "2.14.1", + "Humanizer.Core.zh-Hant": "2.14.1" + } + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Humanizer.Core.af": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "BoQHyu5le+xxKOw+/AUM7CLXneM/Bh3++0qh1u0+D95n6f9eGt9kNc8LcAHLIOwId7Sd5hiAaaav0Nimj3peNw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ar": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "3d1V10LDtmqg5bZjWkA/EkmGFeSfNBcyCH+TiHcHP+HGQQmRq3eBaLcLnOJbVQVn3Z6Ak8GOte4RX4kVCxQlFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.az": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8Z/tp9PdHr/K2Stve2Qs/7uqWPWLUK9D8sOZDNzyv42e20bSoJkHFn7SFoxhmaoVLJwku2jp6P7HuwrfkrP18Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bg": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "S+hIEHicrOcbV2TBtyoPp1AVIGsBzlarOGThhQYCnP6QzEYo/5imtok6LMmhZeTnBFoKhM8yJqRfvJ5yqVQKSQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.bn-BD": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "U3bfj90tnUDRKlL1ZFlzhCHoVgpTcqUlTQxjvGCaFKb+734TTu3nkHUWVZltA1E/swTvimo/aXLtkxnLFrc0EQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.cs": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jWrQkiCTy3L2u1T86cFkgijX6k7hoB0pdcFMWYaSZnm6rvG/XJE40tfhYyKhYYgIc1x9P2GO5AC7xXvFnFdqMQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.da": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "5o0rJyE/2wWUUphC79rgYDnif/21MKTTx9LIzRVz9cjCIVFrJ2bDyR2gapvI9D6fjoyvD1NAfkN18SHBsO8S9g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.de": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "9JD/p+rqjb8f5RdZ3aEJqbjMYkbk4VFii2QDnnOdNo6ywEfg/A5YeOQ55CaBJmy7KvV4tOK4+qHJnX/tg3Z54A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.el": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Xmv6sTL5mqjOWGGpqY7bvbfK5RngaUHSa8fYDGSLyxY9mGdNbDcasnRnMOvi0SxJS9gAqBCn21Xi90n2SHZbFA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.es": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "e//OIAeMB7pjBV1HqqI4pM2Bcw3Jwgpyz9G5Fi4c+RJvhqFwztoWxW57PzTnNJE2lbhGGLQZihFZjsbTUsbczA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fa": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nzDOj1x0NgjXMjsQxrET21t1FbdoRYujzbmZoR8u8ou5CBWY1UNca0j6n/PEJR/iUbt4IxstpszRy41wL/BrpA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fi-FI": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Vnxxx4LUhp3AzowYi6lZLAA9Lh8UqkdwRh4IE2qDXiVpbo08rSbokATaEzFS+o+/jCNZBmoyyyph3vgmcSzhhQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2p4g0BYNzFS3u9SOIDByp2VClYKO0K1ecDV4BkB9EYdEPWfFODYnF+8CH8LpUrpxL2TuWo2fiFx/4Jcmrnkbpg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.fr-BE": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "o6R3SerxCRn5Ij8nCihDNMGXlaJ/1AqefteAssgmU2qXYlSAGdhxmnrQAXZUDlE4YWt/XQ6VkNLtH7oMqsSPFQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.he": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "FPsAhy7Iw6hb+ZitLgYC26xNcgGAHXb0V823yFAzcyoL5ozM+DCJtYfDPYiOpsJhEZmKFTM9No0jUn1M89WGvg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "chnaD89yOlST142AMkAKLuzRcV5df3yyhDyRU5rypDiqrq2HN8y1UR3h1IicEAEtXLoOEQyjSAkAQ6QuXkn7aw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hu": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "hAfnaoF9LTGU/CmFdbnvugN4tIs8ppevVMe3e5bD24+tuKsggMc5hYta9aiydI8JH9JnuVmxvNI4DJee1tK05A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.hy": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "sVIKxOiSBUb4gStRHo9XwwAg9w7TNvAXbjy176gyTtaTiZkcjr9aCPziUlYAF07oNz6SdwdC2mwJBGgvZ0Sl2g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.id": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "4Zl3GTvk3a49Ia/WDNQ97eCupjjQRs2iCIZEQdmkiqyaLWttfb+cYXDMGthP42nufUL0SRsvBctN67oSpnXtsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.is": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "R67A9j/nNgcWzU7gZy1AJ07ABSLvogRbqOWvfRDn4q6hNdbg/mjGjZBp4qCTPnB2mHQQTCKo3oeCUayBCNIBCw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.it": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "jYxGeN4XIKHVND02FZ+Woir3CUTyBhLsqxu9iqR/9BISArkMf1Px6i5pRZnvq4fc5Zn1qw71GKKoCaHDJBsLFw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ja": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TM3ablFNoYx4cYJybmRgpDioHpiKSD7q0QtMrmpsqwtiiEsdW5zz/q4PolwAczFnvrKpN6nBXdjnPPKVet93ng==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ko-KR": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "CtvwvK941k/U0r8PGdEuBEMdW6jv/rBiA9tUhakC7Zd2rA/HCnDcbr1DiNZ+/tRshnhzxy/qwmpY8h4qcAYCtQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ku": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vHmzXcVMe+LNrF9txpdHzpG7XJX65SiN9GQd/Zkt6gsGIIEeECHrkwCN5Jnlkddw2M/b0HS4SNxdR1GrSn7uCA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.lv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E1/KUVnYBS1bdOTMNDD7LV/jdoZv/fbWTLPtvwdMtSdqLyRTllv6PGM9xVQoFDYlpvVGtEl/09glCojPHw8ffA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ms-MY": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "vX8oq9HnYmAF7bek4aGgGFJficHDRTLgp/EOiPv9mBZq0i4SA96qVMYSjJ2YTaxs7Eljqit7pfpE2nmBhY5Fnw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.mt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "pEgTBzUI9hzemF7xrIZigl44LidTUhNu4x/P6M9sAwZjkUF0mMkbpxKkaasOql7lLafKrnszs0xFfaxQyzeuZQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "mbs3m6JJq53ssLqVPxNfqSdTxAcZN3njlG8yhJVx83XVedpTe1ECK9aCa8FKVOXv93Gl+yRHF82Hw9T9LWv2hw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nb-NO": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "AsJxrrVYmIMbKDGe8W6Z6//wKv9dhWH7RsTcEHSr4tQt/80pcNvLi0hgD3fqfTtg0tWKtgch2cLf4prorEV+5A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.nl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "24b0OUdzJxfoqiHPCtYnR5Y4l/s4Oh7KW7uDp+qX25NMAHLCGog2eRfA7p2kRJp8LvnynwwQxm2p534V9m55wQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "17mJNYaBssENVZyQHduiq+bvdXS0nhZJGEXtPKoMhKv3GD//WO0mEfd9wjEBsWCSmWI7bjRqhCidxzN+YtJmsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.pt": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "8HB8qavcVp2la1GJX6t+G9nDYtylPKzyhxr9LAooIei9MnQvNsjEiIE4QvHoeDZ4weuQ9CsPg1c211XUMVEZ4A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ro": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "psXNOcA6R8fSHoQYhpBTtTTYiOk8OBoN3PKCEDgsJKIyeY5xuK81IBdGi77qGZMu/OwBRQjQCBMtPJb0f4O1+A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.ru": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "zm245xUWrajSN2t9H7BTf84/2APbUkKlUJpcdgsvTdAysr1ag9fi1APu6JEok39RRBXDfNRVZHawQ/U8X0pSvQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "Ncw24Vf3ioRnbU4MsMFHafkyYi8JOnTqvK741GftlQvAbULBoTz2+e7JByOaasqeSi0KfTXeegJO+5Wk1c0Mbw==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sl": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "l8sUy4ciAIbVThWNL0atzTS2HWtv8qJrsGWNlqrEKmPwA4SdKolSqnTes9V89fyZTc2Q43jK8fgzVE2C7t009A==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rnNvhpkOrWEymy7R/MiFv7uef8YO5HuXDyvojZ7JpijHWA5dXuVXooCOiA/3E93fYa3pxDuG2OQe4M/olXbQ7w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sr-Latn": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "nuy/ykpk974F8ItoQMS00kJPr2dFNjOSjgzCwfysbu7+gjqHmbLcYs7G4kshLwdA4AsVncxp99LYeJgoh1JF5g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.sv": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "E53+tpAG0RCp+cSSI7TfBPC+NnsEqUuoSV0sU+rWRXWr9MbRWx1+Zj02XMojqjGzHjjOrBFBBio6m74seFl0AA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.th-TH": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "eSevlJtvs1r4vQarNPfZ2kKDp/xMhuD00tVVzRXkSh1IAZbBJI/x2ydxUOwfK9bEwEp+YjvL1Djx2+kw7ziu7g==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.tr": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rQ8N+o7yFcFqdbtu1mmbrXFi8TQ+uy+fVH9OPI0CI3Cu1om5hUU/GOMC3hXsTCI6d79y4XX+0HbnD7FT5khegA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uk": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "2uEfujwXKNm6bdpukaLtEJD+04uUtQD65nSGCetA1fYNizItEaIBUboNfr3GzJxSMQotNwGVM3+nSn8jTd0VSg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Cyrl-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "TD3ME2sprAvFqk9tkWrvSKx5XxEMlAn1sjk+cYClSWZlIMhQQ2Bp/w0VjX1Kc5oeKjxRAnR7vFcLUFLiZIDk9Q==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.uz-Latn-UZ": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "/kHAoF4g0GahnugZiEMpaHlxb+W6jCEbWIdsq9/I1k48ULOsl/J0pxZj93lXC3omGzVF1BTVIeAtv5fW06Phsg==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.vi": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "rsQNh9rmHMBtnsUUlJbShMsIMGflZtPmrMM6JNDw20nhsvqfrdcoDD8cMnLAbuSovtc3dP+swRmLQzKmXDTVPA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-CN": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "uH2dWhrgugkCjDmduLdAFO9w1Mo0q07EuvM0QiIZCVm6FMCu/lGv2fpMu4GX+4HLZ6h5T2Pg9FIdDLCPN2a67w==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hans": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "WH6IhJ8V1UBG7rZXQk3dZUoP2gsi8a0WkL8xL0sN6WGiv695s8nVcmab9tWz20ySQbuzp0UkSxUQFi5jJHIpOQ==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "Humanizer.Core.zh-Hant": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "VIXB7HCUC34OoaGnO3HJVtSv2/wljPhjV7eKH4+TFPgQdJj2lvHNKY41Dtg0Bphu7X5UaXFR4zrYYyo+GNOjbA==", + "dependencies": { + "Humanizer.Core": "[2.14.1]" + } + }, + "JmesPath.Net": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "sL1LeqBm+BWSKvgZN/T470IqkXcKQXmOYsRUZU18jDZeiIBmvUfIe9m3VhiII/jOK/6WmrQ+W8Pqwz3k28WX9g==", + "dependencies": { + "JmesPath.Net.Parser": "1.1.0", + "Newtonsoft.Json": "13.0.4" + } + }, + "JmesPath.Net.Parser": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "NLTE/dPy8lMcZO6E7SL5Jw3fay8Vesll7+hkeRVSRaVNg1RRyPBV3/u6CM7QNgtnzhvyFPDyxUHUuRdh0vzCSg==" + }, + "Json.More.Net": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "fRctF2J2SILYG6wqP21drmeEODmCVkVQ/b3MndDu2fT1swfySyUgq7ePCk+aENGlDcIm05fyfjh9vcuqDEfv3w==" + }, + "JsonConverter.Abstractions": { + "type": "Transitive", + "resolved": "0.13.0", + "contentHash": "Ci3nuKx3GgMDfW9JA4dJpU+hJV5G1ve72mploQP8ivSDpOmo2QbfVAQkxsVzb3UQJCgwxxM6rdE/fPXwM0yj0g==" + }, + "JsonConverter.Newtonsoft.Json": { + "type": "Transitive", + "resolved": "0.13.0", + "contentHash": "K6doeW12emLiJV4laUf58y3kkjng6/IARRtC7+20qIOBrP1pBxGRsjz2IfxCgSBkTML3q6FWe7O+/UE374lsaA==", + "dependencies": { + "JsonConverter.Abstractions": "0.13.0", + "Newtonsoft.Json": "13.0.4", + "Stef.Validation": "0.1.1" + } + }, + "JsonConverter.System.Text.Json": { + "type": "Transitive", + "resolved": "0.13.0", + "contentHash": "UtRbkZT16Z0OVZ9n/h60E0GlUDTul/DjpuuajdsCNvefsmTxf6WNB8Cq9Hjwu9pGjfqOaFOmaz8k54DaHBmc0g==", + "dependencies": { + "JsonConverter.Abstractions": "0.13.0", + "Stef.Validation": "0.1.1" + } + }, + "JsonPath.Net": { + "type": "Transitive", + "resolved": "3.0.2", + "contentHash": "Cmt2mvPYOLljjqSfM1xUYZYTPf8MPbwv2XpCpPxxq9u23/CGrz/fljgd1fJUNujd3+E1adNOyF1TLwppbyQwxg==", + "dependencies": { + "Json.More.Net": "3.0.1" + } + }, + "MetadataReferenceService.Abstractions": { + "type": "Transitive", + "resolved": "0.0.1", + "contentHash": "Sf5ip58vlqWkQIAULIOKFIIFuhtRd8lChsJRZdFo746NVApEp/qgxNf/zCLjbB/RA/8TQGXWrFPKpqjyeh3EMg==", + "dependencies": { + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Stef.Validation": "0.1.1" + } + }, + "MetadataReferenceService.Default": { + "type": "Transitive", + "resolved": "0.0.1", + "contentHash": "ihrchqYobpQMA9tn0W+MGD3oe5onqCttbR3lQfEiVzwF0V9/DS+K4YtvsUPGDC9XIie2Xw3lugSSk97k+OUwnQ==", + "dependencies": { + "MetadataReferenceService.Abstractions": "0.0.1" + } + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.23.0", + "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw==" + }, + "Microsoft.AspNetCore.Http": { + "type": "Transitive", + "resolved": "2.3.9", + "contentHash": "+CcfWi1LoKYbcxt+3toO4xbBG+qSSMbPuuow+cbZKIrITXuu1geN1traamL4jG8QaHdHGm3M0eCh+EOgdMgNPA==", + "dependencies": { + "Microsoft.AspNetCore.Http.Abstractions": "2.3.0", + "Microsoft.AspNetCore.WebUtilities": "2.3.0", + "Microsoft.Extensions.ObjectPool": "8.0.11", + "Microsoft.Extensions.Options": "8.0.2", + "Microsoft.Net.Http.Headers": "2.3.8" + } + }, + "Microsoft.AspNetCore.Http.Abstractions": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "39r9PPrjA6s0blyFv5qarckjNkaHRA5B+3b53ybuGGNTXEj1/DStQJ4NWjFL6QTRQpL9zt7nDyKxZdJOlcnq+Q==", + "dependencies": { + "Microsoft.AspNetCore.Http.Features": "2.3.0" + } + }, + "Microsoft.AspNetCore.Http.Features": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "f10WUgcsKqrkmnz6gt8HeZ7kyKjYN30PO7cSic1lPtH7paPtnQqXPOveul/SIPI43PhRD4trttg4ywnrEmmJpA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.AspNetCore.TestHost": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "Kks+OpQlP/eWQhnTjkiv0H9kc9Uqa7ieAzNV62yJpZ8Ips/WwpfpwrwZUKzV83CBJaBl5OI7J2XxVVTC8vah/Q==" + }, + "Microsoft.AspNetCore.WebUtilities": { + "type": "Transitive", + "resolved": "2.3.0", + "contentHash": "trbXdWzoAEUVd0PE2yTopkz4kjZaAIA7xUWekd5uBw+7xE8Do/YOVTeb9d9koPTlbtZT539aESJjSLSqD8eYrQ==", + "dependencies": { + "Microsoft.Net.Http.Headers": "2.3.0" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.Bcl.Cryptography": { + "type": "Transitive", + "resolved": "10.0.2", + "contentHash": "LG9Yll3B5aNpxv0+D47g6LiOiKBIlodhcHdQwcYzo8VeexFLGqx5ymetmA2aBRyo9cCcWsQWrFsdbsr8LvmWDw==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.3.4", + "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.8.0", + "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.4" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.8.0", + "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.8.0]" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "bOzrFCl6uZCjaSh2bG1ToRQRdx+iXvxosCg9hFyG9OWeAzOFI4xev9OqKeWfKf/kAHyox2JnbcvLVf2ceA7sqA==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "2gLDordUCGf3aNOOuqtTbP5mxhiP9nk6TnvGiE3RnqT891O+Zf/qKu1PIREubs1M16A0SImr4vULBfU5BTDs1Q==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "4ZFBNE+jzR+CrWWlhOesnmywCW7pYKT0dxyAQRdL11yJwxe4jvcAu31eorFtEkoFeCDcUTeNssgPv2yaRRptaQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "N1w5H7uK6gCTnCBZAWzE0/EQYSPysij/uYwDqntqBVvBa6bjMmBKitsnEFd6yh/SX3wLm67nO6+OnZ84K+gZWg==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "plJWK2zpWuuyxI8F8s2scx6Je7N1Ajjs6HvYUGKwRnDMWIVIz9FHwAkiT7ASgrvAOd10T0FPVlh9BzAJJME+jg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5Vnd2I75DmZCVEjSynIdJ/0EGafgnLQwgR3t2C2/fkjx/nRG+cLwxLLdInoHeCEpkD5K4Ov/g9ZCRYrl4TRsaA==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "GqmN2o1CkJvk7uWp+p4CwBYW0w/zfoEbvsiFDbO2G8l1Uz+mrDAbAcZiXhU2lufKPby1cjAUdd5GTWpebYOkOA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.CommandLine": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "33cBeR2HRbzHUTtmcmLdNOApneNGcymwwL4arHuotgVK9Frba8kcDTrvVTj7cSCmF1R9OiSbZH0KxNOwab3HUg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.EnvironmentVariables": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "KRfFSSCV58vEdU7mPED/YMzeovIWF5P0g8s9K8n9HEfy0/WzMq37SrPdXdFN5/dFT/rPMHpF7AvpoXHckbcBFg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "ZOhZYwvbXGTgGVRwswIirofEMVHuWdxjdh0JeUZXwaF9cgcjXdz/t0ELtgaevw7ezTyv47yPNCgGreWtLkn3IQ==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.Json": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "uvJ6sHwjgrkMEJOgiC76G0mcZGXerwyyWkwX34EOjCbxKG6TCtfAoqDKAMsCvEBf9HxjlGQEgqsSMOGCmGBf+A==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Configuration.UserSecrets": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "1s1sKFTk/Foam64JY6+m/diH8drL3Wx6V3gtSd5v1IEZtszZYyc1pW8uRnMblzpNiR0l0t8gGk7tXj3xHzFgdg==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Json": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "ANyvsgkNBRvcJh2XLgn8veGmajf+8m0AbKK+HPWdRL1yraSNVVSmQhFntLtdz/C795jxqqup+k05cs/3jZQPOA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "Kr/e7lUf4+N8tacbqJ2Ctwe/HarKdAc9ZkgKVVqvtJDBKbez+T/KnUwu82KSlnBp/SrpBcxc7u7xkE2oUZT/5Q==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "9uWiKpeOVac355STyChWR/pliFX/5CeLqChW9kKsaxyDH4EUTZxMkT4Jwp/J/peLm0GBFmSX5c0WCse3yCnq1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "c5zqFCY9DiIpMovLd7/d/CTiEtrMOuQ639dhv3PABtKQIKNQikSHwQt8+N679uii9q+B55lgK28Uv64FOwEu8w==", + "dependencies": { + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "jhJAyo38kSrH3ARvWUk0h8itogVnQu2DCZuPo+s0Z+tXes0ugTxMPaHYzap85785eHQmPFqD9TYERqBbtGxn/w==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileSystemGlobbing": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "jSOCVxEwCd4Aq925kJVz1kSO1EpX2OHYKL04qVREXkDU7Ce3pVDdHPYm+fEy8y/th2kJf/DAstRHpJAqoNWP8w==" + }, + "Microsoft.Extensions.Hosting": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "tL9FkfV64GPUDSPvwrgyw42LVzsnVAnyrqJEuZVJbODgrQ3eL63zmzEcVWoCHzfgqUhWggzbgAyUCnz/zfI3Pg==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.Configuration.CommandLine": "10.0.10", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.10", + "Microsoft.Extensions.Configuration.FileExtensions": "10.0.10", + "Microsoft.Extensions.Configuration.Json": "10.0.10", + "Microsoft.Extensions.Configuration.UserSecrets": "10.0.10", + "Microsoft.Extensions.DependencyInjection": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Diagnostics": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Physical": "10.0.10", + "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Configuration": "10.0.10", + "Microsoft.Extensions.Logging.Console": "10.0.10", + "Microsoft.Extensions.Logging.Debug": "10.0.10", + "Microsoft.Extensions.Logging.EventLog": "10.0.10", + "Microsoft.Extensions.Logging.EventSource": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.Hosting.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5LugpYGHk+mkn0a8IZgcyfBca8PCTAU9RQFoMrTdtOOidq88M2SI5f3px6ugnzgxC+eTkvYYJi8pzlUnG5xdAQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "Tf6z5HsL0VDYRTfvsoNrTGHGheCwkTsZBA2FFh5ATJUbkAwug+FFNISJK2gjpUNemlAOoWllAK52HOWCjto3EQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Configuration": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "cLrqxkuEfcilZ8SjK+9KAnpLk9lOoMPaOokF+wRUYie+iUEcdX4/p/+gJkt0BYgWLthjpBUCkVTBI6Kxg0nsOw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Console": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "VIlNzPwPS0GeQVSmCqqo36ugryX3LpE9ul6gEkks5VLET3weH/XMLeWmclwfoGn4Nxi2mwVibB+OZBVJ9tDqvg==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Configuration": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.Debug": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "8+TZBnV5fgBXoVNJ5ROSErUwYogk4hOgV7c2HWK1u5cqKGmiUTUn7+KqZ35iQu8e/B7Ykccyz5OTjdXcidNZ9g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.EventLog": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "0RE4951AzQ+YD4gVrvbq0BhdsiBgSDo44yM7+QBZ2mrmMJeNjY+teCIYfUjqDPVYnKs0HR6SkkhgrX1YgXZq3Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "System.Diagnostics.EventLog": "10.0.10" + } + }, + "Microsoft.Extensions.Logging.EventSource": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "85SAPwXhJtdBInzN2k7SChiFiBGh3KOWay5AfoY+GREF6P7oZA98+ST2p7Z9384iLKYjkZSKIZ/FqIO5aojtNw==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "8.0.11", + "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "tnBmu/LwF25ZQK+HBNCu2xrwnkKoB/XEbJyooGGoYxHrhvxbSKi7eOFiJ4AXBy/QU4vtCvCJfoi8k9Ej72qzOQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Configuration.Binder": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ==" + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "HJbo/lnSfNHUfphPRT910poQc4T2/9+8svFLvzuaYHGAOJ2Tu+oEDqpX0BVP3BJ4OuUM1kylEKyaiX2fCAK3Cw==" + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "ui3fuBT4fs8kdKfBthI4NzLYIBIneEVS8UrL1JVBzAn80UiKmngBBi0BEByE7n/9c+EElcfFlCMFFTqpkBSLNA==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.19.2" + } + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "r5YLDIxGOnkVJHrqXv/iD1FM1CgGrQOdriXuvuWvTPmKbnGANhEysq9XKmN6IHjf2a+9bAGEpnoRBsAQlvJU5w==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "8.19.2" + } + }, + "Microsoft.IdentityModel.Protocols": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "sGxSsSrZXNmca6D+jHH2rVRyo2nNRd/g4H9CFbPmLLq0xgoH1U0orLWE5minfijw7+zq49tBs7txenbfAErRoQ==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "8.19.2" + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "1XOcyY36cVymzE3qKdzKaUEZ4Pzt7ZpSa14JZoPPK1NLFUkQDs85TCqpV6XDo0YjFXj6nVK00AfOHppjghjhtw==", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "8.19.2", + "System.IdentityModel.Tokens.Jwt": "8.19.2" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "GtPC1S02uH1gOO4fQ+zRysIicKmEXaYFP8PIkdJYXqMyruYhopre4ozVHp0XiDSA0+GJvOZH9prxCPvgBMg4Ww==", + "dependencies": { + "Microsoft.Bcl.Cryptography": "10.0.2", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.IdentityModel.Logging": "8.19.2" + } + }, + "Microsoft.Net.Http.Headers": { + "type": "Transitive", + "resolved": "2.3.8", + "contentHash": "JO60u/VVUdaZfv4XQ//zgcH54y8rnxdpcvXnsDqWLKB4adDKaCiaozixDfQ/6H+PKYfkNV2CL8b8U+F9mciE3Q==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "1.9.1", + "contentHash": "No5AudZMmSb+uNXjlgL2y3/stHD2IT4uxqc5yHwkE+/nNux9jbKcaJMvcp9SwgP4DVD8L9/P3OUz8mmmcvEIdQ==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.23.0", + "Microsoft.Testing.Platform": "1.9.1" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.9.1", + "contentHash": "AL46Xe1WBi85Ntd4mNPvat5ZSsZ2uejiVqoKCypr8J3wK0elA5xJ3AN4G/Q4GIwzUFnggZoH/DBjnr9J18IO/g==", + "dependencies": { + "Microsoft.Testing.Platform": "1.9.1" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.9.1", + "contentHash": "QafNtNSmEI0zazdebnsIkDKmFtTSpmx/5PLOjURWwozcPb3tvRxzosQSL8xwYNM1iPhhKiBksXZyRSE2COisrA==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.9.1", + "contentHash": "oTUtyR4X/s9ytuiNA29FGsNCCH0rNmY5Wdm14NCKLjTM1cT9edVSlA+rGS/mVmusPqcP0l/x9qOnMXg16v87RQ==", + "dependencies": { + "Microsoft.Testing.Platform": "1.9.1" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==" + }, + "Namotion.Reflection": { + "type": "Transitive", + "resolved": "2.1.2", + "contentHash": "7tSHAzX8GWKy0qrW6OgQWD7kAZiqzhq+m1503qczuwuK6ZYhOGCQUxw+F3F4KkRM70aB6RMslsRVSCFeouIehw==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.4", + "contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A==" + }, + "NJsonSchema": { + "type": "Transitive", + "resolved": "10.9.0", + "contentHash": "IBPo6Srxn2MEcIFM3HdM4QImrJbsIeujENQyzHL2Pv6wLsKSYAyAEilecRqaLOhoy3snEiPLx7hhv7opbhOxKQ==", + "dependencies": { + "Namotion.Reflection": "2.1.2", + "Newtonsoft.Json": "9.0.1" + } + }, + "NJsonSchema.Extensions": { + "type": "Transitive", + "resolved": "0.2.0", + "contentHash": "zLHUfuCmnaaQbKxqvTALrxhXV6Pbdy4G3ZlAI+7oaXdJmSyQPpMHGcxDBmw0+qHziT7jVImxU1BjcidKJHeprg==", + "dependencies": { + "NJsonSchema": "10.9.0" + } + }, + "Npgsql": { + "type": "Transitive", + "resolved": "10.0.3", + "contentHash": "7nb5YzXuvWWJxB0J8DiyL3we+X4FOctZrt0fIBnucOIaIevFEEwGQVZKtiu9olXdlNAK1eNgqSral6r/jlhI4w==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "10.0.0" + } + }, + "NSwag.Core": { + "type": "Transitive", + "resolved": "13.16.1", + "contentHash": "xiX+H3Bv6zxrqJExPepO5WQVutkDUMdlUA3NqQ8VguwsYwJlkV05eF8XvmbJn/yGJWUag7vLImuXAoj0/327Bg==", + "dependencies": { + "NJsonSchema": "10.7.2", + "Newtonsoft.Json": "9.0.1" + } + }, + "OpenTelemetry": { + "type": "Transitive", + "resolved": "1.15.3", + "contentHash": "N0i6WjPoHPbZyms1ugbDIFAJFuGlpeExJMU/+XSL0lQRUkg/D0utFkDoLXf8Z1km5B+xVZ2GyMXXiX8qdeNmPg==", + "dependencies": { + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.0", + "Microsoft.Extensions.Logging.Configuration": "10.0.0", + "OpenTelemetry.Api.ProviderBuilderExtensions": "1.15.3" + } + }, + "OpenTelemetry.Api": { + "type": "Transitive", + "resolved": "1.15.3", + "contentHash": "fX+fkCysfPut+qCcT3bKqyX4QN9Saf4CgX8HLOHywEVD+Xr7sULtfuypITpoDysjx8R59dn/3mWhgimMH8cm/g==" + }, + "OpenTelemetry.Api.ProviderBuilderExtensions": { + "type": "Transitive", + "resolved": "1.15.3", + "contentHash": "SYn0lqYDwLMWhv/zlNGsQcl2yX++yTumanX46bmOZE/ZDOd1WjPBO2kZaZgKLEZTZk48pavIFGJ6vOvxXgWVFQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.0", + "OpenTelemetry.Api": "1.15.3" + } + }, + "OpenTelemetry.Exporter.OpenTelemetryProtocol": { + "type": "Transitive", + "resolved": "1.15.3", + "contentHash": "FEXJepcseTGbATiCkUfP7ipoFEYYfl/0UmmUwi0KxCPg9PaUA8ab2P1LGopK+/HExasJ1ZutFhZrN6WvUIR23g==", + "dependencies": { + "OpenTelemetry": "1.15.3" + } + }, + "OpenTelemetry.Extensions.Hosting": { + "type": "Transitive", + "resolved": "1.15.3", + "contentHash": "u8n/W8yIlqv0BXZmvId1iVaeWXG42tGKdTkuLYg5g57Y/r9CeUNzqtrSHNdG5IoO8iPX79w3v+WsbAHgUQbfeg==", + "dependencies": { + "Microsoft.Extensions.Hosting.Abstractions": "10.0.0", + "OpenTelemetry": "1.15.3" + } + }, + "OpenTelemetry.Instrumentation.AspNetCore": { + "type": "Transitive", + "resolved": "1.15.2", + "contentHash": "2nPd7r0ug/gd6/CNFL6Rlu+RSQ9WYGSGHAYQ1ssbSqyzKJpqTunfx2I/1O0WB5k+L0cyXbG4XVZpoSoUc3M7wg==", + "dependencies": { + "OpenTelemetry.Api.ProviderBuilderExtensions": "[1.15.3, 2.0.0)" + } + }, + "protobuf-net": { + "type": "Transitive", + "resolved": "3.2.52", + "contentHash": "XbZurNU3B/VaL/5OJ0kshO+AWxsZroI1saKuLfZpDwH2ngb2K9bdF1nIW6elFOViZw7TQCmfVZapxrMKCDqecQ==", + "dependencies": { + "protobuf-net.Core": "3.2.52" + } + }, + "protobuf-net.Core": { + "type": "Transitive", + "resolved": "3.2.52", + "contentHash": "zOpGtUo2QTgbsiI0D0yCe8aUTgDPov6kqIu1CDHI6isqhYcAHdirRrdnfsQXmAUfAWx1LwVYGgC6xe6fNS4UAg==" + }, + "ProtoBufJsonConverter": { + "type": "Transitive", + "resolved": "0.11.0", + "contentHash": "lxvcZQlCtgYZpfm9hhAJVZ1jsPkb9g3fyaAOnQLyEu8MiAowEIdED6jzwfjqLqOhY4AahqnbfRvZ904Ud43X7w==", + "dependencies": { + "MetadataReferenceService.Default": "0.0.1", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", + "Newtonsoft.Json": "13.0.3", + "Stef.Validation": "0.1.1", + "protobuf-net": "3.2.52" + } + }, + "RamlToOpenApiConverter.SourceOnly": { + "type": "Transitive", + "resolved": "0.21.0", + "contentHash": "x0g2c4tgPC5i+ZofhlhSeiWAsOjzkatv523tKGglx0mL05JYevQ5sYPP4r0xthpRAv99/6EuBJ6TbbipsHTMzA==" + }, + "RandomDataGenerator.Net": { + "type": "Transitive", + "resolved": "1.0.19.1", + "contentHash": "OkAqBA69VbYYg+biX2DYWcucOI/yEivkdJ/XPqife/mQAC0r/NArcAU/EI3i/1oRFUUCjibV0b7qEjK+TYTqDw==", + "dependencies": { + "Fare": "2.2.1", + "Stef.Validation": "0.1.1" + } + }, + "Scriban.Signed": { + "type": "Transitive", + "resolved": "7.2.5", + "contentHash": "Fu1AjcAyrZIAW9LIhxVgVyo5EMVdwLhXagKKI2A1UZoI0Wvz2CiRT+VXp1tuiMq2JhlkjVyebj/JQcF4koZacg==" + }, + "SharpYaml": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "/iwULhVBpTjD4wPZhLU+eUWBanDvri/2AGx5YbaAj5kp9kXzhqUfJEy56H5Yi+c+OXsdm/oKD1aTKB24BFp8cw==" + }, + "SharpZipLib": { + "type": "Transitive", + "resolved": "1.4.2", + "contentHash": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==" + }, + "SimMetrics.Net": { + "type": "Transitive", + "resolved": "1.0.5", + "contentHash": "LaSDYOJDh2WncgRboqiWtk/Igqoim/LV7v808qBeWY/f36Ol5oEKguEYpKrWw5ap8KYP0SRXf7/v3zil9koY6Q==" + }, + "SSH.NET": { + "type": "Transitive", + "resolved": "2025.1.0", + "contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", + "dependencies": { + "BouncyCastle.Cryptography": "2.6.2", + "Microsoft.Extensions.Logging.Abstractions": "8.0.3" + } + }, + "Stef.Validation": { + "type": "Transitive", + "resolved": "0.3.0", + "contentHash": "OfzmxQMK4eBzmobph43p1NsLTgVAC3XGTcvQS0odhsdL6uS7UsFzBMK6S9mAfIijZdLW4q98aZ3dtTOeTaQo6Q==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "OvGz3PrzuAI/Sj7LTcXcCe3FClRI1IyRMZjNONcZtFh+Ww7nAtSh4kh08r8KVe/xxkXJPjR0Y1jF7H+N42d4xQ==" + }, + "System.IdentityModel.Tokens.Jwt": { + "type": "Transitive", + "resolved": "8.19.2", + "contentHash": "gqhDC/icByKEutygpr+OFgAmjwTVowyzFjWB8K0q1ww8uFj5a1BQNL+QvijUl9uhq4p8OdDwcAIrjVC+eC9FVA==", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "8.19.2", + "Microsoft.IdentityModel.Tokens": "8.19.2" + } + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "Testcontainers": { + "type": "Transitive", + "resolved": "4.13.0", + "contentHash": "j8vi9jPBNSwaraGGx8w+2gtZyWrlbKxdhiGMS3nektg+KiwjFWx9ghCjs57EoQfvI+IAbzti0oQJupQChwgMog==", + "dependencies": { + "Docker.DotNet.Enhanced": "4.3.3", + "Docker.DotNet.Enhanced.X509": "4.3.3", + "Microsoft.Extensions.Logging.Abstractions": "8.0.3", + "SSH.NET": "2025.1.0", + "SharpZipLib": "1.4.2" + } + }, + "TinyMapper.Signed": { + "type": "Transitive", + "resolved": "4.0.0", + "contentHash": "W5uc9QXp8PUgP3VQ1Qyt3vK8ptyjj38tJ7nEAtRKA6R/4e6+2gsgYrAmRg9fCK1hhe3E0yeAm5acC14qx2CINg==" + }, + "WireMock.Net.Abstractions": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "7ZmPVJxlSBj0E7d47PHLydz15w0TEJ6WH2m7T/hucT3QfxbhN07AV0mwBoyO/T6jv+Af+hgEgmlep3v9TACxPw==" + }, + "WireMock.Net.GraphQL": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "3nMUB7E8ner6fpdOZe91qx/1O3ebEOke1dCeCw+wIvnmtPhlfdOuss2saICqc1nrwLv2bAOVQ29606Ayinf7kw==", + "dependencies": { + "GraphQL.NewtonsoftJson": "8.5.0", + "WireMock.Net.Shared": "2.13.0" + } + }, + "WireMock.Net.Matchers.SystemTextJsonPath": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "uhuQW2mFvi2X3v5Om3nDYYaF/ApkDHsFB0A1pklyLJnBUp1iUp+6Imt3t0j1bpMggHKaXJXEhDCqoDxvGP4FIQ==", + "dependencies": { + "JsonPath.Net": "3.0.2", + "WireMock.Net.Shared": "2.13.0" + } + }, + "WireMock.Net.MimePart": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "bFJsUJ+zKOZC8FMO+8kyRHW51Qt5DkKRKS7//dzuBcuJVY2XdEnOdfaAlMfnN9T73XrHCi8skU+G1xdfWD2x3g==", + "dependencies": { + "Stef.Validation": "0.3.0", + "WireMock.Net.Shared": "2.13.0" + } + }, + "WireMock.Net.Minimal": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "7VIKKuEiAHa19x7rbyf1s90UymTVQVTXz1CZc9mUM7DdW1LdaYyHUqBK4mBycvzsxuam7+jkU7l+aMJGAb0kBA==", + "dependencies": { + "JmesPath.Net": "1.1.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.34.0", + "NJsonSchema.Extensions": "0.2.0", + "NSwag.Core": "13.16.1", + "Scriban.Signed": "7.2.5", + "SimMetrics.Net": "1.0.5", + "TinyMapper.Signed": "4.0.0", + "WireMock.Net.OpenApiParser": "2.13.0", + "WireMock.Net.Shared": "2.13.0", + "WireMock.Org.Abstractions": "2.13.0" + } + }, + "WireMock.Net.OpenApiParser": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "XQ2hgycULdhp1F16OqixwDwz2zaPtk3rpdurmL9MLZjr8TUWMCyHfStXeaU/vHu/of/xnYAh+qB0dwtdmGwY0Q==", + "dependencies": { + "Newtonsoft.Json": "13.0.4", + "RamlToOpenApiConverter.SourceOnly": "0.21.0", + "RandomDataGenerator.Net": "1.0.19.1", + "SharpYaml": "2.1.4", + "Stef.Validation": "0.3.0", + "WireMock.Net.Abstractions": "2.13.0", + "YamlDotNet": "18.1.0" + } + }, + "WireMock.Net.OpenTelemetry": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "3SjRQcAd1pPZXB7jtj7vx7cbWdkskQl030W2QJldIOf+P9VtvKPJ5SsNCKI43Eg5O7RyrYuy2AQVdik7lbUi/Q==", + "dependencies": { + "OpenTelemetry.Exporter.OpenTelemetryProtocol": "1.15.3", + "OpenTelemetry.Extensions.Hosting": "1.15.3", + "OpenTelemetry.Instrumentation.AspNetCore": "1.15.2", + "WireMock.Net.Shared": "2.13.0" + } + }, + "WireMock.Net.ProtoBuf": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "WRG6cujOXRe3ZiStkElH82lOlDhS3YiV/cpXQbnCLpTkf+F/RBmrivzqO7ILcTccVKdwiIbT+ANdCR0vVCXZMg==", + "dependencies": { + "ProtoBufJsonConverter": "0.11.0", + "WireMock.Net.Shared": "2.13.0" + } + }, + "WireMock.Net.Shared": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "vvA0ssOFv3IoQI5UL5dr3mtAgF5imyit999sWEJYtXp7lLeA/fGvmbYLEpG5CeQ1RRtEcIxnJkRdtcttYmIC4Q==", + "dependencies": { + "AnyOf": "0.5.0.1", + "Handlebars.Net.Helpers": "2.5.5", + "Handlebars.Net.Helpers.Humanizer": "2.5.5", + "Handlebars.Net.Helpers.Json": "2.5.5", + "Handlebars.Net.Helpers.Random": "2.5.5", + "Handlebars.Net.Helpers.XPath": "2.5.5", + "Handlebars.Net.Helpers.Xeger": "2.5.5", + "Handlebars.Net.Helpers.Xslt": "2.5.5", + "JsonConverter.Newtonsoft.Json": "0.13.0", + "JsonConverter.System.Text.Json": "0.13.0", + "Microsoft.AspNetCore.Http": "2.3.9", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", + "Stef.Validation": "0.3.0", + "WireMock.Net.Abstractions": "2.13.0" + } + }, + "WireMock.Org.Abstractions": { + "type": "Transitive", + "resolved": "2.13.0", + "contentHash": "1z7W3ryp+xufZ77ux3NODNMl/jw00Koagpj8aOaFQQgOQuxJMd8hICi1ErF1DsIXd8Ewp13s2HGntj4yWeRfRA==" + }, + "XPath2": { + "type": "Transitive", + "resolved": "1.1.5", + "contentHash": "LQg7kZyAmmb+qvv5TiOuuijxN97rRbR05qbMkVIH+i+sx9CA2UNUKGNtdVxWEXOabS8BIwlXm6ox1OOTjvZ6jw==" + }, + "XPath2.Extensions": { + "type": "Transitive", + "resolved": "1.1.5", + "contentHash": "oEbdGUJsF25QL3Vj1GgSlT2xdbxnka5dKcjuA9CouWCV/l9ecSfypOv78B1+YUD8a8w47prLNw2i3ofLNcrbGA==", + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "XPath2": "1.1.5" + } + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.27.0", + "contentHash": "y/pxIQaLvk/kxAoDkZW9GnHLCEqzwl5TW0vtX3pweyQpjizB9y3DXhb9pkw2dGeUqhLjsxvvJM1k89JowU6z3g==" + }, + "xunit.v3.assert": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "BPciBghgEEaJN/JG00QfCYDfEfnLgQhfnYEy+j1izoeHVNYd5+3Wm8GJ6JgYysOhpBPYGE+sbf75JtrRc7jrdA==" + }, + "xunit.v3.common": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "Hj775PEH6GTbbg0wfKRvG2hNspDCvTH9irXhH4qIWgdrOSV1sQlqPie+DOvFeigsFg2fxSM3ZAaaCDQs+KreFA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0" + } + }, + "xunit.v3.core.mtp-v1": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "Ga5aA2Ca9ktz+5k3g5ukzwfexwoqwDUpV6z7atSEUvqtd6JuybU1XopHqg1oFd78QdTfZgZE9h5sHpO4qYIi5w==", + "dependencies": { + "Microsoft.Testing.Extensions.Telemetry": "1.9.1", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.9.1", + "Microsoft.Testing.Platform": "1.9.1", + "Microsoft.Testing.Platform.MSBuild": "1.9.1", + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.inproc.console": "[3.2.2]" + } + }, + "xunit.v3.extensibility.core": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "srY8z/oMPvh/t8axtO2DwrHajhFMH7tnqKildvYrVQIfICi8fOn3yIBWkVPAcrKmHMwvXRJ/XsQM3VMR6DOYfQ==", + "dependencies": { + "xunit.v3.common": "[3.2.2]" + } + }, + "xunit.v3.mtp-v1": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "O41aAzYKBT5PWqATa1oEWVNCyEUypFQ4va6K0kz37dduV3EKzXNMaV2UnEhufzU4Cce1I33gg0oldS8tGL5I0A==", + "dependencies": { + "xunit.analyzers": "1.27.0", + "xunit.v3.assert": "[3.2.2]", + "xunit.v3.core.mtp-v1": "[3.2.2]" + } + }, + "xunit.v3.runner.common": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "/hkHkQCzGrugelOAehprm7RIWdsUFVmIVaD6jDH/8DNGCymTlKKPTbGokD5czbAfqfex47mBP0sb0zbHYwrO/g==", + "dependencies": { + "Microsoft.Win32.Registry": "[5.0.0]", + "xunit.v3.common": "[3.2.2]" + } + }, + "xunit.v3.runner.inproc.console": { + "type": "Transitive", + "resolved": "3.2.2", + "contentHash": "ulWOdSvCk+bPXijJZ73bth9NyoOHsAs1ZOvamYbCkD4DNLX/Bd29Ve2ZNUwBbK0MqfIYWXHZViy/HKrdEC/izw==", + "dependencies": { + "xunit.v3.extensibility.core": "[3.2.2]", + "xunit.v3.runner.common": "[3.2.2]" + } + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "18.1.0", + "contentHash": "5K+9KFg2TdTl7VXv88Qzi/0lqK6JFoNP3lRuImPYGRV7K/QYklDyTrj4+A+KAki1JsQi6qKY+hDyY7d6WRqjrw==" + }, + "dodossh.api": { + "type": "Project", + "dependencies": { + "DodoSSH.Contracts": "[1.0.0, )", + "DodoSSH.Crypto": "[1.0.0, )", + "DodoSSH.Domain": "[1.0.0, )", + "DodoSSH.Infrastructure": "[1.0.0, )", + "Microsoft.AspNetCore.Authentication.JwtBearer": "[10.0.10, )", + "Microsoft.AspNetCore.OpenApi": "[10.0.10, )" + } + }, + "dodossh.contracts": { + "type": "Project" + }, + "dodossh.crypto": { + "type": "Project", + "dependencies": { + "NSec.Cryptography": "[26.4.0, )" + } + }, + "dodossh.domain": { + "type": "Project" + }, + "dodossh.infrastructure": { + "type": "Project", + "dependencies": { + "DodoSSH.Domain": "[1.0.0, )", + "EFCore.NamingConventions": "[10.0.1, )", + "Npgsql.EntityFrameworkCore.PostgreSQL": "[10.0.3, )" + } + }, + "BouncyCastle.Cryptography": { + "type": "CentralTransitive", + "requested": "[2.6.2, )", + "resolved": "2.6.2", + "contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" + }, + "EFCore.NamingConventions": { + "type": "CentralTransitive", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "Xs5k8XfNKPkkQSkGmZkmDI1je0prLTdxse+s8PgTFZxyBrlrTLzTBUTVJtQKSsbvu4y+luAv8DdtO5SALJE++A==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[10.0.1, 11.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[10.0.1, 11.0.0)", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1" + } + }, + "libsodium": { + "type": "CentralTransitive", + "requested": "[1.0.22, )", + "resolved": "1.0.22", + "contentHash": "KPD9SloJFclrsjnhABu7dzWrcyYkwPbvx5l1gRSPAX/0n+OBtSiVCKtGFv4n+ecWUHU0tCG9LSSwoZZx673zBQ==" + }, + "Microsoft.AspNetCore.Authentication.JwtBearer": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "VAcqS42zb9WJd9DjPdkVTS5YrQENmNzPNJuRu8VAW7x3TEWUipc4d4hHzVJdFB0h/KLdr4XcXZzRHcUOKVanMQ==", + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.19.2" + } + }, + "Microsoft.AspNetCore.OpenApi": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "d4Atx9IHq7JgX0F/h7Db+m9zAUzC+cKdI9k+OWnnyQIOUQtfvjIEuhvbjPigVMkAmPUgCbJ8Yp6M9ghUqHtJSQ==", + "dependencies": { + "Microsoft.OpenApi": "2.0.0" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "a0V7zj/VbYP6dTdWpUgE/r2PuLKtUGe2aJ0lVKkn/wP9ZhaxUz2kQydVfvOjCv2SKxlrqdBfHhPD4Cvlf+4ffA==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.10", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.10", + "Microsoft.Extensions.Caching.Memory": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10" + } + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "CentralTransitive", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "wNonj40aZxia+GtuBiiD6ZqVh4h6y5Nje1bGdmzZ8/ui0QRsAN+S0SIrLHFCEGbG9cDbeaE40sh+Lr7o9rRs6g==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "10.0.10", + "Microsoft.Extensions.Caching.Memory": "10.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging": "10.0.10" + } + }, + "Microsoft.OpenApi": { + "type": "CentralTransitive", + "requested": "[2.11.0, )", + "resolved": "2.11.0", + "contentHash": "/ignjfdeKT2SGLIR7QEv19KnI0rvoxRG/TYDOZdK9EsWLjKK9IK8i1Mo5NRm9PRV3i64DzlTqnIflWvoyfljLg==" + }, + "Npgsql.EntityFrameworkCore.PostgreSQL": { + "type": "CentralTransitive", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "IPGrrZnRkuW7OlHDhUESZz4G5DLkW7Nej/O3Cx+0iTsgyU5XJxBgpsvTHLloo3WWuAKKbDHXBvWPVkX1deRh1Q==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "[10.0.4, 11.0.0)", + "Microsoft.EntityFrameworkCore.Relational": "[10.0.4, 11.0.0)", + "Npgsql": "10.0.3" + } + }, + "NSec.Cryptography": { + "type": "CentralTransitive", + "requested": "[26.4.0, )", + "resolved": "26.4.0", + "contentHash": "0vsCtY5f+YgQROiWNqzgWp+l2pddfk9FkWoGV/bEo0MuEYPKlJWuoA8aOfO6qp3f+EnObKE3zSJhn1PspJeJVg==", + "dependencies": { + "libsodium": "[1.0.22, 1.0.23)" + } + } + } + } +} \ No newline at end of file