diff --git a/docs/api/asr-service.yaml b/docs/api/asr-service.yaml index 53f13c9..00e3634 100644 --- a/docs/api/asr-service.yaml +++ b/docs/api/asr-service.yaml @@ -48,7 +48,6 @@ paths: parameters: - name: schemaType in: query - required: true schema: $ref: '#/components/schemas/CredentialSchemaType' requestBody: diff --git a/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/ISchemaBusinessLogic.cs b/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/ISchemaBusinessLogic.cs index feb49a4..1aedf43 100644 --- a/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/ISchemaBusinessLogic.cs +++ b/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/ISchemaBusinessLogic.cs @@ -25,5 +25,5 @@ namespace Org.Eclipse.TractusX.SsiAuthoritySchemaRegistry.Service.BusinessLogic; public interface ISchemaBusinessLogic : ITransient { - Task Validate(CredentialSchemaType schemaType, JsonDocument content, CancellationToken cancellationToken); + Task Validate(CredentialSchemaType? schemaType, JsonDocument content); } diff --git a/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/SchemaBusinessLogic.cs b/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/SchemaBusinessLogic.cs index 9c0b7e2..d3636ca 100644 --- a/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/SchemaBusinessLogic.cs +++ b/src/registry/SsiAuthoritySchemaRegistry.Service/BusinessLogic/SchemaBusinessLogic.cs @@ -20,6 +20,7 @@ using Json.Schema; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.SsiAuthoritySchemaRegistry.Service.Models; +using System.Collections.Concurrent; using System.Reflection; using System.Text.Json; @@ -27,7 +28,7 @@ namespace Org.Eclipse.TractusX.SsiAuthoritySchemaRegistry.Service.BusinessLogic; public class SchemaBusinessLogic : ISchemaBusinessLogic { - public async Task Validate(CredentialSchemaType schemaType, JsonDocument content, CancellationToken cancellationToken) + public async Task Validate(CredentialSchemaType? schemaType, JsonDocument content) { var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (location == null) @@ -35,6 +36,30 @@ public async Task Validate(CredentialSchemaType schemaType, JsonDocument c throw new UnexpectedConditionException("Assembly location must be set"); } + if (schemaType is not null) + { + return await ValidateSchema(schemaType.Value, content, location).ConfigureAwait(ConfigureAwaitOptions.None); + } + + var options = new ParallelOptions + { + MaxDegreeOfParallelism = 2 + }; + + var typeResult = false; + await Parallel.ForEachAsync(Enum.GetValues(), options, async (type, _) => + { + if (await ValidateSchema(type, content, location).ConfigureAwait(ConfigureAwaitOptions.None)) + { + typeResult = true; + } + }) + .ConfigureAwait(ConfigureAwaitOptions.None); + return typeResult; + } + + private static async Task ValidateSchema(CredentialSchemaType schemaType, JsonDocument content, string location) + { var path = Path.Combine(location, "Schemas", $"{schemaType}.schema.json"); var schema = await JsonSchema.FromStream(File.OpenRead(path)).ConfigureAwait(false); SchemaRegistry.Global.Register(schema); diff --git a/src/registry/SsiAuthoritySchemaRegistry.Service/Controllers/SchemaController.cs b/src/registry/SsiAuthoritySchemaRegistry.Service/Controllers/SchemaController.cs index 8af03e9..bee583e 100644 --- a/src/registry/SsiAuthoritySchemaRegistry.Service/Controllers/SchemaController.cs +++ b/src/registry/SsiAuthoritySchemaRegistry.Service/Controllers/SchemaController.cs @@ -12,10 +12,10 @@ public static RouteGroupBuilder MapSchemaApi(this RouteGroupBuilder group) { var schema = group.MapGroup("/schema"); - schema.MapPost("validate", ([FromQuery] CredentialSchemaType schemaType, [FromBody] JsonDocument content, CancellationToken cancellationToken, ISchemaBusinessLogic logic) => logic.Validate(schemaType, content, cancellationToken)) + schema.MapPost("validate", ([FromBody] JsonDocument content, [FromServices] ISchemaBusinessLogic logic, [FromQuery] CredentialSchemaType? schemaType = null) => logic.Validate(schemaType, content)) .WithSwaggerDescription("Gets all credentials with optional filter possibilities", "Example: POST: api/schema/validate", - "The type of the schema that should be validated", + "Optional: The type of the schema that should be validated", "The schema as json") .WithDefaultResponses() .Produces(StatusCodes.Status200OK, typeof(bool), Constants.JsonContentType); diff --git a/tests/registry/SsiAuthoritySchemaRegistry.Service.Tests/Controllers/SchemaControllerTests.cs b/tests/registry/SsiAuthoritySchemaRegistry.Service.Tests/Controllers/SchemaControllerTests.cs index 7f24295..25ab9e6 100644 --- a/tests/registry/SsiAuthoritySchemaRegistry.Service.Tests/Controllers/SchemaControllerTests.cs +++ b/tests/registry/SsiAuthoritySchemaRegistry.Service.Tests/Controllers/SchemaControllerTests.cs @@ -27,7 +27,8 @@ namespace Org.Eclipse.TractusX.SsiAuthoritySchemaRegistry.Service.Tests.Controllers; -public class SchemaControllerTests(IntegrationTestFactory factory) : IClassFixture +public class SchemaControllerTests(IntegrationTestFactory factory) + : IClassFixture { private static readonly JsonSerializerOptions JsonOptions = new() { @@ -39,197 +40,156 @@ public class SchemaControllerTests(IntegrationTestFactory factory) : IClassFixtu private const string BaseUrl = "/api/schema"; private readonly HttpClient _client = factory.CreateClient(); + private readonly JsonDocument _businessPartnerSchema = JsonDocument.Parse(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "issuer": "test", + "type": [ + "VerifiableCredential", + "BpnCredential" + ], + "issuanceDate": "2024-05-29T01:01:01Z", + "expirationDate": "2024-05-29T01:01:01Z", + "credentialSubject": { + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "holderIdentifier": "BPNL00000001TEST", + "bpn": "BPNL00000003CRHL" + } + } + """); + + private readonly JsonDocument _frameworkSchema = JsonDocument.Parse(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "issuer": "test", + "type": [ + "VerifiableCredential", + "MembershipCredential" + ], + "issuanceDate": "2024-05-29T01:01:01Z", + "expirationDate": "2024-12-31T23:59:59Z", + "credentialSubject": { + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "holderIdentifier": "Test", + "group": "UseCaseFramework", + "useCase": "16c1029e-c4af-47e6-9c33-a144625cc7ac", + "contractTemplate": "https://example.org/temp-1", + "contractVersion": "1.0" + } + } + """); + + private readonly JsonDocument _dismantlerSchema = JsonDocument.Parse(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "issuer": "test", + "type": [ + "VerifiableCredential", + "MembershipCredential" + ], + "issuanceDate": "2024-05-29T01:01:01Z", + "expirationDate": "2024-12-31T23:59:59Z", + "credentialSubject": { + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "holderIdentifier": "Test", + "allowedVehicleBrands": [ "BMW", "Mercedes" ] + } + } + """); + + private readonly JsonDocument _membershipSchema = JsonDocument.Parse(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "issuer": "test", + "type": [ + "VerifiableCredential", + "MembershipCredential" + ], + "issuanceDate": "2024-05-29T01:01:01Z", + "expirationDate": "2024-12-31T23:59:59Z", + "credentialSubject": { + "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", + "holderIdentifier": "Test", + "memberOf": "catena-x" + } + } + """); + #region Validate - [Fact] - public async Task Validate_WithValidBusinessPartner_ReturnsTrue() + [Theory] + [InlineData(CredentialSchemaType.FrameworkCredential, true)] + [InlineData(CredentialSchemaType.FrameworkCredential, false)] + [InlineData(CredentialSchemaType.BusinessPartnerCredential, true)] + [InlineData(CredentialSchemaType.BusinessPartnerCredential, false)] + [InlineData(CredentialSchemaType.MembershipCredential, true)] + [InlineData(CredentialSchemaType.MembershipCredential, false)] + [InlineData(CredentialSchemaType.DismantlerCredential, true)] + [InlineData(CredentialSchemaType.DismantlerCredential, false)] + public async Task Validate_WithValidSchema_ReturnsTrue(CredentialSchemaType schemaType, bool withSchemaTypeQueryParam) { // Arrange - var json = JsonDocument.Parse(""" - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/security/suites/jws-2020/v1" - ], - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "issuer": "test", - "type": [ - "VerifiableCredential", - "BpnCredential" - ], - "issuanceDate": "2024-05-29T01:01:01Z", - "expirationDate": "2024-05-29T01:01:01Z", - "credentialSubject": { - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "holderIdentifier": "BPNL00000001TEST", - "bpn": "BPNL00000003CRHL" - } - } - """); + var json = schemaType switch + { + CredentialSchemaType.DismantlerCredential => _dismantlerSchema, + CredentialSchemaType.BusinessPartnerCredential => _businessPartnerSchema, + CredentialSchemaType.FrameworkCredential => _frameworkSchema, + CredentialSchemaType.MembershipCredential => _membershipSchema, + _ => throw new ArgumentOutOfRangeException(nameof(schemaType), schemaType, null) + }; // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.BusinessPartnerCredential}", json, JsonOptions); - - // Assert - var result = await data.Content.ReadFromJsonAsync(); - result.Should().BeTrue(); - } - - [Fact] - public async Task Validate_WithInvalidBusinessPartner_ReturnsFalse() - { - // Arrange - var json = JsonDocument.Parse("{}"); + var requestUri = $"{BaseUrl}/validate"; + if (withSchemaTypeQueryParam) + { + requestUri = $"{requestUri}?schemaType={schemaType}"; + } - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.BusinessPartnerCredential}", json, JsonOptions); - - // Assert - var result = await data.Content.ReadFromJsonAsync(JsonOptions); - result.Should().BeFalse(); - } - - [Fact] - public async Task Validate_WithValidDismantler_ReturnsTrue() - { - // Arrange - var schema = JsonDocument.Parse(""" - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/security/suites/jws-2020/v1" - ], - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "issuer": "test", - "type": [ - "VerifiableCredential", - "MembershipCredential" - ], - "issuanceDate": "2024-05-29T01:01:01Z", - "expirationDate": "2024-12-31T23:59:59Z", - "credentialSubject": { - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "holderIdentifier": "Test", - "allowedVehicleBrands": [ "BMW", "Mercedes" ] - } - } - """); - - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.DismantlerCredential}", schema, JsonOptions); + var data = await _client.PostAsJsonAsync(requestUri, json, JsonOptions); // Assert var result = await data.Content.ReadFromJsonAsync(); result.Should().BeTrue(); } - [Fact] - public async Task Validate_WithInvalidDismantler_ReturnsFalse() + [Theory] + [InlineData(CredentialSchemaType.FrameworkCredential, true)] + [InlineData(CredentialSchemaType.FrameworkCredential, false)] + [InlineData(CredentialSchemaType.BusinessPartnerCredential, true)] + [InlineData(CredentialSchemaType.BusinessPartnerCredential, false)] + [InlineData(CredentialSchemaType.MembershipCredential, true)] + [InlineData(CredentialSchemaType.MembershipCredential, false)] + [InlineData(CredentialSchemaType.DismantlerCredential, true)] + [InlineData(CredentialSchemaType.DismantlerCredential, false)] + public async Task Validate_WithInvalid_ReturnsFalse(CredentialSchemaType schemaType, bool withSchemaTypeQueryParam) { // Arrange var json = JsonDocument.Parse("{}"); // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.DismantlerCredential}", json, JsonOptions); + var requestUri = $"{BaseUrl}/validate"; + if (withSchemaTypeQueryParam) + { + requestUri = $"{requestUri}?schemaType={schemaType}"; + } - // Assert - var result = await data.Content.ReadFromJsonAsync(); - result.Should().BeFalse(); - } - - [Fact] - public async Task Validate_WithValidFramework_ReturnsTrue() - { - // Arrange - var json = JsonDocument.Parse(""" - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/security/suites/jws-2020/v1" - ], - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "issuer": "test", - "type": [ - "VerifiableCredential", - "MembershipCredential" - ], - "issuanceDate": "2024-05-29T01:01:01Z", - "expirationDate": "2024-12-31T23:59:59Z", - "credentialSubject": { - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "holderIdentifier": "Test", - "group": "UseCaseFramework", - "useCase": "16c1029e-c4af-47e6-9c33-a144625cc7ac", - "contractTemplate": "https://example.org/temp-1", - "contractVersion": "1.0" - } - } - """); - - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.FrameworkCredential}", json, JsonOptions); - - // Assert - var result = await data.Content.ReadFromJsonAsync(); - result.Should().BeTrue(); - } - - [Fact] - public async Task Validate_WithInvalidFramework_ReturnsFalse() - { - // Arrange - var json = JsonDocument.Parse("{}"); - - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.FrameworkCredential}", json, JsonOptions); - - // Assert - var result = await data.Content.ReadFromJsonAsync(); - result.Should().BeFalse(); - } - - [Fact] - public async Task Validate_WithValidMembership_ReturnsTrue() - { - // Arrange - var json = JsonDocument.Parse(""" - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/security/suites/jws-2020/v1" - ], - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "issuer": "test", - "type": [ - "VerifiableCredential", - "MembershipCredential" - ], - "issuanceDate": "2024-05-29T01:01:01Z", - "expirationDate": "2024-12-31T23:59:59Z", - "credentialSubject": { - "id": "6b74cd89-6c25-4e3c-9fb7-3ee15c803afc", - "holderIdentifier": "Test", - "memberOf": "catena-x" - } - } - """); - - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.MembershipCredential}", json, JsonOptions); - - // Assert - var result = await data.Content.ReadFromJsonAsync(); - result.Should().BeTrue(); - } - - [Fact] - public async Task Validate_WithInvalidMembership_ReturnsFalse() - { - // Arrange - var json = JsonDocument.Parse("{}"); - - // Act - var data = await _client.PostAsJsonAsync($"{BaseUrl}/validate?schemaType={CredentialSchemaType.MembershipCredential}", json, JsonOptions); + var data = await _client.PostAsJsonAsync(requestUri, json, JsonOptions); // Assert var result = await data.Content.ReadFromJsonAsync();