From e08caa2c8a3a27d2b4cc84b5ecfe75e906e7db5c Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Tue, 26 Mar 2024 11:20:05 +0100 Subject: [PATCH] feat(revocation): add endpoints to revoke credentials * add endpoint for issuer to revoke a credential * add endpoint for holder to revoke a credential * add logic to revoke credentials when they are expired Refs: #14 #15 #16 --- DEPENDENCIES | 1 - .../ExpiryCheckService.cs | 40 +- .../Models/DocumentData.cs | 5 +- .../Models/SsiApprovalData.cs | 2 + .../CompanySsiDetailsRepository.cs | 7 +- .../Repositories/CredentialRepository.cs | 25 + .../Repositories/DocumentRepository.cs | 13 + .../ICompanySsiDetailsRepository.cs | 2 +- .../Repositories/ICredentialRepository.cs | 4 + .../Repositories/IDocumentRepository.cs | 1 + .../Enums/ProcessStepTypeId.cs | 7 +- .../Enums/ProcessTypeId.cs | 1 + .../Enums/VerifiedCredentialExternalTypeId.cs | 15 +- .../Enums/VerifiedCredentialTypeId.cs | 15 +- .../20240327184333_1.0.0-rc.2.Designer.cs | 1482 +++++++++++++++++ .../Migrations/20240327184333_1.0.0-rc.2.cs | 170 ++ .../IssuerDbContextModelSnapshot.cs | 60 +- .../Seeder/Data/use_cases.json | 22 +- ...ternal_type_detail_versions.consortia.json | 36 +- ...dential_external_type_detail_versions.json | 34 +- ...edential_type_assigned_external_types.json | 12 + ...rified_credential_type_assigned_kinds.json | 12 + ...ed_credential_type_assigned_use_cases.json | 16 +- .../Portal.Service/Models/MailData.cs | 7 +- .../Portal.Service/Services/IPortalService.cs | 2 +- .../Portal.Service/Services/PortalService.cs | 2 +- .../Models/GetCredentialResponse.cs | 3 +- .../Models/RevokeCredentialRequest.cs | 30 + .../Services/IBasicAuthTokenService.cs | 2 +- .../Wallet.Service/Services/IWalletService.cs | 2 + .../Wallet.Service/Services/WalletService.cs | 26 + .../BusinessLogic/IRevocationBusinessLogic.cs | 28 + .../BusinessLogic/IssuerBusinessLogic.cs | 57 +- .../BusinessLogic/RevocationBusinessLogic.cs | 102 ++ .../Controllers/RevocationController.cs | 67 + .../RevocationServiceCollectionExtensions.cs | 29 + .../CredentialErrorMessageContainer.cs | 8 +- .../RevocationErrorMessageContainer.cs | 42 + .../Models/CredentialData.cs | 6 +- .../SsiCredentialIssuer.Service/Program.cs | 9 +- .../appsettings.json | 12 + .../CredentialCreationProcessHandler.cs} | 6 +- .../ICredentialCreationProcessHandler.cs} | 4 +- .../CredentialProcess.Library.csproj | 1 + .../CredentialHandlerExtensions.cs | 15 +- .../CredentialCreationProcessHandler.cs | 113 ++ .../Expiry/ICredentialExpiryProcessHandler.cs | 29 + .../CredentialCreationProcessTypeExecutor.cs} | 24 +- .../CredentialProcessCollectionExtensions.cs | 12 +- .../CredentialExpiryProcessTypeExecutor.cs | 109 ++ .../Processes.Library.csproj | 1 + src/processes/Processes.Worker/Program.cs | 5 +- .../Processes.Worker/appsettings.json | 11 +- .../ExpiryCheckServiceTests.cs | 20 +- .../CompanySsiDetailsRepositoryTests.cs | 32 +- .../CredentialRepositoryTests.cs | 33 + .../DocumentRepositoryTests.cs | 31 + ...al_external_type_detail_versions.test.json | 32 +- ...edential_type_assigned_use_cases.test.json | 4 - .../PortalServiceTests.cs | 4 +- .../Services/WalletServiceTests.cs | 110 +- .../BusinessLogic/IssuerBusinessLogicTests.cs | 87 +- .../RevocationBusinessLogicTests.cs | 239 +++ ... CredentialCreationProcessHandlerTests.cs} | 9 +- .../CredentialExpiryProcessHandlerTests.cs | 209 +++ ...entialCreationProcessTypeExecutorTests.cs} | 23 +- ...redentialExpiryProcessTypeExecutorTests.cs | 236 +++ 67 files changed, 3585 insertions(+), 230 deletions(-) create mode 100644 src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.Designer.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.cs create mode 100644 src/externalservices/Wallet.Service/Models/RevokeCredentialRequest.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IRevocationBusinessLogic.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/BusinessLogic/RevocationBusinessLogic.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Controllers/RevocationController.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/DependencyInjection/RevocationServiceCollectionExtensions.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/ErrorHandling/RevocationErrorMessageContainer.cs rename src/processes/CredentialProcess.Library/{CredentialProcessHandler.cs => Creation/CredentialCreationProcessHandler.cs} (96%) rename src/processes/CredentialProcess.Library/{ICredentialProcessHandler.cs => Creation/ICredentialCreationProcessHandler.cs} (96%) create mode 100644 src/processes/CredentialProcess.Library/Expiry/CredentialCreationProcessHandler.cs create mode 100644 src/processes/CredentialProcess.Library/Expiry/ICredentialExpiryProcessHandler.cs rename src/processes/CredentialProcess.Worker/{CredentialProcessTypeExecutor.cs => Creation/CredentialCreationProcessTypeExecutor.cs} (85%) create mode 100644 src/processes/CredentialProcess.Worker/Expiry/CredentialExpiryProcessTypeExecutor.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/RevocationBusinessLogicTests.cs rename tests/processes/CredentialProcess.Library.Tests/{CredentialProcessHandlerTests.cs => CredentialCreationProcessHandlerTests.cs} (97%) create mode 100644 tests/processes/CredentialProcess.Library.Tests/CredentialExpiryProcessHandlerTests.cs rename tests/processes/CredentialProcess.Worker.Tests/{CredentialProcessTypeExecutorTests.cs => CredentialCreationProcessTypeExecutorTests.cs} (90%) create mode 100644 tests/processes/CredentialProcess.Worker.Tests/CredentialExpiryProcessTypeExecutorTests.cs diff --git a/DEPENDENCIES b/DEPENDENCIES index fcae7a8a..ea71b550 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -15,7 +15,6 @@ nuget/nuget/-/JsonSchema.Net/6.0.3, MIT AND OFL-1.1 AND CC-BY-SA-4.0, approved, nuget/nuget/-/Laraue.EfCoreTriggers.Common/7.1.0, MIT, approved, #10247 nuget/nuget/-/Laraue.EfCoreTriggers.PostgreSql/7.1.0, MIT, approved, #10248 nuget/nuget/-/Mono.TextTemplating/2.2.1, MIT, approved, clearlydefined -nuget/nuget/-/Newtonsoft.Json/12.0.2, MIT AND BSD-3-Clause, approved, #11114 nuget/nuget/-/Newtonsoft.Json/13.0.1, MIT AND BSD-3-Clause, approved, #3266 nuget/nuget/-/Newtonsoft.Json/13.0.3, MIT AND BSD-3-Clause, approved, #3266 nuget/nuget/-/Npgsql.EntityFrameworkCore.PostgreSQL/7.0.11, PostgreSQL AND MIT AND Apache-2.0, approved, #10081 diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs b/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs index 729d7e4a..7b3d63d7 100644 --- a/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs @@ -80,6 +80,7 @@ public async Task ExecuteAsync(CancellationToken stoppingToken) var now = dateTimeProvider.OffsetNow; var companySsiDetailsRepository = repositories.GetInstance(); + var processStepRepository = repositories.GetInstance(); var inactiveVcsToDelete = now.AddDays(-(_settings.InactiveVcsToDeleteInWeeks * 7)); var expiredVcsToDelete = now.AddMonths(-_settings.ExpiredVcsToDeleteInMonth); @@ -87,8 +88,7 @@ public async Task ExecuteAsync(CancellationToken stoppingToken) .GetExpiryData(now, inactiveVcsToDelete, expiredVcsToDelete); await foreach (var credential in credentials.WithCancellation(stoppingToken).ConfigureAwait(false)) { - await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService, - stoppingToken); + await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService, processStepRepository, stoppingToken); } } catch (Exception ex) @@ -104,6 +104,7 @@ private static async Task ProcessCredentials( ICompanySsiDetailsRepository companySsiDetailsRepository, IIssuerRepositories repositories, IPortalService portalService, + IProcessStepRepository processStepRepository, CancellationToken cancellationToken) { if (data.ScheduleData.IsVcToDelete) @@ -112,7 +113,7 @@ private static async Task ProcessCredentials( } else if (data.ScheduleData.IsVcToDecline) { - await HandleDecline(data, companySsiDetailsRepository, portalService, cancellationToken).ConfigureAwait(false); + HandleDecline(data.Id, companySsiDetailsRepository, processStepRepository); } else { @@ -123,30 +124,21 @@ private static async Task ProcessCredentials( await repositories.SaveAsync().ConfigureAwait(false); } - private static async ValueTask HandleDecline( - CredentialExpiryData data, + private static void HandleDecline( + Guid credentialId, ICompanySsiDetailsRepository companySsiDetailsRepository, - IPortalService portalService, - CancellationToken cancellationToken) + IProcessStepRepository processStepRepository) { - var content = JsonSerializer.Serialize(new { Type = data.VerifiedCredentialTypeId, CredentialId = data.Id }, Options); - await portalService.AddNotification(content, data.RequesterId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken); - companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(data.Id, c => + var processId = processStepRepository.CreateProcess(ProcessTypeId.DECLINE_CREDENTIAL).Id; + processStepRepository.CreateProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, ProcessStepStatusId.TODO, processId); + companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(credentialId, c => { - c.CompanySsiDetailStatusId = data.CompanySsiDetailStatusId; + c.ProcessId = null; }, c => { - c.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + c.ProcessId = processId; }); - - var typeValue = data.VerifiedCredentialTypeId.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.VerifiedCredentialTypeId} does not exists"); - var mailParameters = new Dictionary - { - { "requestName", typeValue }, - { "reason", "The credential is already expired" } - }; - await portalService.TriggerMail("CredentialRejected", data.RequesterId, mailParameters, cancellationToken); } private static async ValueTask HandleNotification( @@ -184,11 +176,11 @@ private static async ValueTask HandleNotification( }, Options); await portalService.AddNotification(content, data.RequesterId, NotificationTypeId.CREDENTIAL_EXPIRY, cancellationToken); var typeValue = data.VerifiedCredentialTypeId.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.VerifiedCredentialTypeId} does not exists"); - var mailParameters = new Dictionary + var mailParameters = new MailParameter[] { - { "typeId", typeValue }, - { "version", data.DetailVersion ?? "no version" }, - { "expiryDate", data.ExpiryDate?.ToString("dd MMMM yyyy") ?? throw new ConflictException("Expiry Date must be set here") } + new("typeId", typeValue), + new("version", data.DetailVersion ?? "no version"), + new("expiryDate", data.ExpiryDate?.ToString("dd MMMM yyyy") ?? throw new ConflictException("Expiry Date must be set here")) }; await portalService.TriggerMail("CredentialExpiry", data.RequesterId, mailParameters, cancellationToken); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs index 5f2202d4..eea92f79 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs @@ -21,8 +21,7 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; -public record DocumentData -( +public record DocumentData( Guid DocumentId, string DocumentName, - DocumentTypeId argDocumentTypeId); + DocumentTypeId DocumentTypeId); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs index 2180ec38..0daf4b1a 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs @@ -18,6 +18,7 @@ ********************************************************************************/ using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Text.Json; namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; @@ -27,6 +28,7 @@ public record SsiApprovalData( Guid? ProcessId, VerifiedCredentialTypeKindId? Kind, string? Bpn, + JsonDocument? Schema, DetailData? DetailData ); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs index 0f230919..4faefc45 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs @@ -144,16 +144,16 @@ public Task CheckSsiDetailsExistsForCompany(string bpnl, VerifiedCredentia (verifiedCredentialExternalTypeUseCaseDetailId == null || x.VerifiedCredentialExternalTypeDetailVersionId == verifiedCredentialExternalTypeUseCaseDetailId)); /// - public Task<(bool Exists, string? Version, string? Template, IEnumerable UseCase, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId) => + public Task<(bool Exists, string? Version, string? Template, IEnumerable ExternalTypeIds, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId) => _context.VerifiedCredentialExternalTypeDetailVersions .Where(x => x.Id == verifiedCredentialExternalTypeUseCaseDetailId && x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Any(y => y.VerifiedCredentialTypeId == verifiedCredentialTypeId)) - .Select(x => new ValueTuple, DateTimeOffset>( + .Select(x => new ValueTuple, DateTimeOffset>( true, x.Version, x.Template, - x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Select(y => y.VerifiedCredentialType!.VerifiedCredentialTypeAssignedUseCase!.UseCase!.Shortname), + x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Select(y => y.VerifiedCredentialExternalTypeId), x.Expiry)) .SingleOrDefaultAsync(); @@ -188,6 +188,7 @@ public IQueryable GetAllCredentialDetails(CompanySsiDetailStat x.ProcessId, x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind == null ? null : x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId, x.Bpnl, + x.CompanySsiProcessData!.Schema, x.VerifiedCredentialExternalTypeDetailVersion == null ? null : new DetailData( diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs index 719103c5..48ed9b32 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs @@ -20,6 +20,7 @@ using Microsoft.EntityFrameworkCore; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using System.Text.Json; @@ -76,4 +77,28 @@ public CredentialRepository(IssuerDbContext dbContext) .Where(x => x.CompanySsiDetailId == credentialId) .Select(x => new ValueTuple(x.CompanySsiDetail!.Bpnl, x.CallbackUrl)) .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid? ExternalCredentialId, CompanySsiDetailStatusId StatusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> Documents)> GetRevocationDataById(Guid credentialId) => + _dbContext.CompanySsiDetails + .Where(x => x.Id == credentialId) + .Select(x => new ValueTuple>( + true, + x.ExternalCredentialId, + x.CompanySsiDetailStatusId, + x.Documents.Select(d => new ValueTuple(d.Id, d.DocumentStatusId)))) + .SingleOrDefaultAsync(); + + public void AttachAndModifyCredential(Guid credentialId, Action? initialize, Action modify) + { + var entity = new CompanySsiDetail(credentialId, string.Empty, default!, default!, null!, Guid.Empty, default!); + initialize?.Invoke(entity); + _dbContext.CompanySsiDetails.Attach(entity); + modify(entity); + } + + public Task<(VerifiedCredentialTypeId TypeId, Guid RequesterId)> GetCredentialNotificationData(Guid credentialId) => + _dbContext.CompanySsiDetails + .Where(x => x.Id == credentialId) + .Select(x => new ValueTuple(x.VerifiedCredentialTypeId, x.CreatorUserId)) + .SingleOrDefaultAsync(); } diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs index b6bf1b6d..5b2e01f3 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs @@ -60,4 +60,17 @@ public void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDe var document = new CompanySsiDetailAssignedDocument(documentId, companySsiDetailId); _dbContext.CompanySsiDetailAssignedDocuments.Add(document); } + + public void AttachAndModifyDocuments(IEnumerable<(Guid DocumentId, Action? Initialize, Action Modify)> documentData) + { + var initial = documentData.Select(x => + { + var document = new Document(x.DocumentId, null!, null!, null!, default, default, default, default); + x.Initialize?.Invoke(document); + return (Document: document, x.Modify); + } + ).ToList(); + _dbContext.AttachRange(initial.Select(x => x.Document)); + initial.ForEach(x => x.Modify(x.Document)); + } } diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs index 225a51e0..154f46b8 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs @@ -70,7 +70,7 @@ public interface ICompanySsiDetailsRepository /// Id of vc external type use case detail id /// Id of the vc type /// Returns a valueTuple with identifiers if the externalTypeUseCaseDetailId exists and the corresponding credentialTypeId - Task<(bool Exists, string? Version, string? Template, IEnumerable UseCase, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId); + Task<(bool Exists, string? Version, string? Template, IEnumerable ExternalTypeIds, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId); /// /// Checks whether the given credentialTypeId is a Certificate diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs index 3902ad2e..7b088c6a 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs @@ -18,6 +18,7 @@ ********************************************************************************/ using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using System.Text.Json; @@ -31,4 +32,7 @@ public interface ICredentialRepository Task<(VerifiedCredentialTypeKindId CredentialTypeKindId, JsonDocument Schema)> GetCredentialStorageInformationById(Guid credentialId); Task<(Guid? ExternalCredentialId, VerifiedCredentialTypeKindId KindId, bool HasEncryptionInformation, string? CallbackUrl)> GetExternalCredentialAndKindId(Guid credentialId); Task<(string Bpn, string? CallbackUrl)> GetCallbackUrl(Guid credentialId); + Task<(bool Exists, Guid? ExternalCredentialId, CompanySsiDetailStatusId StatusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> Documents)> GetRevocationDataById(Guid credentialId); + void AttachAndModifyCredential(Guid credentialId, Action? initialize, Action modify); + Task<(VerifiedCredentialTypeId TypeId, Guid RequesterId)> GetCredentialNotificationData(Guid credentialId); } diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs index 5b5c105b..e447f0de 100644 --- a/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs @@ -40,4 +40,5 @@ public interface IDocumentRepository Document CreateDocument(string documentName, byte[] documentContent, byte[] hash, MediaTypeId mediaTypeId, DocumentTypeId documentTypeId, Action? setupOptionalFields); void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDetailId); + void AttachAndModifyDocuments(IEnumerable<(Guid DocumentId, Action? Initialize, Action Modify)> documentData); } diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs index a44fed17..e870cf07 100644 --- a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs @@ -21,10 +21,15 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; public enum ProcessStepTypeId { - // Issuer Process + // CREATE CREDENTIAL PROCESS CREATE_CREDENTIAL = 1, SIGN_CREDENTIAL = 2, SAVE_CREDENTIAL_DOCUMENT = 3, CREATE_CREDENTIAL_FOR_HOLDER = 4, TRIGGER_CALLBACK = 5, + + // DECLINE PROCESS + REVOKE_CREDENTIAL = 100, + TRIGGER_NOTIFICATION = 101, + TRIGGER_MAIL = 102 } diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs index a07c1bca..faedae91 100644 --- a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs @@ -22,4 +22,5 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; public enum ProcessTypeId { CREATE_CREDENTIAL = 1, + DECLINE_CREDENTIAL = 2 } diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs index fbb6178c..a0e5fb13 100644 --- a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs +++ b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs @@ -35,12 +35,21 @@ public enum VerifiedCredentialExternalTypeId [EnumMember(Value = "vehicleDismantle")] VEHICLE_DISMANTLE = 4, - [EnumMember(Value = "SustainabilityCredential")] - SUSTAINABILITY_CREDENTIAL = 5, + [EnumMember(Value = "CircularEconomyCredential")] + CIRCULAR_ECONOMY = 5, [EnumMember(Value = "QualityCredential")] QUALITY_CREDENTIAL = 6, [EnumMember(Value = "BusinessPartnerCredential")] - BUSINESS_PARTNER_NUMBER = 7 + BUSINESS_PARTNER_NUMBER = 7, + + [EnumMember(Value = "DemandCapacityCredential")] + DEMAND_AND_CAPACITY_MANAGEMENT = 8, + + [EnumMember(Value = "DemandCapacityCredential")] + DEMAND_AND_CAPACITY_MANAGEMENT_PURIS = 9, + + [EnumMember(Value = "BusinessPartnerCredential")] + BUSINESS_PARTNER_DATA_MANAGEMENT = 10 } diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs index 725f9d22..46d94ab7 100644 --- a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs +++ b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs @@ -35,12 +35,21 @@ public enum VerifiedCredentialTypeId [EnumMember(Value = "Dismantler Certificate")] DISMANTLER_CERTIFICATE = 4, - [EnumMember(Value = "Sustainability Framework")] - SUSTAINABILITY_FRAMEWORK = 5, + [EnumMember(Value = "Circular Economy")] + CIRCULAR_ECONOMY = 5, [EnumMember(Value = "frameworkAgreement.quality")] FRAMEWORK_AGREEMENT_QUALITY = 6, [EnumMember(Value = "BusinessPartnerCredential")] - BUSINESS_PARTNER_NUMBER = 7 + BUSINESS_PARTNER_NUMBER = 7, + + [EnumMember(Value = "Demand and Capacity Management")] + DEMAND_AND_CAPACITY_MANAGEMENT = 8, + + [EnumMember(Value = "Demand and Capacity Management")] + DEMAND_AND_CAPACITY_MANAGEMENT_PURIS = 9, + + [EnumMember(Value = "Business Partner Data Management")] + BUSINESS_PARTNER_DATA_MANAGEMENT = 10 } diff --git a/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.Designer.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.Designer.cs new file mode 100644 index 00000000..ece6b55c --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.Designer.cs @@ -0,0 +1,1482 @@ +/******************************************************************************** +// * Copyright (c) 2024 Contributors to the Eclipse Foundation +// * +// * See the NOTICE file(s) distributed with this work for additional +// * information regarding copyright ownership. +// * +// * This program and the accompanying materials are made available under the +// * terms of the Apache License, Version 2.0 which is available at +// * https://www.apache.org/licenses/LICENSE-2.0. +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// * License for the specific language governing permissions and limitations +// * under the License. +// * +// * SPDX-License-Identifier: Apache-2.0 +// ********************************************************************************/ + +// +using System; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; + +#nullable disable + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Migrations +{ + [DbContext(typeof(IssuerDbContext))] + [Migration("20240327184333_1.0.0-rc.2")] + partial class _100rc2 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("issuer") + .UseCollation("en_US.utf8") + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities.AuditCompanySsiDetail20240228", b => + { + b.Property("AuditV1Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("audit_v1id"); + + b.Property("AuditV1DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("audit_v1date_last_changed"); + + b.Property("AuditV1LastEditorId") + .HasColumnType("uuid") + .HasColumnName("audit_v1last_editor_id"); + + b.Property("AuditV1OperationId") + .HasColumnType("integer") + .HasColumnName("audit_v1operation_id"); + + b.Property("Bpnl") + .IsRequired() + .HasColumnType("text") + .HasColumnName("bpnl"); + + b.Property("CompanySsiDetailStatusId") + .HasColumnType("integer") + .HasColumnName("company_ssi_detail_status_id"); + + b.Property("CreatorUserId") + .HasColumnType("uuid") + .HasColumnName("creator_user_id"); + + b.Property("Credential") + .HasColumnType("text") + .HasColumnName("credential"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("ExpiryCheckTypeId") + .HasColumnType("integer") + .HasColumnName("expiry_check_type_id"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ExternalCredentialId") + .HasColumnType("uuid") + .HasColumnName("external_credential_id"); + + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("IssuerBpn") + .IsRequired() + .HasColumnType("text") + .HasColumnName("issuer_bpn"); + + b.Property("LastEditorId") + .HasColumnType("uuid") + .HasColumnName("last_editor_id"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("VerifiedCredentialExternalTypeDetailVersionId") + .HasColumnType("uuid") + .HasColumnName("verified_credential_external_type_detail_version_id"); + + b.Property("VerifiedCredentialTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_id"); + + b.HasKey("AuditV1Id") + .HasName("pk_audit_company_ssi_detail20240228"); + + b.ToTable("audit_company_ssi_detail20240228", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities.AuditDocument20240305", b => + { + b.Property("AuditV1Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("audit_v1id"); + + b.Property("AuditV1DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("audit_v1date_last_changed"); + + b.Property("AuditV1LastEditorId") + .HasColumnType("uuid") + .HasColumnName("audit_v1last_editor_id"); + + b.Property("AuditV1OperationId") + .HasColumnType("integer") + .HasColumnName("audit_v1operation_id"); + + b.Property("CompanyUserId") + .HasColumnType("uuid") + .HasColumnName("company_user_id"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("DocumentContent") + .HasColumnType("bytea") + .HasColumnName("document_content"); + + b.Property("DocumentHash") + .HasColumnType("bytea") + .HasColumnName("document_hash"); + + b.Property("DocumentName") + .HasColumnType("text") + .HasColumnName("document_name"); + + b.Property("DocumentStatusId") + .HasColumnType("integer") + .HasColumnName("document_status_id"); + + b.Property("DocumentTypeId") + .HasColumnType("integer") + .HasColumnName("document_type_id"); + + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("LastEditorId") + .HasColumnType("uuid") + .HasColumnName("last_editor_id"); + + b.Property("MediaTypeId") + .HasColumnType("integer") + .HasColumnName("media_type_id"); + + b.HasKey("AuditV1Id") + .HasName("pk_audit_document20240305"); + + b.ToTable("audit_document20240305", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Bpnl") + .IsRequired() + .HasColumnType("text") + .HasColumnName("bpnl"); + + b.Property("CompanySsiDetailStatusId") + .HasColumnType("integer") + .HasColumnName("company_ssi_detail_status_id"); + + b.Property("CreatorUserId") + .HasColumnType("uuid") + .HasColumnName("creator_user_id"); + + b.Property("Credential") + .HasColumnType("text") + .HasColumnName("credential"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("ExpiryCheckTypeId") + .HasColumnType("integer") + .HasColumnName("expiry_check_type_id"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ExternalCredentialId") + .HasColumnType("uuid") + .HasColumnName("external_credential_id"); + + b.Property("IssuerBpn") + .IsRequired() + .HasColumnType("text") + .HasColumnName("issuer_bpn"); + + b.Property("LastEditorId") + .HasColumnType("uuid") + .HasColumnName("last_editor_id"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("VerifiedCredentialExternalTypeDetailVersionId") + .HasColumnType("uuid") + .HasColumnName("verified_credential_external_type_detail_version_id"); + + b.Property("VerifiedCredentialTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_id"); + + b.HasKey("Id") + .HasName("pk_company_ssi_details"); + + b.HasIndex("CompanySsiDetailStatusId") + .HasDatabaseName("ix_company_ssi_details_company_ssi_detail_status_id"); + + b.HasIndex("ExpiryCheckTypeId") + .HasDatabaseName("ix_company_ssi_details_expiry_check_type_id"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_company_ssi_details_process_id"); + + b.HasIndex("VerifiedCredentialExternalTypeDetailVersionId") + .HasDatabaseName("ix_company_ssi_details_verified_credential_external_type_detai"); + + b.HasIndex("VerifiedCredentialTypeId") + .HasDatabaseName("ix_company_ssi_details_verified_credential_type_id"); + + b.ToTable("company_ssi_details", "issuer", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL"); + }); + + b + .HasAnnotation("LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL", "CREATE FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL\"() RETURNS trigger as $LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL$\r\nBEGIN\r\n INSERT INTO \"issuer\".\"audit_company_ssi_detail20240228\" (\"id\", \"bpnl\", \"issuer_bpn\", \"verified_credential_type_id\", \"company_ssi_detail_status_id\", \"date_created\", \"creator_user_id\", \"expiry_date\", \"verified_credential_external_type_detail_version_id\", \"expiry_check_type_id\", \"process_id\", \"external_credential_id\", \"credential\", \"date_last_changed\", \"last_editor_id\", \"audit_v1id\", \"audit_v1operation_id\", \"audit_v1date_last_changed\", \"audit_v1last_editor_id\") SELECT NEW.\"id\", \r\n NEW.\"bpnl\", \r\n NEW.\"issuer_bpn\", \r\n NEW.\"verified_credential_type_id\", \r\n NEW.\"company_ssi_detail_status_id\", \r\n NEW.\"date_created\", \r\n NEW.\"creator_user_id\", \r\n NEW.\"expiry_date\", \r\n NEW.\"verified_credential_external_type_detail_version_id\", \r\n NEW.\"expiry_check_type_id\", \r\n NEW.\"process_id\", \r\n NEW.\"external_credential_id\", \r\n NEW.\"credential\", \r\n NEW.\"date_last_changed\", \r\n NEW.\"last_editor_id\", \r\n gen_random_uuid(), \r\n 1, \r\n CURRENT_DATE, \r\n NEW.\"last_editor_id\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL AFTER INSERT\r\nON \"issuer\".\"company_ssi_details\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"issuer\".\"LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL\"();") + .HasAnnotation("LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL", "CREATE FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL\"() RETURNS trigger as $LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL$\r\nBEGIN\r\n INSERT INTO \"issuer\".\"audit_company_ssi_detail20240228\" (\"id\", \"bpnl\", \"issuer_bpn\", \"verified_credential_type_id\", \"company_ssi_detail_status_id\", \"date_created\", \"creator_user_id\", \"expiry_date\", \"verified_credential_external_type_detail_version_id\", \"expiry_check_type_id\", \"process_id\", \"external_credential_id\", \"credential\", \"date_last_changed\", \"last_editor_id\", \"audit_v1id\", \"audit_v1operation_id\", \"audit_v1date_last_changed\", \"audit_v1last_editor_id\") SELECT NEW.\"id\", \r\n NEW.\"bpnl\", \r\n NEW.\"issuer_bpn\", \r\n NEW.\"verified_credential_type_id\", \r\n NEW.\"company_ssi_detail_status_id\", \r\n NEW.\"date_created\", \r\n NEW.\"creator_user_id\", \r\n NEW.\"expiry_date\", \r\n NEW.\"verified_credential_external_type_detail_version_id\", \r\n NEW.\"expiry_check_type_id\", \r\n NEW.\"process_id\", \r\n NEW.\"external_credential_id\", \r\n NEW.\"credential\", \r\n NEW.\"date_last_changed\", \r\n NEW.\"last_editor_id\", \r\n gen_random_uuid(), \r\n 2, \r\n CURRENT_DATE, \r\n NEW.\"last_editor_id\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL AFTER UPDATE\r\nON \"issuer\".\"company_ssi_details\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL\"();"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetailAssignedDocument", b => + { + b.Property("DocumentId") + .HasColumnType("uuid") + .HasColumnName("document_id"); + + b.Property("CompanySsiDetailId") + .HasColumnType("uuid") + .HasColumnName("company_ssi_detail_id"); + + b.HasKey("DocumentId", "CompanySsiDetailId") + .HasName("pk_company_ssi_detail_assigned_documents"); + + b.HasIndex("CompanySsiDetailId") + .HasDatabaseName("ix_company_ssi_detail_assigned_documents_company_ssi_detail_id"); + + b.ToTable("company_ssi_detail_assigned_documents", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetailStatus", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_company_ssi_detail_statuses"); + + b.ToTable("company_ssi_detail_statuses", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "PENDING" + }, + new + { + Id = 2, + Label = "ACTIVE" + }, + new + { + Id = 3, + Label = "REVOKED" + }, + new + { + Id = 4, + Label = "INACTIVE" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiProcessData", b => + { + b.Property("CompanySsiDetailId") + .HasColumnType("uuid") + .HasColumnName("company_ssi_detail_id"); + + b.Property("CallbackUrl") + .HasColumnType("text") + .HasColumnName("callback_url"); + + b.Property("ClientId") + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .HasColumnType("bytea") + .HasColumnName("client_secret"); + + b.Property("CredentialTypeKindId") + .HasColumnType("integer") + .HasColumnName("credential_type_kind_id"); + + b.Property("EncryptionMode") + .HasColumnType("integer") + .HasColumnName("encryption_mode"); + + b.Property("HolderWalletUrl") + .HasColumnType("text") + .HasColumnName("holder_wallet_url"); + + b.Property("InitializationVector") + .HasColumnType("bytea") + .HasColumnName("initialization_vector"); + + b.Property("Schema") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("schema"); + + b.HasKey("CompanySsiDetailId") + .HasName("pk_company_ssi_process_data"); + + b.HasIndex("CredentialTypeKindId") + .HasDatabaseName("ix_company_ssi_process_data_credential_type_kind_id"); + + b.ToTable("company_ssi_process_data", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CompanyUserId") + .HasColumnType("uuid") + .HasColumnName("company_user_id"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("DocumentContent") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("document_content"); + + b.Property("DocumentHash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("document_hash"); + + b.Property("DocumentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("document_name"); + + b.Property("DocumentStatusId") + .HasColumnType("integer") + .HasColumnName("document_status_id"); + + b.Property("DocumentTypeId") + .HasColumnType("integer") + .HasColumnName("document_type_id"); + + b.Property("LastEditorId") + .HasColumnType("uuid") + .HasColumnName("last_editor_id"); + + b.Property("MediaTypeId") + .HasColumnType("integer") + .HasColumnName("media_type_id"); + + b.HasKey("Id") + .HasName("pk_documents"); + + b.HasIndex("DocumentStatusId") + .HasDatabaseName("ix_documents_document_status_id"); + + b.HasIndex("DocumentTypeId") + .HasDatabaseName("ix_documents_document_type_id"); + + b.HasIndex("MediaTypeId") + .HasDatabaseName("ix_documents_media_type_id"); + + b.ToTable("documents", "issuer", t => + { + t.HasTrigger("LC_TRIGGER_AFTER_INSERT_DOCUMENT"); + + t.HasTrigger("LC_TRIGGER_AFTER_UPDATE_DOCUMENT"); + }); + + b + .HasAnnotation("LC_TRIGGER_AFTER_INSERT_DOCUMENT", "CREATE FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_INSERT_DOCUMENT\"() RETURNS trigger as $LC_TRIGGER_AFTER_INSERT_DOCUMENT$\r\nBEGIN\r\n INSERT INTO \"issuer\".\"audit_document20240305\" (\"id\", \"date_created\", \"document_hash\", \"document_content\", \"document_name\", \"media_type_id\", \"document_type_id\", \"document_status_id\", \"company_user_id\", \"date_last_changed\", \"last_editor_id\", \"audit_v1id\", \"audit_v1operation_id\", \"audit_v1date_last_changed\", \"audit_v1last_editor_id\") SELECT NEW.\"id\", \r\n NEW.\"date_created\", \r\n NEW.\"document_hash\", \r\n NEW.\"document_content\", \r\n NEW.\"document_name\", \r\n NEW.\"media_type_id\", \r\n NEW.\"document_type_id\", \r\n NEW.\"document_status_id\", \r\n NEW.\"company_user_id\", \r\n NEW.\"date_last_changed\", \r\n NEW.\"last_editor_id\", \r\n gen_random_uuid(), \r\n 1, \r\n CURRENT_DATE, \r\n NEW.\"last_editor_id\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_AFTER_INSERT_DOCUMENT$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_AFTER_INSERT_DOCUMENT AFTER INSERT\r\nON \"issuer\".\"documents\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"issuer\".\"LC_TRIGGER_AFTER_INSERT_DOCUMENT\"();") + .HasAnnotation("LC_TRIGGER_AFTER_UPDATE_DOCUMENT", "CREATE FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_DOCUMENT\"() RETURNS trigger as $LC_TRIGGER_AFTER_UPDATE_DOCUMENT$\r\nBEGIN\r\n INSERT INTO \"issuer\".\"audit_document20240305\" (\"id\", \"date_created\", \"document_hash\", \"document_content\", \"document_name\", \"media_type_id\", \"document_type_id\", \"document_status_id\", \"company_user_id\", \"date_last_changed\", \"last_editor_id\", \"audit_v1id\", \"audit_v1operation_id\", \"audit_v1date_last_changed\", \"audit_v1last_editor_id\") SELECT NEW.\"id\", \r\n NEW.\"date_created\", \r\n NEW.\"document_hash\", \r\n NEW.\"document_content\", \r\n NEW.\"document_name\", \r\n NEW.\"media_type_id\", \r\n NEW.\"document_type_id\", \r\n NEW.\"document_status_id\", \r\n NEW.\"company_user_id\", \r\n NEW.\"date_last_changed\", \r\n NEW.\"last_editor_id\", \r\n gen_random_uuid(), \r\n 2, \r\n CURRENT_DATE, \r\n NEW.\"last_editor_id\";\r\nRETURN NEW;\r\nEND;\r\n$LC_TRIGGER_AFTER_UPDATE_DOCUMENT$ LANGUAGE plpgsql;\r\nCREATE TRIGGER LC_TRIGGER_AFTER_UPDATE_DOCUMENT AFTER UPDATE\r\nON \"issuer\".\"documents\"\r\nFOR EACH ROW EXECUTE PROCEDURE \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_DOCUMENT\"();"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentStatus", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_document_status"); + + b.ToTable("document_status", "issuer"); + + b.HasData( + new + { + Id = 2, + Label = "ACTIVE" + }, + new + { + Id = 3, + Label = "INACTIVE" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_document_types"); + + b.ToTable("document_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "PRESENTATION" + }, + new + { + Id = 2, + Label = "CREDENTIAL" + }, + new + { + Id = 3, + Label = "VERIFIED_CREDENTIAL" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ExpiryCheckType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_expiry_check_types"); + + b.ToTable("expiry_check_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "ONE_MONTH" + }, + new + { + Id = 2, + Label = "TWO_WEEKS" + }, + new + { + Id = 3, + Label = "ONE_DAY" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.MediaType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_media_types"); + + b.ToTable("media_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "JPEG" + }, + new + { + Id = 2, + Label = "GIF" + }, + new + { + Id = 3, + Label = "PNG" + }, + new + { + Id = 4, + Label = "SVG" + }, + new + { + Id = 5, + Label = "TIFF" + }, + new + { + Id = 6, + Label = "PDF" + }, + new + { + Id = 7, + Label = "JSON" + }, + new + { + Id = 8, + Label = "PEM" + }, + new + { + Id = 9, + Label = "CA_CERT" + }, + new + { + Id = 10, + Label = "PKX_CER" + }, + new + { + Id = 11, + Label = "OCTET" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Process", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("LockExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("lock_expiry_date"); + + b.Property("ProcessTypeId") + .HasColumnType("integer") + .HasColumnName("process_type_id"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uuid") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_processes"); + + b.HasIndex("ProcessTypeId") + .HasDatabaseName("ix_processes_process_type_id"); + + b.ToTable("processes", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("ProcessStepStatusId") + .HasColumnType("integer") + .HasColumnName("process_step_status_id"); + + b.Property("ProcessStepTypeId") + .HasColumnType("integer") + .HasColumnName("process_step_type_id"); + + b.HasKey("Id") + .HasName("pk_process_steps"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_process_steps_process_id"); + + b.HasIndex("ProcessStepStatusId") + .HasDatabaseName("ix_process_steps_process_step_status_id"); + + b.HasIndex("ProcessStepTypeId") + .HasDatabaseName("ix_process_steps_process_step_type_id"); + + b.ToTable("process_steps", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepStatus", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_step_statuses"); + + b.ToTable("process_step_statuses", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "TODO" + }, + new + { + Id = 2, + Label = "DONE" + }, + new + { + Id = 3, + Label = "SKIPPED" + }, + new + { + Id = 4, + Label = "FAILED" + }, + new + { + Id = 5, + Label = "DUPLICATE" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_step_types"); + + b.ToTable("process_step_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "CREATE_CREDENTIAL" + }, + new + { + Id = 2, + Label = "SIGN_CREDENTIAL" + }, + new + { + Id = 3, + Label = "SAVE_CREDENTIAL_DOCUMENT" + }, + new + { + Id = 4, + Label = "CREATE_CREDENTIAL_FOR_HOLDER" + }, + new + { + Id = 5, + Label = "TRIGGER_CALLBACK" + }, + new + { + Id = 100, + Label = "REVOKE_CREDENTIAL" + }, + new + { + Id = 101, + Label = "TRIGGER_NOTIFICATION" + }, + new + { + Id = 102, + Label = "TRIGGER_MAIL" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_types"); + + b.ToTable("process_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "CREATE_CREDENTIAL" + }, + new + { + Id = 2, + Label = "DECLINE_CREDENTIAL" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.UseCase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("name"); + + b.Property("Shortname") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("shortname"); + + b.HasKey("Id") + .HasName("pk_use_cases"); + + b.ToTable("use_cases", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_verified_credential_external_types"); + + b.ToTable("verified_credential_external_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "TRACEABILITY_CREDENTIAL" + }, + new + { + Id = 2, + Label = "PCF_CREDENTIAL" + }, + new + { + Id = 3, + Label = "BEHAVIOR_TWIN_CREDENTIAL" + }, + new + { + Id = 4, + Label = "VEHICLE_DISMANTLE" + }, + new + { + Id = 5, + Label = "CIRCULAR_ECONOMY" + }, + new + { + Id = 6, + Label = "QUALITY_CREDENTIAL" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }, + new + { + Id = 8, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT" + }, + new + { + Id = 9, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" + }, + new + { + Id = 10, + Label = "BUSINESS_PARTNER_DATA_MANAGEMENT" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalTypeDetailVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Expiry") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry"); + + b.Property("Template") + .HasColumnType("text") + .HasColumnName("template"); + + b.Property("ValidFrom") + .HasColumnType("timestamp with time zone") + .HasColumnName("valid_from"); + + b.Property("VerifiedCredentialExternalTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_external_type_id"); + + b.Property("Version") + .HasColumnType("text") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_verified_credential_external_type_detail_versions"); + + b.HasIndex("VerifiedCredentialExternalTypeId", "Version") + .IsUnique() + .HasDatabaseName("ix_verified_credential_external_type_detail_versions_verified_"); + + b.ToTable("verified_credential_external_type_detail_versions", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_verified_credential_types"); + + b.ToTable("verified_credential_types", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "TRACEABILITY_FRAMEWORK" + }, + new + { + Id = 2, + Label = "PCF_FRAMEWORK" + }, + new + { + Id = 3, + Label = "BEHAVIOR_TWIN_FRAMEWORK" + }, + new + { + Id = 4, + Label = "DISMANTLER_CERTIFICATE" + }, + new + { + Id = 5, + Label = "CIRCULAR_ECONOMY" + }, + new + { + Id = 6, + Label = "FRAMEWORK_AGREEMENT_QUALITY" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }, + new + { + Id = 8, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT" + }, + new + { + Id = 9, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" + }, + new + { + Id = 10, + Label = "BUSINESS_PARTNER_DATA_MANAGEMENT" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedExternalType", b => + { + b.Property("VerifiedCredentialTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_id"); + + b.Property("VerifiedCredentialExternalTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_external_type_id"); + + b.HasKey("VerifiedCredentialTypeId", "VerifiedCredentialExternalTypeId") + .HasName("pk_verified_credential_type_assigned_external_types"); + + b.HasIndex("VerifiedCredentialExternalTypeId") + .HasDatabaseName("ix_verified_credential_type_assigned_external_types_verified_c"); + + b.HasIndex("VerifiedCredentialTypeId") + .IsUnique() + .HasDatabaseName("ix_verified_credential_type_assigned_external_types_verified_c1"); + + b.ToTable("verified_credential_type_assigned_external_types", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedKind", b => + { + b.Property("VerifiedCredentialTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_id"); + + b.Property("VerifiedCredentialTypeKindId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_kind_id"); + + b.HasKey("VerifiedCredentialTypeId", "VerifiedCredentialTypeKindId") + .HasName("pk_verified_credential_type_assigned_kinds"); + + b.HasIndex("VerifiedCredentialTypeId") + .HasDatabaseName("ix_verified_credential_type_assigned_kinds_verified_credential"); + + b.HasIndex("VerifiedCredentialTypeKindId") + .HasDatabaseName("ix_verified_credential_type_assigned_kinds_verified_credential1"); + + b.ToTable("verified_credential_type_assigned_kinds", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedUseCase", b => + { + b.Property("VerifiedCredentialTypeId") + .HasColumnType("integer") + .HasColumnName("verified_credential_type_id"); + + b.Property("UseCaseId") + .HasColumnType("uuid") + .HasColumnName("use_case_id"); + + b.HasKey("VerifiedCredentialTypeId", "UseCaseId") + .HasName("pk_verified_credential_type_assigned_use_cases"); + + b.HasIndex("UseCaseId") + .IsUnique() + .HasDatabaseName("ix_verified_credential_type_assigned_use_cases_use_case_id"); + + b.HasIndex("VerifiedCredentialTypeId") + .IsUnique() + .HasDatabaseName("ix_verified_credential_type_assigned_use_cases_verified_creden"); + + b.ToTable("verified_credential_type_assigned_use_cases", "issuer"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeKind", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_verified_credential_type_kinds"); + + b.ToTable("verified_credential_type_kinds", "issuer"); + + b.HasData( + new + { + Id = 1, + Label = "FRAMEWORK" + }, + new + { + Id = 2, + Label = "MEMBERSHIP" + }, + new + { + Id = 3, + Label = "BPN" + }); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetail", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetailStatus", "CompanySsiDetailStatus") + .WithMany("CompanySsiDetails") + .HasForeignKey("CompanySsiDetailStatusId") + .IsRequired() + .HasConstraintName("fk_company_ssi_details_company_ssi_detail_statuses_company_ssi"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ExpiryCheckType", "ExpiryCheckType") + .WithMany("CompanySsiDetails") + .HasForeignKey("ExpiryCheckTypeId") + .HasConstraintName("fk_company_ssi_details_expiry_check_types_expiry_check_type_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Process", "Process") + .WithMany("CompanySsiDetails") + .HasForeignKey("ProcessId") + .HasConstraintName("fk_company_ssi_details_processes_process_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalTypeDetailVersion", "VerifiedCredentialExternalTypeDetailVersion") + .WithMany("CompanySsiDetails") + .HasForeignKey("VerifiedCredentialExternalTypeDetailVersionId") + .HasConstraintName("fk_company_ssi_details_verified_credential_external_type_detai"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", "VerifiedCredentialType") + .WithMany("CompanySsiDetails") + .HasForeignKey("VerifiedCredentialTypeId") + .IsRequired() + .HasConstraintName("fk_company_ssi_details_verified_credential_types_verified_cred"); + + b.Navigation("CompanySsiDetailStatus"); + + b.Navigation("ExpiryCheckType"); + + b.Navigation("Process"); + + b.Navigation("VerifiedCredentialExternalTypeDetailVersion"); + + b.Navigation("VerifiedCredentialType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetailAssignedDocument", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetail", "CompanySsiDetail") + .WithMany() + .HasForeignKey("CompanySsiDetailId") + .IsRequired() + .HasConstraintName("fk_company_ssi_detail_assigned_documents_company_ssi_details_c"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .IsRequired() + .HasConstraintName("fk_company_ssi_detail_assigned_documents_documents_document_id"); + + b.Navigation("CompanySsiDetail"); + + b.Navigation("Document"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiProcessData", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetail", "CompanySsiDetail") + .WithOne("CompanySsiProcessData") + .HasForeignKey("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiProcessData", "CompanySsiDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_company_ssi_process_data_company_ssi_details_company_ssi_de"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeKind", "CredentialTypeKind") + .WithMany("CompanySsiProcessData") + .HasForeignKey("CredentialTypeKindId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_company_ssi_process_data_verified_credential_type_kinds_cre"); + + b.Navigation("CompanySsiDetail"); + + b.Navigation("CredentialTypeKind"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Document", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentStatus", "DocumentStatus") + .WithMany("Documents") + .HasForeignKey("DocumentStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_documents_document_status_document_status_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentType", "DocumentType") + .WithMany("Documents") + .HasForeignKey("DocumentTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_documents_document_types_document_type_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.MediaType", "MediaType") + .WithMany("Documents") + .HasForeignKey("MediaTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_documents_media_types_media_type_id"); + + b.Navigation("DocumentStatus"); + + b.Navigation("DocumentType"); + + b.Navigation("MediaType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Process", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessType", "ProcessType") + .WithMany("Processes") + .HasForeignKey("ProcessTypeId") + .IsRequired() + .HasConstraintName("fk_processes_process_types_process_type_id"); + + b.Navigation("ProcessType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStep", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Process", "Process") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_process_steps_processes_process_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepStatus", "ProcessStepStatus") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessStepStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_process_steps_process_step_statuses_process_step_status_id"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepType", "ProcessStepType") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessStepTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_process_steps_process_step_types_process_step_type_id"); + + b.Navigation("Process"); + + b.Navigation("ProcessStepStatus"); + + b.Navigation("ProcessStepType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalTypeDetailVersion", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalType", "VerifiedCredentialExternalType") + .WithMany("VerifiedCredentialExternalTypeDetailVersions") + .HasForeignKey("VerifiedCredentialExternalTypeId") + .IsRequired() + .HasConstraintName("fk_verified_credential_external_type_detail_versions_verified_"); + + b.Navigation("VerifiedCredentialExternalType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedExternalType", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalType", "VerifiedCredentialExternalType") + .WithMany("VerifiedCredentialTypeAssignedExternalTypes") + .HasForeignKey("VerifiedCredentialExternalTypeId") + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_external_types_verified_c"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", "VerifiedCredentialType") + .WithOne("VerifiedCredentialTypeAssignedExternalType") + .HasForeignKey("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedExternalType", "VerifiedCredentialTypeId") + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_external_types_verified_c1"); + + b.Navigation("VerifiedCredentialExternalType"); + + b.Navigation("VerifiedCredentialType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedKind", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", "VerifiedCredentialType") + .WithOne("VerifiedCredentialTypeAssignedKind") + .HasForeignKey("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedKind", "VerifiedCredentialTypeId") + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_kinds_verified_credential"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeKind", "VerifiedCredentialTypeKind") + .WithMany("VerifiedCredentialTypeAssignedKinds") + .HasForeignKey("VerifiedCredentialTypeKindId") + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_kinds_verified_credential1"); + + b.Navigation("VerifiedCredentialType"); + + b.Navigation("VerifiedCredentialTypeKind"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedUseCase", b => + { + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.UseCase", "UseCase") + .WithOne("VerifiedCredentialAssignedUseCase") + .HasForeignKey("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedUseCase", "UseCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_use_cases_use_cases_use_c"); + + b.HasOne("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", "VerifiedCredentialType") + .WithOne("VerifiedCredentialTypeAssignedUseCase") + .HasForeignKey("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeAssignedUseCase", "VerifiedCredentialTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_verified_credential_type_assigned_use_cases_verified_creden"); + + b.Navigation("UseCase"); + + b.Navigation("VerifiedCredentialType"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetail", b => + { + b.Navigation("CompanySsiProcessData"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.CompanySsiDetailStatus", b => + { + b.Navigation("CompanySsiDetails"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentStatus", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.DocumentType", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ExpiryCheckType", b => + { + b.Navigation("CompanySsiDetails"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.MediaType", b => + { + b.Navigation("Documents"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.Process", b => + { + b.Navigation("CompanySsiDetails"); + + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepStatus", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessStepType", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.ProcessType", b => + { + b.Navigation("Processes"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.UseCase", b => + { + b.Navigation("VerifiedCredentialAssignedUseCase"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalType", b => + { + b.Navigation("VerifiedCredentialExternalTypeDetailVersions"); + + b.Navigation("VerifiedCredentialTypeAssignedExternalTypes"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialExternalTypeDetailVersion", b => + { + b.Navigation("CompanySsiDetails"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialType", b => + { + b.Navigation("CompanySsiDetails"); + + b.Navigation("VerifiedCredentialTypeAssignedExternalType"); + + b.Navigation("VerifiedCredentialTypeAssignedKind"); + + b.Navigation("VerifiedCredentialTypeAssignedUseCase"); + }); + + modelBuilder.Entity("Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities.VerifiedCredentialTypeKind", b => + { + b.Navigation("CompanySsiProcessData"); + + b.Navigation("VerifiedCredentialTypeAssignedKinds"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.cs new file mode 100644 index 00000000..db1db1cf --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240327184333_1.0.0-rc.2.cs @@ -0,0 +1,170 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Migrations +{ + /// + public partial class _100rc2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + schema: "issuer", + table: "process_step_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 100, "REVOKE_CREDENTIAL" }, + { 101, "TRIGGER_NOTIFICATION" }, + { 102, "TRIGGER_MAIL" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "process_types", + columns: new[] { "id", "label" }, + values: new object[] { 2, "DECLINE_CREDENTIAL" }); + + migrationBuilder.UpdateData( + schema: "issuer", + table: "verified_credential_external_types", + keyColumn: "id", + keyValue: 5, + column: "label", + value: "CIRCULAR_ECONOMY"); + + migrationBuilder.InsertData( + schema: "issuer", + table: "verified_credential_external_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 8, "DEMAND_AND_CAPACITY_MANAGEMENT" }, + { 9, "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" }, + { 10, "BUSINESS_PARTNER_DATA_MANAGEMENT" } + }); + + migrationBuilder.UpdateData( + schema: "issuer", + table: "verified_credential_types", + keyColumn: "id", + keyValue: 5, + column: "label", + value: "CIRCULAR_ECONOMY"); + + migrationBuilder.InsertData( + schema: "issuer", + table: "verified_credential_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 8, "DEMAND_AND_CAPACITY_MANAGEMENT" }, + { 9, "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" }, + { 10, "BUSINESS_PARTNER_DATA_MANAGEMENT" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + schema: "issuer", + table: "process_step_types", + keyColumn: "id", + keyValue: 100); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "process_step_types", + keyColumn: "id", + keyValue: 101); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "process_step_types", + keyColumn: "id", + keyValue: 102); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "process_types", + keyColumn: "id", + keyValue: 2); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_external_types", + keyColumn: "id", + keyValue: 8); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_external_types", + keyColumn: "id", + keyValue: 9); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_external_types", + keyColumn: "id", + keyValue: 10); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_types", + keyColumn: "id", + keyValue: 8); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_types", + keyColumn: "id", + keyValue: 9); + + migrationBuilder.DeleteData( + schema: "issuer", + table: "verified_credential_types", + keyColumn: "id", + keyValue: 10); + + migrationBuilder.UpdateData( + schema: "issuer", + table: "verified_credential_external_types", + keyColumn: "id", + keyValue: 5, + column: "label", + value: "SUSTAINABILITY_CREDENTIAL"); + + migrationBuilder.UpdateData( + schema: "issuer", + table: "verified_credential_types", + keyColumn: "id", + keyValue: 5, + column: "label", + value: "SUSTAINABILITY_FRAMEWORK"); + } + } +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs index bf3c8653..df6864b5 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs @@ -18,12 +18,16 @@ // ********************************************************************************/ // - +using System; using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +#nullable disable + namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Migrations { [DbContext(typeof(IssuerDbContext))] @@ -810,6 +814,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 5, Label = "TRIGGER_CALLBACK" + }, + new + { + Id = 100, + Label = "REVOKE_CREDENTIAL" + }, + new + { + Id = 101, + Label = "TRIGGER_NOTIFICATION" + }, + new + { + Id = 102, + Label = "TRIGGER_MAIL" }); }); @@ -835,6 +854,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 1, Label = "CREATE_CREDENTIAL" + }, + new + { + Id = 2, + Label = "DECLINE_CREDENTIAL" }); }); @@ -903,7 +927,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 5, - Label = "SUSTAINABILITY_CREDENTIAL" + Label = "CIRCULAR_ECONOMY" }, new { @@ -914,6 +938,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 7, Label = "BUSINESS_PARTNER_NUMBER" + }, + new + { + Id = 8, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT" + }, + new + { + Id = 9, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" + }, + new + { + Id = 10, + Label = "BUSINESS_PARTNER_DATA_MANAGEMENT" }); }); @@ -995,7 +1034,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 5, - Label = "SUSTAINABILITY_FRAMEWORK" + Label = "CIRCULAR_ECONOMY" }, new { @@ -1006,6 +1045,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 7, Label = "BUSINESS_PARTNER_NUMBER" + }, + new + { + Id = 8, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT" + }, + new + { + Id = 9, + Label = "DEMAND_AND_CAPACITY_MANAGEMENT_PURIS" + }, + new + { + Id = 10, + Label = "BUSINESS_PARTNER_DATA_MANAGEMENT" }); }); diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json index 6c33df1d..e780a810 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json @@ -4,16 +4,6 @@ "name": "Traceability", "shortname": "T" }, - { - "id": "06b243a4-ba51-4bf3-bc40-5d79a2231b87", - "name": "Sustainability & CO2-Footprint", - "shortname": "CO2" - }, - { - "id": "06b243a4-ba51-4bf3-bc40-5d79a2231b88", - "name": "Manufacturing as a Service", - "shortname": "MaaS" - }, { "id": "06b243a4-ba51-4bf3-bc40-5d79a2231b89", "name": "Real-Time Control", @@ -29,11 +19,6 @@ "name": "Circular Economy", "shortname": "CE" }, - { - "id": "1aacde78-35ec-4df3-ba1e-f988cddcbbd9", - "name": "None", - "shortname": "None" - }, { "id": "41e4a4c0-aae4-41c0-97c9-ebafde410de4", "name": "Demand and Capacity Management", @@ -41,7 +26,7 @@ }, { "id": "6909ccc7-37c8-4088-99ab-790f20702460", - "name": "Business Partner Management", + "name": "Business Partner Data Management", "shortname": "BPDM" }, { @@ -53,5 +38,10 @@ "id": "b3948771-3372-4568-9e0e-acca4e674098", "name": "Behavior Twin", "shortname": "BT" + }, + { + "id": "3793a2d9-6bc5-44f0-b952-7d3f6b747dd7", + "name": "Demand and Capacity Management", + "shortname": "Puris" } ] \ No newline at end of file diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json index 58bb092c..c622324c 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json @@ -1,42 +1,34 @@ [ - { - "id": "1268a76a-ca19-4dd8-b932-01f24071d562", - "verified_credential_external_type_id": 3, - "version": "1.0.0", - "template": null, - "valid_from": "2023-06-01 00:00:00.000000 +00:00", - "expiry": "2023-09-30 00:00:00.000000 +00:00" - }, { "id": "1268a76a-ca19-4dd8-b932-01f24071d563", "verified_credential_external_type_id": 1, - "version": "2.0.0", - "template": null, + "version": "2.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf", "valid_from": "2023-06-01 00:00:00.000000 +00:00", "expiry": "2023-12-23 00:00:00.000000 +00:00" }, { "id": "1268a76a-ca19-4dd8-b932-01f24071d564", "verified_credential_external_type_id": 1, - "version": "3.0.0", - "template": null, + "version": "3.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf", "valid_from": "2024-01-01 00:00:00.000000 +00:00", "expiry": "2024-12-31 00:00:00.000000 +00:00" }, { - "id": "1268a76a-ca19-4dd8-b932-01f24071d565", - "verified_credential_external_type_id": 5, - "version": "1.0.0", + "id": "1268a76a-ca19-4dd8-b932-01f24071d562", + "verified_credential_external_type_id": 3, + "version": "1.0", "template": null, - "valid_from": "2024-01-01 00:00:00.000000 +00:00", - "expiry": "2024-12-31 00:00:00.000000 +00:00" + "valid_from": "2023-06-01 00:00:00.000000 +00:00", + "expiry": "2023-09-30 00:00:00.000000 +00:00" }, { - "id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", - "verified_credential_external_type_id": 4, - "version": null, - "template": null, + "id": "1268a76a-ca19-4dd8-b932-01f24071d565", + "verified_credential_external_type_id": 5, + "version": "1.0", + "template": " https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_CircularEconomy.pdf", "valid_from": "2024-01-01 00:00:00.000000 +00:00", - "expiry": "2999-12-31 23:59:59.000000 +00:00" + "expiry": "2024-12-31 00:00:00.000000 +00:00" } ] diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json index ee5ccd40..7aa80f1b 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json @@ -2,7 +2,7 @@ { "id": "1268a76a-ca19-4dd8-b932-01f24071d560", "verified_credential_external_type_id": 1, - "version": "1.0.0", + "version": "1.0", "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf", "valid_from": "2023-06-01 00:00:00.000000 +00:00", "expiry": "2023-09-30 00:00:00.000000 +00:00" @@ -10,7 +10,7 @@ { "id": "1268a76a-ca19-4dd8-b932-01f24071d561", "verified_credential_external_type_id": 2, - "version": "1.0.0", + "version": "1.0", "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_PCF.pdf", "valid_from": "2023-06-01 00:00:00.000000 +00:00", "expiry": "2023-09-30 00:00:00.000000 +00:00" @@ -18,9 +18,33 @@ { "id": "37aa6259-b452-4d50-b09e-827929dcfa15", "verified_credential_external_type_id": 6, - "version": "1.0.0", + "version": "1.0", "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_PCF.pdf", - "valid_from": "2023-10-16 00:00:00.000000 +00:00", - "expiry": "2023-10-16 00:00:00.000000 +00:00" + "valid_from": "2024-03-27 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" + }, + { + "id": "27d12475-c970-4979-892b-8f88e819018f", + "verified_credential_external_type_id": 8, + "version": "1.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_DemanAndCapacity.pdf", + "valid_from": "2024-03-27 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" + }, + { + "id": "a7585e82-4789-47ce-9184-5788086b1943", + "verified_credential_external_type_id": 9, + "version": "1.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Puris.pdf", + "valid_from": "2024-03-27 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" + }, + { + "id": "0077addf-f50d-4f5e-bc41-26c45d407104", + "verified_credential_external_type_id": 10, + "version": "1.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_BPDM.pdf", + "valid_from": "2024-03-27 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" } ] diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json index c07dc64e..5451b105 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json @@ -22,5 +22,17 @@ { "verified_credential_external_type_id": 6, "verified_credential_type_id": 6 + }, + { + "verified_credential_external_type_id": 8, + "verified_credential_type_id": 8 + }, + { + "verified_credential_external_type_id": 9, + "verified_credential_type_id": 9 + }, + { + "verified_credential_external_type_id": 10, + "verified_credential_type_id": 10 } ] diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json index 365789bd..4880da7a 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json @@ -26,5 +26,17 @@ { "verified_credential_type_id": 7, "verified_credential_type_kind_id": 3 + }, + { + "verified_credential_type_id": 8, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 9, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 10, + "verified_credential_type_kind_id": 1 } ] diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json index 92d3a49f..5d034bca 100644 --- a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json @@ -3,10 +3,6 @@ "verified_credential_type_id": 1, "use_case_id": "06b243a4-ba51-4bf3-bc40-5d79a2231b86" }, - { - "verified_credential_type_id": 2, - "use_case_id": "06b243a4-ba51-4bf3-bc40-5d79a2231b87" - }, { "verified_credential_type_id": 3, "use_case_id": "b3948771-3372-4568-9e0e-acca4e674098" @@ -18,5 +14,17 @@ { "verified_credential_type_id": 6, "use_case_id": "c065a349-f649-47f8-94d5-1a504a855419" + }, + { + "verified_credential_type_id": 8, + "use_case_id": "41e4a4c0-aae4-41c0-97c9-ebafde410de4" + }, + { + "verified_credential_type_id": 9, + "use_case_id": "3793a2d9-6bc5-44f0-b952-7d3f6b747dd7" + }, + { + "verified_credential_type_id": 10, + "use_case_id": "6909ccc7-37c8-4088-99ab-790f20702460" } ] diff --git a/src/externalservices/Portal.Service/Models/MailData.cs b/src/externalservices/Portal.Service/Models/MailData.cs index b3834531..c82c13cd 100644 --- a/src/externalservices/Portal.Service/Models/MailData.cs +++ b/src/externalservices/Portal.Service/Models/MailData.cs @@ -24,5 +24,10 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; public record MailData( [property: JsonPropertyName("requester")] Guid Requester, [property: JsonPropertyName("template")] string Template, - [property: JsonPropertyName("mailParameters")] IDictionary MailParameters + [property: JsonPropertyName("mailParameters")] IEnumerable MailParameters +); + +public record MailParameter( + [property: JsonPropertyName("key")] string Key, + [property: JsonPropertyName("value")] string Value ); diff --git a/src/externalservices/Portal.Service/Services/IPortalService.cs b/src/externalservices/Portal.Service/Services/IPortalService.cs index c09725cd..bacc5e0d 100644 --- a/src/externalservices/Portal.Service/Services/IPortalService.cs +++ b/src/externalservices/Portal.Service/Services/IPortalService.cs @@ -24,5 +24,5 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; public interface IPortalService { Task AddNotification(string content, Guid requester, NotificationTypeId notificationTypeId, CancellationToken cancellationToken); - Task TriggerMail(string template, Guid requester, IDictionary mailParameters, CancellationToken cancellationToken); + Task TriggerMail(string template, Guid requester, IEnumerable mailParameters, CancellationToken cancellationToken); } diff --git a/src/externalservices/Portal.Service/Services/PortalService.cs b/src/externalservices/Portal.Service/Services/PortalService.cs index fb63a2a7..44c45bc6 100644 --- a/src/externalservices/Portal.Service/Services/PortalService.cs +++ b/src/externalservices/Portal.Service/Services/PortalService.cs @@ -49,7 +49,7 @@ await client.PostAsJsonAsync("api/notifications/management", data, Options, canc .ConfigureAwait(false); } - public async Task TriggerMail(string template, Guid requester, IDictionary mailParameters, CancellationToken cancellationToken) + public async Task TriggerMail(string template, Guid requester, IEnumerable mailParameters, CancellationToken cancellationToken) { var client = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); var data = new MailData(requester, template, mailParameters); diff --git a/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs b/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs index 19a6902c..15569a95 100644 --- a/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs +++ b/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs @@ -6,5 +6,6 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; public record GetCredentialResponse( [property: JsonPropertyName("verifiableCredential")] string VerifiableCredential, [property: JsonPropertyName("credential")] JsonDocument Credential, - [property: JsonPropertyName("signing_key_id")] string SigningKeyId + [property: JsonPropertyName("signing_key_id")] string SigningKeyId, + [property: JsonPropertyName("revocationStatus")] string RevocationStatus ); diff --git a/src/externalservices/Wallet.Service/Models/RevokeCredentialRequest.cs b/src/externalservices/Wallet.Service/Models/RevokeCredentialRequest.cs new file mode 100644 index 00000000..5c565baf --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/RevokeCredentialRequest.cs @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; + +public record RevokeCredentialRequest( + [property: JsonPropertyName("payload")] RevokePayload Payload +); + +public record RevokePayload( + [property: JsonPropertyName("revoke")] bool Revoke +); diff --git a/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs b/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs index e379cb34..693cd234 100644 --- a/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs +++ b/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs @@ -21,5 +21,5 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; public interface IBasicAuthTokenService { - Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken); + Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken = default); } diff --git a/src/externalservices/Wallet.Service/Services/IWalletService.cs b/src/externalservices/Wallet.Service/Services/IWalletService.cs index 27365f47..9f775310 100644 --- a/src/externalservices/Wallet.Service/Services/IWalletService.cs +++ b/src/externalservices/Wallet.Service/Services/IWalletService.cs @@ -27,4 +27,6 @@ public interface IWalletService Task SignCredential(Guid credentialId, CancellationToken cancellationToken); Task CreateCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, string credential, CancellationToken cancellationToken); Task GetCredential(Guid externalCredentialId, CancellationToken cancellationToken); + Task RevokeCredentialForIssuer(Guid externalCredentialId, CancellationToken cancellationToken); + Task RevokeCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, Guid externalCredentialId, CancellationToken cancellationToken); } diff --git a/src/externalservices/Wallet.Service/Services/WalletService.cs b/src/externalservices/Wallet.Service/Services/WalletService.cs index e5fb59ff..7dda3d40 100644 --- a/src/externalservices/Wallet.Service/Services/WalletService.cs +++ b/src/externalservices/Wallet.Service/Services/WalletService.cs @@ -113,4 +113,30 @@ public async Task CreateCredentialForHolder(string holderWalletUrl, string return response.Id; } + + public async Task RevokeCredentialForIssuer(Guid externalCredentialId, CancellationToken cancellationToken) + { + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(_settings, cancellationToken); + var data = new RevokeCredentialRequest(new RevokePayload(true)); + await client.PatchAsJsonAsync($"/api/v2.0.0/credentials/{externalCredentialId}", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("revoke-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + } + + public async Task RevokeCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, Guid externalCredentialId, CancellationToken cancellationToken) + { + var authSettings = new BasicAuthSettings + { + ClientId = clientId, + ClientSecret = clientSecret, + TokenAddress = $"{holderWalletUrl}/oauth/token" + }; + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(authSettings, cancellationToken); + var data = new RevokeCredentialRequest(new RevokePayload(true)); + await client.PatchAsJsonAsync($"/api/v2.0.0/credentials/{externalCredentialId}", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("revoke-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + } } diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IRevocationBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IRevocationBusinessLogic.cs new file mode 100644 index 00000000..a91c9b21 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IRevocationBusinessLogic.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +public interface IRevocationBusinessLogic +{ + Task RevokeIssuerCredential(Guid credentialId, CancellationToken cancellationToken); + Task RevokeHolderCredential(Guid credentialId, TechnicalUserDetails walletInformation, CancellationToken cancellationToken); +} diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs index 8dd4b588..a9b6accd 100644 --- a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs @@ -183,6 +183,7 @@ public async Task ApproveCredential(Guid credentialId, CancellationToken cancell var processId = CreateProcess(); var expiry = GetExpiryDate(data.DetailData?.ExpiryDate); + UpdateIssuanceDate(credentialId, data, companySsiRepository); companySsiRepository.AttachAndModifyCompanySsiDetails(credentialId, c => { c.CompanySsiDetailStatusId = data.Status; @@ -197,18 +198,31 @@ public async Task ApproveCredential(Guid credentialId, CancellationToken cancell c.ProcessId = processId; }); var typeValue = data.Type.GetEnumValue() ?? throw UnexpectedConditionException.Create(CredentialErrors.CREDENTIAL_TYPE_NOT_FOUND, new ErrorParameter[] { new("verifiedCredentialType", data.Type.ToString()) }); - var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options); - await _portalService.AddNotification(content, _identity.IdentityId, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(false); - var mailParameters = new Dictionary + var mailParameters = new MailParameter[] { - { "requestName", typeValue }, - { "credentialType", typeValue }, - { "expiryDate", expiry.ToString("o", CultureInfo.InvariantCulture) } + new("requestName", typeValue), + new("credentialType", typeValue), + new("expiryDate", expiry.ToString("o", CultureInfo.InvariantCulture)) }; await _portalService.TriggerMail("CredentialApproval", _identity.IdentityId, mailParameters, cancellationToken).ConfigureAwait(false); + var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options); + await _portalService.AddNotification(content, _identity.IdentityId, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(false); await _repositories.SaveAsync().ConfigureAwait(false); } + private void UpdateIssuanceDate(Guid credentialId, SsiApprovalData data, + ICompanySsiDetailsRepository companySsiRepository) + { + var frameworkCredential = data.Schema!.Deserialize(); + if (frameworkCredential == null) + { + throw UnexpectedConditionException.Create(CredentialErrors.SCHEMA_NOT_FRAMEWORK); + } + + var newCredential = frameworkCredential with { IssuanceDate = _dateTimeProvider.OffsetNow }; + companySsiRepository.AttachAndModifyProcessData(credentialId, c => c.Schema = JsonDocument.Parse(JsonSerializer.Serialize(frameworkCredential, Options)), c => c.Schema = JsonDocument.Parse(JsonSerializer.Serialize(newCredential, Options))); + } + private Guid CreateProcess() { var processStepRepository = _repositories.GetInstance(); @@ -253,6 +267,11 @@ private static void ValidateApprovalData(Guid credentialId, bool exists, SsiAppr { throw UnexpectedConditionException.Create(CredentialErrors.ALREADY_LINKED_PROCESS); } + + if (data.Schema is null) + { + throw UnexpectedConditionException.Create(CredentialErrors.SCHEMA_NOT_SET); + } } private DateTimeOffset GetExpiryDate(DateTimeOffset? expiryDate) @@ -288,10 +307,10 @@ public async Task RejectCredential(Guid credentialId, CancellationToken cancella var content = JsonSerializer.Serialize(new { Type = type, CredentialId = credentialId }, Options); await _portalService.AddNotification(content, _identity.IdentityId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken).ConfigureAwait(false); - var mailParameters = new Dictionary + var mailParameters = new MailParameter[] { - { "requestName", typeValue }, - { "reason", "Declined by the Operator" } + new("requestName", typeValue), + new("reason", "Declined by the Operator") }; await _portalService.TriggerMail("CredentialRejected", _identity.IdentityId, mailParameters, cancellationToken).ConfigureAwait(false); @@ -400,19 +419,24 @@ public async Task CreateFrameworkCredential(CreateFrameworkCredentialReque throw ControllerArgumentException.Create(CredentialErrors.EMPTY_TEMPLATE); } - if (result.UseCase.Count() != 1) + if (result.ExternalTypeIds.Count() != 1) { throw ControllerArgumentException.Create(CredentialErrors.MULTIPLE_USE_CASES); } - var useCase = result.UseCase.Single(); + var externalTypeId = result.ExternalTypeIds.Single().GetEnumValue(); + if (externalTypeId is null) + { + throw ControllerArgumentException.Create(CredentialErrors.EMPTY_EXTERNAL_TYPE_ID); + } + var holderDid = await GetHolderInformation(requestData.Holder, cancellationToken).ConfigureAwait(false); var schemaData = new FrameworkCredential( Guid.NewGuid(), Context, - new[] { "VerifiableCredential", $"{useCase}Credential" }, - $"{useCase}Credential", - $"Framework Credential for UseCase {useCase}", + new[] { "VerifiableCredential", externalTypeId }, + externalTypeId, + $"Framework Credential for UseCase {externalTypeId}", DateTimeOffset.UtcNow, result.Expiry, _settings.IssuerDid, @@ -420,7 +444,7 @@ public async Task CreateFrameworkCredential(CreateFrameworkCredentialReque holderDid, requestData.HolderBpn, "UseCaseFramework", - useCase, + externalTypeId, result.Template!, result.Version! ), @@ -505,8 +529,9 @@ private async Task HandleCredentialProcessCreation( c.ClientId = technicalUserDetails.ClientId; c.ClientSecret = secret; - c.InitializationVector = initializationVector; c.HolderWalletUrl = technicalUserDetails.WalletUrl; + c.EncryptionMode = cryptoConfig.Index; + c.InitializationVector = initializationVector; c.CallbackUrl = callbackUrl; }); diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/RevocationBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/RevocationBusinessLogic.cs new file mode 100644 index 00000000..09b68cd8 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/RevocationBusinessLogic.cs @@ -0,0 +1,102 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +public class RevocationBusinessLogic : IRevocationBusinessLogic +{ + private readonly IIssuerRepositories _repositories; + private readonly IWalletService _walletService; + + public RevocationBusinessLogic(IIssuerRepositories repositories, IWalletService walletService) + { + _repositories = repositories; + _walletService = walletService; + } + + public async Task RevokeIssuerCredential(Guid credentialId, CancellationToken cancellationToken) + { + // check for is issuer + var credentialRepository = _repositories.GetInstance(); + var data = await RevokeCredentialInternal(credentialId, credentialRepository).ConfigureAwait(false); + if (data.StatusId != CompanySsiDetailStatusId.ACTIVE) + { + return; + } + + // call walletService + await _walletService.RevokeCredentialForIssuer(data.ExternalCredentialId, cancellationToken).ConfigureAwait(false); + UpdateData(credentialId, data.StatusId, data.Documents, credentialRepository); + } + + public async Task RevokeHolderCredential(Guid credentialId, TechnicalUserDetails walletInformation, CancellationToken cancellationToken) + { + // check for is holder + var credentialRepository = _repositories.GetInstance(); + var data = await RevokeCredentialInternal(credentialId, credentialRepository).ConfigureAwait(false); + if (data.StatusId != CompanySsiDetailStatusId.ACTIVE) + { + return; + } + + // call walletService + await _walletService.RevokeCredentialForHolder(walletInformation.WalletUrl, walletInformation.ClientId, walletInformation.ClientSecret, data.ExternalCredentialId, cancellationToken).ConfigureAwait(false); + UpdateData(credentialId, data.StatusId, data.Documents, credentialRepository); + } + + private static async Task<(Guid ExternalCredentialId, CompanySsiDetailStatusId StatusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> Documents)> RevokeCredentialInternal(Guid credentialId, ICredentialRepository credentialRepository) + { + var data = await credentialRepository.GetRevocationDataById(credentialId) + .ConfigureAwait(false); + if (!data.Exists) + { + throw NotFoundException.Create(RevocationDataErrors.CREDENTIAL_NOT_FOUND, new ErrorParameter[] { new("credentialId", credentialId.ToString()) }); + } + + if (data.ExternalCredentialId is null) + { + throw ConflictException.Create(RevocationDataErrors.EXTERNAL_CREDENTIAL_ID_NOT_SET, new ErrorParameter[] { new("credentialId", credentialId.ToString()) }); + } + + return (data.ExternalCredentialId.Value, data.StatusId, data.Documents); + } + + private void UpdateData(Guid credentialId, CompanySsiDetailStatusId statusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> documentData, ICredentialRepository credentialRepository) + { + _repositories.GetInstance().AttachAndModifyDocuments( + documentData.Select(d => new ValueTuple?, Action>( + d.DocumentId, + document => document.DocumentStatusId = d.DocumentStatusId, + document => document.DocumentStatusId = DocumentStatusId.INACTIVE + ))); + + credentialRepository.AttachAndModifyCredential(credentialId, + x => x.CompanySsiDetailStatusId = statusId, + x => x.CompanySsiDetailStatusId = CompanySsiDetailStatusId.REVOKED); + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Controllers/RevocationController.cs b/src/issuer/SsiCredentialIssuer.Service/Controllers/RevocationController.cs new file mode 100644 index 00000000..fd57e7c4 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Controllers/RevocationController.cs @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.AspNetCore.Mvc; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Extensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Controllers; + +/// +/// Creates a new instance of +/// +public static class RevocationController +{ + public static RouteGroupBuilder MapRevocationApi(this RouteGroupBuilder group) + { + var revocation = group.MapGroup("/revocation"); + + revocation.MapPost("issuer/credentials/{credentialId}", ([FromRoute] Guid credentialId, CancellationToken cancellationToken, [FromServices] IRevocationBusinessLogic logic) => logic.RevokeIssuerCredential(credentialId, cancellationToken)) + .WithSwaggerDescription("Revokes an credential which was issued by the given issuer", + "POST: api/revocation/issuer/credentials/{credentialId}", + "Id of the credential that should be revoked") + .RequireAuthorization(r => + { + r.RequireRole("revoke_credentials_issuer"); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(Guid)); + revocation.MapPost("credentials/{credentialId}", ([FromRoute] Guid credentialId, [FromBody] TechnicalUserDetails data, CancellationToken cancellationToken, [FromServices] IRevocationBusinessLogic logic) => logic.RevokeHolderCredential(credentialId, data, cancellationToken)) + .WithSwaggerDescription("Revokes an credential of an holder", + "POST: api/revocation/credentials/{credentialId}", + "Id of the credential that should be revoked", + "The information for the holder wallet", + "CancellationToken") + .RequireAuthorization(r => + { + r.RequireRole("revoke_credential"); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(Guid), contentType: Constants.JsonContentType); + + return group; + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/RevocationServiceCollectionExtensions.cs b/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/RevocationServiceCollectionExtensions.cs new file mode 100644 index 00000000..1dd0233f --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/RevocationServiceCollectionExtensions.cs @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.DependencyInjection; + +public static class RevocationServiceCollectionExtensions +{ + public static IServiceCollection AddRevocationService(this IServiceCollection services) => + services + .AddTransient(); +} diff --git a/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs index 8168311f..26a50906 100644 --- a/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs +++ b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs @@ -54,6 +54,9 @@ public class CredentialErrorMessageContainer : IErrorMessageContainer { CredentialErrors.DID_NOT_SET, "Did must not be null" }, { CredentialErrors.ALREADY_LINKED_PROCESS, "Credential should not already be linked to a process" }, { CredentialErrors.INVALID_DID_LOCATION, "The did url location must be a valid url" }, + { CredentialErrors.EMPTY_EXTERNAL_TYPE_ID, "External Type ID must be set" }, + { CredentialErrors.SCHEMA_NOT_SET, "The json schema must be set when approving a credential" }, + { CredentialErrors.SCHEMA_NOT_FRAMEWORK, "The schema must be a framework credential" } }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); public Type Type { get => typeof(CredentialErrors); } @@ -88,5 +91,8 @@ public enum CredentialErrors MULTIPLE_USE_CASES, DID_NOT_SET, ALREADY_LINKED_PROCESS, - INVALID_DID_LOCATION + INVALID_DID_LOCATION, + EMPTY_EXTERNAL_TYPE_ID, + SCHEMA_NOT_SET, + SCHEMA_NOT_FRAMEWORK } diff --git a/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/RevocationErrorMessageContainer.cs b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/RevocationErrorMessageContainer.cs new file mode 100644 index 00000000..2b52828e --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/RevocationErrorMessageContainer.cs @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Service; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; + +[ExcludeFromCodeCoverage] +public class RevocationErrorMessageContainer : IErrorMessageContainer +{ + private static readonly IReadOnlyDictionary _messageContainer = new Dictionary { + { RevocationDataErrors.CREDENTIAL_NOT_FOUND, "Credential {credentialId} does not exist" }, + { RevocationDataErrors.EXTERNAL_CREDENTIAL_ID_NOT_SET, "External Credential Id must be set for {credentialId}" } + }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); + + public Type Type { get => typeof(RevocationDataErrors); } + public IReadOnlyDictionary MessageContainer { get => _messageContainer; } +} + +public enum RevocationDataErrors +{ + CREDENTIAL_NOT_FOUND, + EXTERNAL_CREDENTIAL_ID_NOT_SET +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs b/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs index 378379d6..8bbd802d 100644 --- a/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs +++ b/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs @@ -35,7 +35,7 @@ public record FrameworkCredential( public record FrameworkCredentialSubject( [property: JsonPropertyName("id")] string Did, - [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("holderIdentifier")] string HolderIdentifier, [property: JsonPropertyName("group")] string Group, [property: JsonPropertyName("useCase")] string UseCase, [property: JsonPropertyName("contractTemplate")] string ContractTemplate, @@ -56,7 +56,7 @@ public record MembershipCredential( public record MembershipCredentialSubject( [property: JsonPropertyName("id")] string Did, - [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("holderIdentifier")] string HolderIdentifier, [property: JsonPropertyName("memberOf")] string MemberOf ); @@ -79,6 +79,6 @@ public record CredentialStatus( public record BpnCredentialSubject( [property: JsonPropertyName("id")] string Did, - [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("holderIdentifier")] string HolderIdentifier, [property: JsonPropertyName("bpn")] string Bpn ); diff --git a/src/issuer/SsiCredentialIssuer.Service/Program.cs b/src/issuer/SsiCredentialIssuer.Service/Program.cs index 3b1dcd7f..b5596fcb 100644 --- a/src/issuer/SsiCredentialIssuer.Service/Program.cs +++ b/src/issuer/SsiCredentialIssuer.Service/Program.cs @@ -29,6 +29,7 @@ using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.DependencyInjection; using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; using System.Text.Json.Serialization; const string VERSION = "v1"; @@ -52,13 +53,17 @@ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }) .AddCredentialService(builder.Configuration.GetSection("Credential")) + .AddRevocationService() + .AddWalletService(builder.Configuration) .AddPortalService(builder.Configuration.GetSection("Portal")) .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); }, (app, _) => { app.MapGroup("/api") .WithOpenApi() - .MapIssuerApi(); + .MapIssuerApi() + .MapRevocationApi(); }); diff --git a/src/issuer/SsiCredentialIssuer.Service/appsettings.json b/src/issuer/SsiCredentialIssuer.Service/appsettings.json index 67731a38..9726c0c8 100644 --- a/src/issuer/SsiCredentialIssuer.Service/appsettings.json +++ b/src/issuer/SsiCredentialIssuer.Service/appsettings.json @@ -53,6 +53,18 @@ "KeycloakTokenAddress": "", "BaseAddress": "" }, + "Wallet": { + "Username": "", + "Password": "", + "ClientId": "", + "GrantType": "", + "ClientSecret": "", + "Scope": "", + "TokenAddress": "", + "BaseAddress": "", + "EncryptionConfigIndex": 0, + "EncryptionConfigs": [] + }, "Credential": { "IssuerDid": "", "IssuerBpn": "", diff --git a/src/processes/CredentialProcess.Library/CredentialProcessHandler.cs b/src/processes/CredentialProcess.Library/Creation/CredentialCreationProcessHandler.cs similarity index 96% rename from src/processes/CredentialProcess.Library/CredentialProcessHandler.cs rename to src/processes/CredentialProcess.Library/Creation/CredentialCreationProcessHandler.cs index 0fa0f39a..12819124 100644 --- a/src/processes/CredentialProcess.Library/CredentialProcessHandler.cs +++ b/src/processes/CredentialProcess.Library/Creation/CredentialCreationProcessHandler.cs @@ -26,15 +26,15 @@ using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.BusinessLogic; using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; -namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library; +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; -public class CredentialProcessHandler : ICredentialProcessHandler +public class CredentialCreationProcessHandler : ICredentialCreationProcessHandler { private readonly IIssuerRepositories _issuerRepositories; private readonly IWalletBusinessLogic _walletBusinessLogic; private readonly ICallbackService _callbackService; - public CredentialProcessHandler(IIssuerRepositories issuerRepositories, IWalletBusinessLogic walletBusinessLogic, ICallbackService callbackService) + public CredentialCreationProcessHandler(IIssuerRepositories issuerRepositories, IWalletBusinessLogic walletBusinessLogic, ICallbackService callbackService) { _issuerRepositories = issuerRepositories; _walletBusinessLogic = walletBusinessLogic; diff --git a/src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs b/src/processes/CredentialProcess.Library/Creation/ICredentialCreationProcessHandler.cs similarity index 96% rename from src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs rename to src/processes/CredentialProcess.Library/Creation/ICredentialCreationProcessHandler.cs index d21b1c9e..f68c31ab 100644 --- a/src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs +++ b/src/processes/CredentialProcess.Library/Creation/ICredentialCreationProcessHandler.cs @@ -19,9 +19,9 @@ using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; -namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library; +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; -public interface ICredentialProcessHandler +public interface ICredentialCreationProcessHandler { Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCredential(Guid credentialId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SignCredential(Guid credentialId, CancellationToken cancellationToken); diff --git a/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj b/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj index 5a100318..bb58a792 100644 --- a/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj +++ b/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj @@ -30,6 +30,7 @@ + diff --git a/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs b/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs index 6296675f..ac774cbd 100644 --- a/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs +++ b/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs @@ -19,16 +19,27 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.DependencyInjection; public static class CredentialHandlerExtensions { - public static IServiceCollection AddCredentialProcessHandler(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddCredentialCreationProcessHandler(this IServiceCollection services, IConfiguration config) { services - .AddTransient() + .AddTransient() + .AddWalletService(config); + + return services; + } + + public static IServiceCollection AddCredentialExpiryProcessHandler(this IServiceCollection services, IConfiguration config) + { + services + .AddTransient() .AddWalletService(config); return services; diff --git a/src/processes/CredentialProcess.Library/Expiry/CredentialCreationProcessHandler.cs b/src/processes/CredentialProcess.Library/Expiry/CredentialCreationProcessHandler.cs new file mode 100644 index 00000000..c9dfabef --- /dev/null +++ b/src/processes/CredentialProcess.Library/Expiry/CredentialCreationProcessHandler.cs @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; + +public class CredentialExpiryProcessHandler : ICredentialExpiryProcessHandler +{ + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + private readonly IIssuerRepositories _repositories; + private readonly IWalletService _walletService; + private readonly IPortalService _portalService; + + public CredentialExpiryProcessHandler(IIssuerRepositories repositories, IWalletService walletService, IPortalService portalService) + { + _repositories = repositories; + _walletService = walletService; + _portalService = portalService; + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> RevokeCredential(Guid credentialId, CancellationToken cancellationToken) + { + var credentialRepository = _repositories.GetInstance(); + var data = await credentialRepository.GetRevocationDataById(credentialId) + .ConfigureAwait(false); + if (!data.Exists) + { + throw new NotFoundException($"Credential {credentialId} does not exist"); + } + + if (data.ExternalCredentialId is null) + { + throw new ConflictException($"External Credential Id must be set for {credentialId}"); + } + + // call walletService + await _walletService.RevokeCredentialForIssuer(data.ExternalCredentialId.Value, cancellationToken).ConfigureAwait(false); + + _repositories.GetInstance().AttachAndModifyDocuments( + data.Documents.Select(d => new ValueTuple?, Action>( + d.DocumentId, + document => document.DocumentStatusId = d.DocumentStatusId, + document => document.DocumentStatusId = DocumentStatusId.INACTIVE + ))); + + credentialRepository.AttachAndModifyCredential(credentialId, + x => x.CompanySsiDetailStatusId = data.StatusId, + x => x.CompanySsiDetailStatusId = CompanySsiDetailStatusId.REVOKED); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.TRIGGER_NOTIFICATION, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerNotification(Guid credentialId, CancellationToken cancellationToken) + { + var (typeId, requesterId) = await _repositories.GetInstance().GetCredentialNotificationData(credentialId).ConfigureAwait(false); + var content = JsonSerializer.Serialize(new { Type = typeId, CredentialId = credentialId }, Options); + await _portalService.AddNotification(content, requesterId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.TRIGGER_MAIL, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerMail(Guid credentialId, CancellationToken cancellationToken) + { + var (typeId, requesterId) = await _repositories.GetInstance().GetCredentialNotificationData(credentialId).ConfigureAwait(false); + + var typeValue = typeId.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {typeId} does not exists"); + var mailParameters = new MailParameter[] + { + new("requestName", typeValue), + new("reason", "The credential is already expired") + }; + await _portalService.TriggerMail("CredentialRejected", requesterId, mailParameters, cancellationToken); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.DONE, + false, + null); + } +} diff --git a/src/processes/CredentialProcess.Library/Expiry/ICredentialExpiryProcessHandler.cs b/src/processes/CredentialProcess.Library/Expiry/ICredentialExpiryProcessHandler.cs new file mode 100644 index 00000000..33bcc790 --- /dev/null +++ b/src/processes/CredentialProcess.Library/Expiry/ICredentialExpiryProcessHandler.cs @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; + +public interface ICredentialExpiryProcessHandler +{ + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> RevokeCredential(Guid credentialId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerNotification(Guid credentialId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerMail(Guid credentialId, CancellationToken cancellationToken); +} diff --git a/src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs b/src/processes/CredentialProcess.Worker/Creation/CredentialCreationProcessTypeExecutor.cs similarity index 85% rename from src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs rename to src/processes/CredentialProcess.Worker/Creation/CredentialCreationProcessTypeExecutor.cs index b260f92d..acdcb4ac 100644 --- a/src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs +++ b/src/processes/CredentialProcess.Worker/Creation/CredentialCreationProcessTypeExecutor.cs @@ -18,19 +18,19 @@ ********************************************************************************/ using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; using System.Collections.Immutable; -namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker; +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Creation; -public class CredentialProcessTypeExecutor : IProcessTypeExecutor +public class CredentialCreationProcessTypeExecutor : IProcessTypeExecutor { private readonly IIssuerRepositories _issuerRepositories; - private readonly ICredentialProcessHandler _credentialProcessHandler; + private readonly ICredentialCreationProcessHandler _credentialCreationProcessHandler; private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( ProcessStepTypeId.CREATE_CREDENTIAL, @@ -41,12 +41,12 @@ public class CredentialProcessTypeExecutor : IProcessTypeExecutor private Guid _credentialId; - public CredentialProcessTypeExecutor( + public CredentialCreationProcessTypeExecutor( IIssuerRepositories issuerRepositories, - ICredentialProcessHandler credentialProcessHandler) + ICredentialCreationProcessHandler credentialCreationProcessHandler) { _issuerRepositories = issuerRepositories; - _credentialProcessHandler = credentialProcessHandler; + _credentialCreationProcessHandler = credentialCreationProcessHandler; } public ProcessTypeId GetProcessTypeId() => ProcessTypeId.CREATE_CREDENTIAL; @@ -82,15 +82,15 @@ public CredentialProcessTypeExecutor( { (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { - ProcessStepTypeId.CREATE_CREDENTIAL => await _credentialProcessHandler.CreateCredential(_credentialId, cancellationToken) + ProcessStepTypeId.CREATE_CREDENTIAL => await _credentialCreationProcessHandler.CreateCredential(_credentialId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.SIGN_CREDENTIAL => await _credentialProcessHandler.SignCredential(_credentialId, cancellationToken) + ProcessStepTypeId.SIGN_CREDENTIAL => await _credentialCreationProcessHandler.SignCredential(_credentialId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT => await _credentialProcessHandler.SaveCredentialDocument(_credentialId, cancellationToken) + ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT => await _credentialCreationProcessHandler.SaveCredentialDocument(_credentialId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER => await _credentialProcessHandler.CreateCredentialForHolder(_credentialId, cancellationToken) + ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER => await _credentialCreationProcessHandler.CreateCredentialForHolder(_credentialId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.TRIGGER_CALLBACK => await _credentialProcessHandler.TriggerCallback(_credentialId, cancellationToken) + ProcessStepTypeId.TRIGGER_CALLBACK => await _credentialCreationProcessHandler.TriggerCallback(_credentialId, cancellationToken) .ConfigureAwait(false), _ => (null, ProcessStepStatusId.TODO, false, null) }; diff --git a/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs b/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs index ba49a183..503203d7 100644 --- a/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs +++ b/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs @@ -20,14 +20,20 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Expiry; using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.DependencyInjection; public static class CredentialProcessCollectionExtensions { - public static IServiceCollection AddCredentialProcessExecutor(this IServiceCollection services, IConfiguration config) => + public static IServiceCollection AddCredentialCreationProcessExecutor(this IServiceCollection services, IConfiguration config) => services - .AddTransient() - .AddCredentialProcessHandler(config); + .AddTransient() + .AddCredentialCreationProcessHandler(config); + + public static IServiceCollection AddCredentialExpiryProcessExecutor(this IServiceCollection services, IConfiguration config) => + services + .AddTransient() + .AddCredentialExpiryProcessHandler(config); } diff --git a/src/processes/CredentialProcess.Worker/Expiry/CredentialExpiryProcessTypeExecutor.cs b/src/processes/CredentialProcess.Worker/Expiry/CredentialExpiryProcessTypeExecutor.cs new file mode 100644 index 00000000..d3710403 --- /dev/null +++ b/src/processes/CredentialProcess.Worker/Expiry/CredentialExpiryProcessTypeExecutor.cs @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Expiry; + +public class CredentialExpiryProcessTypeExecutor : IProcessTypeExecutor +{ + private readonly IIssuerRepositories _issuerRepositories; + private readonly ICredentialExpiryProcessHandler _credentialExpiryProcessHandler; + + private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( + ProcessStepTypeId.REVOKE_CREDENTIAL, + ProcessStepTypeId.TRIGGER_NOTIFICATION, + ProcessStepTypeId.TRIGGER_MAIL); + + private Guid _credentialId; + + public CredentialExpiryProcessTypeExecutor( + IIssuerRepositories issuerRepositories, + ICredentialExpiryProcessHandler credentialExpiryProcessHandler) + { + _issuerRepositories = issuerRepositories; + _credentialExpiryProcessHandler = credentialExpiryProcessHandler; + } + + public ProcessTypeId GetProcessTypeId() => ProcessTypeId.DECLINE_CREDENTIAL; + public bool IsExecutableStepTypeId(ProcessStepTypeId processStepTypeId) => _executableProcessSteps.Contains(processStepTypeId); + public IEnumerable GetExecutableStepTypeIds() => _executableProcessSteps; + public ValueTask IsLockRequested(ProcessStepTypeId processStepTypeId) => new(false); + + public async ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds) + { + var (exists, credentialId) = await _issuerRepositories.GetInstance().GetDataForProcessId(processId).ConfigureAwait(false); + if (!exists) + { + throw new NotFoundException($"process {processId} does not exist or is not associated with an credential"); + } + + _credentialId = credentialId; + return new IProcessTypeExecutor.InitializationResult(false, null); + } + + public async ValueTask ExecuteProcessStep(ProcessStepTypeId processStepTypeId, IEnumerable processStepTypeIds, CancellationToken cancellationToken) + { + if (_credentialId == Guid.Empty) + { + throw new UnexpectedConditionException("credentialId should never be empty here"); + } + + IEnumerable? nextStepTypeIds; + ProcessStepStatusId stepStatusId; + bool modified; + string? processMessage; + + try + { + (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch + { + ProcessStepTypeId.REVOKE_CREDENTIAL => await _credentialExpiryProcessHandler.RevokeCredential(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.TRIGGER_NOTIFICATION => await _credentialExpiryProcessHandler.TriggerNotification(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.TRIGGER_MAIL => await _credentialExpiryProcessHandler.TriggerMail(_credentialId, cancellationToken) + .ConfigureAwait(false), + _ => (null, ProcessStepStatusId.TODO, false, null) + }; + } + catch (Exception ex) when (ex is not SystemException) + { + (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex); + modified = true; + } + + return new IProcessTypeExecutor.StepExecutionResult(modified, stepStatusId, nextStepTypeIds, null, processMessage); + } + + private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex) + { + return ex switch + { + ServiceException { IsRecoverable: true } => (ProcessStepStatusId.TODO, ex.Message, null), + _ => (ProcessStepStatusId.FAILED, ex.Message, null) + }; + } +} diff --git a/src/processes/Processes.Library/Processes.Library.csproj b/src/processes/Processes.Library/Processes.Library.csproj index 56ca7374..f9aed95e 100644 --- a/src/processes/Processes.Library/Processes.Library.csproj +++ b/src/processes/Processes.Library/Processes.Library.csproj @@ -35,6 +35,7 @@ + diff --git a/src/processes/Processes.Worker/Program.cs b/src/processes/Processes.Worker/Program.cs index 125aee70..396cddb3 100644 --- a/src/processes/Processes.Worker/Program.cs +++ b/src/processes/Processes.Worker/Program.cs @@ -42,14 +42,15 @@ .AddProcessExecutionService(hostContext.Configuration.GetSection("Processes")) .AddPortalService(hostContext.Configuration.GetSection("Portal")) .AddCallbackService(hostContext.Configuration.GetSection("Callback")) - .AddCredentialProcessExecutor(hostContext.Configuration); + .AddCredentialCreationProcessExecutor(hostContext.Configuration) + .AddCredentialExpiryProcessExecutor(hostContext.Configuration); }) .AddLogging() .Build(); Log.Information("Building worker completed"); using var tokenSource = new CancellationTokenSource(); - Console.CancelKeyPress += (s, e) => + Console.CancelKeyPress += (_, e) => { Log.Information("Canceling..."); tokenSource.Cancel(); diff --git a/src/processes/Processes.Worker/appsettings.json b/src/processes/Processes.Worker/appsettings.json index 50b53b47..db783398 100644 --- a/src/processes/Processes.Worker/appsettings.json +++ b/src/processes/Processes.Worker/appsettings.json @@ -46,14 +46,7 @@ "Scope": "", "TokenAddress": "", "BaseAddress": "", - "EncrptionConfigIndex": 0, - "EncryptionConfigs": [ - { - "Index": 0, - "EncryptionKey": "", - "CipherMode": "", - "PaddingMode": "" - } - ] + "EncryptionConfigIndex": 0, + "EncryptionConfigs": [] } } diff --git a/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs index ffa94ee0..ba9a7f13 100644 --- a/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs +++ b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs @@ -30,6 +30,7 @@ using Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.DependencyInjection; using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.Tests; @@ -40,6 +41,7 @@ public class ExpiryCheckServiceTests private readonly IDateTimeProvider _dateTimeProvider; private readonly IIssuerRepositories _issuerRepositories; + private readonly IProcessStepRepository _processStepRepository; private readonly IPortalService _portalService; private readonly ICompanySsiDetailsRepository _companySsiDetailsRepository; private readonly ExpiryCheckServiceSettings _settings; @@ -56,9 +58,12 @@ public ExpiryCheckServiceTests() _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); _issuerRepositories = A.Fake(); _companySsiDetailsRepository = A.Fake(); + _processStepRepository = A.Fake(); A.CallTo(() => _issuerRepositories.GetInstance()) .Returns(_companySsiDetailsRepository); + A.CallTo(() => _issuerRepositories.GetInstance()) + .Returns(_processStepRepository); _dateTimeProvider = A.Fake(); _portalService = A.Fake(); @@ -127,13 +132,6 @@ public async Task ExecuteAsync_WithPendingAndExpiryBeforeNow_DeclinesRequest() A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); A.CallTo(() => _companySsiDetailsRepository.GetExpiryData(A._, A._, A._)) .Returns(data.ToAsyncEnumerable()); - A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(A._, - A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action updateFields) => - { - initialize?.Invoke(ssiDetail); - updateFields.Invoke(ssiDetail); - }); // Act await _sut.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); @@ -141,10 +139,8 @@ public async Task ExecuteAsync_WithPendingAndExpiryBeforeNow_DeclinesRequest() // Assert A.CallTo(() => _companySsiDetailsRepository.RemoveSsiDetail(ssiDetail.Id)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _portalService.AddNotification(A._, ssiDetail.CreatorUserId, NotificationTypeId.CREDENTIAL_REJECTED, A._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _portalService.TriggerMail("CredentialRejected", ssiDetail.CreatorUserId, A>._, A._)).MustHaveHappenedOnceExactly(); - - ssiDetail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.INACTIVE); + A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.DECLINE_CREDENTIAL)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _processStepRepository.CreateProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, ProcessStepStatusId.TODO, A._)).MustHaveHappenedOnceExactly(); } [Theory] @@ -189,7 +185,7 @@ public async Task ExecuteAsync_WithActiveCloseToExpiry_NotifiesCreator(int days, A.CallTo(() => _companySsiDetailsRepository.RemoveSsiDetail(ssiDetail.Id)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); A.CallTo(() => _portalService.AddNotification(A._, ssiDetail.CreatorUserId, NotificationTypeId.CREDENTIAL_EXPIRY, A._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _portalService.TriggerMail("CredentialExpiry", ssiDetail.CreatorUserId, A>._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalService.TriggerMail("CredentialExpiry", ssiDetail.CreatorUserId, A>._, A._)).MustHaveHappenedOnceExactly(); ssiDetail.ExpiryCheckTypeId.Should().Be(expiryCheckTypeId); } diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs index 09e18438..53ed1328 100644 --- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs @@ -59,18 +59,20 @@ public async Task GetDetailsForCompany_WithValidData_ReturnsExpected() var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, DateTimeOffset.MinValue).ToListAsync().ConfigureAwait(false); // Assert - result.Should().HaveCount(5); - result.Where(x => x.Description != null).Should().HaveCount(5).And.Satisfy( + result.Should().HaveCount(8); + result.Where(x => x.Description != null).Should().HaveCount(7).And.Satisfy( x => x.Description == "T", - x => x.Description == "CO2", x => x.Description == "BT", x => x.Description == "CE", - x => x.Description == "QM"); + x => x.Description == "QM", + x => x.Description == "DCM", + x => x.Description == "Puris", + x => x.Description == "BPDM"); var traceability = result.Single(x => x.CredentialType == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); traceability.VerifiedCredentials.Should().HaveCount(3).And.Satisfy( - x => x.ExternalDetailData.Version == "1.0.0" && x.SsiDetailData.Single().ParticipationStatus == CompanySsiDetailStatusId.PENDING, - x => x.ExternalDetailData.Version == "2.0.0" && !x.SsiDetailData.Any(), - x => x.ExternalDetailData.Version == "3.0.0" && !x.SsiDetailData.Any()); + x => x.ExternalDetailData.Version == "1.0" && x.SsiDetailData.Single().ParticipationStatus == CompanySsiDetailStatusId.PENDING, + x => x.ExternalDetailData.Version == "2.0" && !x.SsiDetailData.Any(), + x => x.ExternalDetailData.Version == "3.0" && !x.SsiDetailData.Any()); } [Fact] @@ -84,18 +86,20 @@ public async Task GetDetailsForCompany_WithExpiryFilter_ReturnsExpected() var result = await sut.GetUseCaseParticipationForCompany(ValidBpnl, dt).ToListAsync().ConfigureAwait(false); // Assert - result.Should().HaveCount(5); - result.Where(x => x.Description != null).Should().HaveCount(5).And.Satisfy( + result.Should().HaveCount(8); + result.Where(x => x.Description != null).Should().HaveCount(7).And.Satisfy( x => x.Description == "T", - x => x.Description == "CO2", x => x.Description == "BT", x => x.Description == "CE", - x => x.Description == "QM"); + x => x.Description == "QM", + x => x.Description == "DCM", + x => x.Description == "Puris", + x => x.Description == "BPDM"); var traceability = result.Single(x => x.CredentialType == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); traceability.VerifiedCredentials.Should().HaveCount(3).And.Satisfy( - x => x.ExternalDetailData.Version == "1.0.0" && x.SsiDetailData.Count() == 1, - x => x.ExternalDetailData.Version == "2.0.0" && !x.SsiDetailData.Any(), - x => x.ExternalDetailData.Version == "3.0.0" && !x.SsiDetailData.Any()); + x => x.ExternalDetailData.Version == "1.0" && x.SsiDetailData.Count() == 1, + x => x.ExternalDetailData.Version == "2.0" && !x.SsiDetailData.Any(), + x => x.ExternalDetailData.Version == "3.0" && !x.SsiDetailData.Any()); } #endregion diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs index 8b31a0ea..a306588a 100644 --- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs @@ -20,8 +20,11 @@ using AutoFixture; using AutoFixture.AutoFakeItEasy; using FluentAssertions; +using Microsoft.EntityFrameworkCore; using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using Xunit; using Xunit.Extensions.AssemblyFixture; @@ -135,6 +138,29 @@ public async Task GetExternalCredentialAndKindId_ReturnsExpectedDocument() #endregion + #region AttachAndModifyCredential + + [Fact] + public async Task AttachAndModifyCredential_ReturnsExpectedResult() + { + // Arrange + var (sut, context) = await CreateSutWithContext().ConfigureAwait(false); + + // Act + sut.AttachAndModifyCredential(Guid.NewGuid(), x => x.CompanySsiDetailStatusId = CompanySsiDetailStatusId.ACTIVE, x => x.CompanySsiDetailStatusId = CompanySsiDetailStatusId.PENDING); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().ContainSingle(); + var entity = changedEntries.Single(); + entity.State.Should().Be(EntityState.Modified); + ((CompanySsiDetail)entity.Entity).CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.PENDING); + } + + #endregion + #region Setup private async Task CreateSut() @@ -144,5 +170,12 @@ private async Task CreateSut() return sut; } + private async Task<(CredentialRepository Sut, IssuerDbContext Context)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new CredentialRepository(context); + return (sut, context); + } + #endregion } diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs index 2d24a59f..a33be766 100644 --- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs @@ -24,6 +24,7 @@ using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using System.Text; using Xunit; @@ -103,6 +104,36 @@ public async Task AssignDocumentToCompanySsiDetails_ReturnsExpectedDocument() #endregion + #region AttachAndModifyDocuments + + [Fact] + public async Task AttachAndModifyDocuments_ReturnsExpectedResult() + { + // Arrange + var (sut, context) = await CreateSut().ConfigureAwait(false); + + var documentData = new (Guid DocumentId, Action?, Action)[] { + (Guid.NewGuid(), null, document => document.DocumentStatusId = DocumentStatusId.INACTIVE), + (Guid.NewGuid(), document => document.DocumentStatusId = DocumentStatusId.INACTIVE, document => document.DocumentStatusId = DocumentStatusId.INACTIVE), + (Guid.NewGuid(), document => document.DocumentStatusId = DocumentStatusId.ACTIVE, document => document.DocumentStatusId = DocumentStatusId.INACTIVE), + }; + + // Act + sut.AttachAndModifyDocuments(documentData); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().HaveCount(3).And.AllSatisfy(x => x.Entity.Should().BeOfType()).And.Satisfy( + x => x.State == EntityState.Modified && ((Document)x.Entity).Id == documentData[0].DocumentId && ((Document)x.Entity).DocumentStatusId == DocumentStatusId.INACTIVE, + x => x.State == EntityState.Unchanged && ((Document)x.Entity).Id == documentData[1].DocumentId && ((Document)x.Entity).DocumentStatusId == DocumentStatusId.INACTIVE, + x => x.State == EntityState.Modified && ((Document)x.Entity).Id == documentData[2].DocumentId && ((Document)x.Entity).DocumentStatusId == DocumentStatusId.INACTIVE + ); + } + + #endregion + #region Setup private async Task<(DocumentRepository, IssuerDbContext)> CreateSut() diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json index 02688638..b12c0a51 100644 --- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json @@ -1,28 +1,28 @@ [ - { - "id": "1268a76a-ca19-4dd8-b932-01f24071d562", - "verified_credential_external_type_id": 3, - "version": "1.0.0", - "template": "https://catena-x.net/.._Policies_Trace_3.0_EN.pdf", - "valid_from": "2023-06-01 00:00:00.000000 +00:00", - "expiry": "2023-09-30 00:00:00.000000 +00:00" - }, { "id": "1268a76a-ca19-4dd8-b932-01f24071d563", "verified_credential_external_type_id": 1, - "version": "2.0.0", - "template": "https://catena-x.net/.._Policies_Trace_3.0_EN.pdf", + "version": "2.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf", "valid_from": "2023-06-01 00:00:00.000000 +00:00", "expiry": "2023-12-23 00:00:00.000000 +00:00" }, { "id": "1268a76a-ca19-4dd8-b932-01f24071d564", "verified_credential_external_type_id": 1, - "version": "3.0.0", - "template": "https://catena-x.net/.._Policies_Trace_3.0_EN.pdf", + "version": "3.0", + "template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf", "valid_from": "2024-01-01 00:00:00.000000 +00:00", "expiry": "2024-12-31 00:00:00.000000 +00:00" }, + { + "id": "1268a76a-ca19-4dd8-b932-01f24071d562", + "verified_credential_external_type_id": 3, + "version": "1.0", + "template": null, + "valid_from": "2023-06-01 00:00:00.000000 +00:00", + "expiry": "2023-09-30 00:00:00.000000 +00:00" + }, { "id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", "verified_credential_external_type_id": 4, @@ -30,5 +30,13 @@ "template": null, "valid_from": "2024-01-01 00:00:00.000000 +00:00", "expiry": "2999-12-31 23:59:59.000000 +00:00" + }, + { + "id": "1268a76a-ca19-4dd8-b932-01f24071d565", + "verified_credential_external_type_id": 5, + "version": "1.0", + "template": " https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_CircularEconomy.pdf", + "valid_from": "2024-01-01 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" } ] diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json index b66a8edc..e45dd47e 100644 --- a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json @@ -2,9 +2,5 @@ { "company_credential_detail_id": "9f5b9934-4014-4099-91e9-7b1aee696b03", "use_case_id": "06b243a4-ba51-4bf3-bc40-5d79a2231b86" - }, - { - "company_credential_detail_id": "9f5b9934-4014-4099-91e9-7b1aee696b04", - "use_case_id": "06b243a4-ba51-4bf3-bc40-5d79a2231b87" } ] diff --git a/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs b/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs index 65fb167f..69d5e05d 100644 --- a/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs +++ b/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs @@ -137,7 +137,7 @@ public async Task TriggerMail_WithValid_DoesNotThrowException() var sut = new PortalService(_tokenService, _options); // Act - await sut.TriggerMail("Test", requesterId, new Dictionary(), CancellationToken.None).ConfigureAwait(false); + await sut.TriggerMail("Test", requesterId, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); // Assert httpMessageHandlerMock.RequestMessage.Should().Match(x => @@ -166,7 +166,7 @@ public async Task TriggerMail_WithConflict_ThrowsServiceExceptionWithErrorConten var sut = new PortalService(_tokenService, _options); // Act - async Task Act() => await sut.TriggerMail("Test", requesterId, new Dictionary(), CancellationToken.None).ConfigureAwait(false); + async Task Act() => await sut.TriggerMail("Test", requesterId, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); diff --git a/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs b/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs index 7c72d5ca..70698329 100644 --- a/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs +++ b/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs @@ -171,7 +171,7 @@ public async Task GetCredential_WithValid_DoesNotThrowException() "root": "123" } """; - var response = new GetCredentialResponse("test", JsonDocument.Parse(json), "test123"); + var response = new GetCredentialResponse("test", JsonDocument.Parse(json), "test123", "VALID"); var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.OK, new StringContent(JsonSerializer.Serialize(response))); var httpClient = new HttpClient(httpMessageHandlerMock) { @@ -271,4 +271,112 @@ public async Task CreateCredentialForHolder_WithConflict_ThrowsServiceExceptionW } #endregion + + #region RevokeCredentialForIssuer + + [Fact] + public async Task RevokeCredentialForIssuer_WithValid_DoesNotThrowException() + { + // Arrange + var id = Guid.NewGuid(); + var response = new CreateCredentialResponse(id); + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.OK, new StringContent(JsonSerializer.Serialize(response))); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _basicAuthTokenService.GetBasicAuthorizedClient(A._, A._)) + .Returns(httpClient); + + // Act + await _sut.RevokeCredentialForIssuer(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(RevokeCredentialRequest) && + ((x.Content as JsonContent)!.Value as RevokeCredentialRequest)!.Payload.Revoke); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system revoke-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system revoke-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system revoke-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system revoke-credential failed with statuscode 403")] + public async Task RevokeCredentialForIssuer_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var httpMessageHandlerMock = content == null + ? new HttpMessageHandlerMock(statusCode) + : new HttpMessageHandlerMock(statusCode, new StringContent(content)); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _basicAuthTokenService.GetBasicAuthorizedClient(A._, A._)).Returns(httpClient); + + // Act + async Task Act() => await _sut.RevokeCredentialForIssuer(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion + + #region RevokeCredentialForHolder + + [Fact] + public async Task RevokeCredentialForHolder_WithValid_DoesNotThrowException() + { + // Arrange + var id = Guid.NewGuid(); + var response = new CreateCredentialResponse(id); + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.OK, new StringContent(JsonSerializer.Serialize(response))); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _basicAuthTokenService.GetBasicAuthorizedClient(A._, A._)) + .Returns(httpClient); + + // Act + await _sut.RevokeCredentialForHolder("https://test.de", "test123", "cl1", Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(RevokeCredentialRequest) && + ((x.Content as JsonContent)!.Value as RevokeCredentialRequest)!.Payload.Revoke); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system revoke-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system revoke-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system revoke-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system revoke-credential failed with statuscode 403")] + public async Task RevokeCredentialForHolder_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var httpMessageHandlerMock = content == null + ? new HttpMessageHandlerMock(statusCode) + : new HttpMessageHandlerMock(statusCode, new StringContent(content)); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _basicAuthTokenService.GetBasicAuthorizedClient(A._, A._)).Returns(httpClient); + + // Act + async Task Act() => await _sut.RevokeCredentialForHolder("https://test.de", "test123", "cl1", Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion } diff --git a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs index 4f35795d..e8998690 100644 --- a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs +++ b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs @@ -41,6 +41,8 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Tests.BusinessLogic; public class IssuerBusinessLogicTests { + private static readonly IEnumerable Context = new[] { "https://www.w3.org/2018/credentials/v1", "https://w3id.org/catenax/credentials/v1.0.0" }; + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; private static readonly Guid CredentialId = Guid.NewGuid(); private static readonly string Bpnl = "BPNL00000001TEST"; private static readonly string IssuerBpnl = "BPNL000001ISSUER"; @@ -64,6 +66,7 @@ public IssuerBusinessLogicTests() _fixture.Behaviors.OfType().ToList() .ForEach(b => _fixture.Behaviors.Remove(b)); _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _fixture.Customize(x => x.FromFactory(() => JsonDocument.Parse("{}"))); _issuerRepositories = A.Fake(); _companySsiDetailsRepository = A.Fake(); @@ -154,7 +157,7 @@ public async Task ApproveCredential_WithoutExistingSsiDetail_ThrowsNotFoundExcep // Assert ex.Message.Should().Be(CredentialErrors.SSI_DETAILS_NOT_FOUND.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -177,7 +180,7 @@ public async Task ApproveCredential_WithStatusNotPending_ThrowsConflictException // Assert ex.Message.Should().Be(CredentialErrors.CREDENTIAL_NOT_PENDING.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -199,7 +202,7 @@ public async Task ApproveCredential_WithBpnNotSetActiveSsiDetail_ThrowsConflictE // Assert ex.Message.Should().Be(CredentialErrors.BPN_NOT_SET.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -216,12 +219,14 @@ public async Task ApproveCredential_WithExpiryInThePast_ReturnsExpected() DateTimeOffset.Now.AddDays(-5) ); + var schema = CreateSchema(); var data = new SsiApprovalData( CompanySsiDetailStatusId.PENDING, typeId, null, VerifiedCredentialTypeKindId.FRAMEWORK, Bpnl, + JsonDocument.Parse(schema), detailData ); @@ -236,7 +241,7 @@ public async Task ApproveCredential_WithExpiryInThePast_ReturnsExpected() // Assert ex.Message.Should().Be(CredentialErrors.EXPIRY_DATE_IN_PAST.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _portalService.AddNotification(A._, A._, A._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -253,12 +258,14 @@ public async Task ApproveCredential_WithInvalidCredentialType_ThrowsException() DateTimeOffset.UtcNow ); + var schema = CreateSchema(); var data = new SsiApprovalData( CompanySsiDetailStatusId.PENDING, default, null, VerifiedCredentialTypeKindId.FRAMEWORK, Bpnl, + JsonDocument.Parse(schema), useCaseData ); @@ -285,6 +292,7 @@ public async Task ApproveCredential_WithDetailVersionNotSet_ThrowsConflictExcept null, VerifiedCredentialTypeKindId.FRAMEWORK, Bpnl, + null, null ); @@ -297,7 +305,7 @@ public async Task ApproveCredential_WithDetailVersionNotSet_ThrowsConflictExcept var ex = await Assert.ThrowsAsync(Act); // Assert - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) @@ -316,6 +324,7 @@ public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictExcep Guid.NewGuid(), VerifiedCredentialTypeKindId.FRAMEWORK, Bpnl, + null, new DetailData( VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, "test", @@ -333,7 +342,7 @@ public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictExcep var ex = await Assert.ThrowsAsync(Act); // Assert - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) @@ -343,11 +352,11 @@ public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictExcep [Theory] [InlineData(VerifiedCredentialTypeKindId.FRAMEWORK, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL)] - [InlineData(VerifiedCredentialTypeKindId.MEMBERSHIP, VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, VerifiedCredentialExternalTypeId.VEHICLE_DISMANTLE)] - [InlineData(VerifiedCredentialTypeKindId.BPN, VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, VerifiedCredentialExternalTypeId.BUSINESS_PARTNER_NUMBER)] public async Task ApproveCredential_WithValid_ReturnsExpected(VerifiedCredentialTypeKindId kindId, VerifiedCredentialTypeId typeId, VerifiedCredentialExternalTypeId externalTypeId) { // Arrange + var schema = CreateSchema(); + var processData = new CompanySsiProcessData(CredentialId, JsonDocument.Parse(schema), VerifiedCredentialTypeKindId.FRAMEWORK); var now = DateTimeOffset.UtcNow; var detailData = new DetailData( externalTypeId, @@ -362,6 +371,7 @@ public async Task ApproveCredential_WithValid_ReturnsExpected(VerifiedCredential null, kindId, Bpnl, + JsonDocument.Parse(schema), detailData ); @@ -375,19 +385,54 @@ public async Task ApproveCredential_WithValid_ReturnsExpected(VerifiedCredential initialize?.Invoke(detail); updateFields.Invoke(detail); }); + A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyProcessData(CredentialId, A?>._, A>._!)) + .Invokes((Guid _, Action? initialize, Action updateFields) => + { + initialize?.Invoke(processData); + updateFields.Invoke(processData); + }); // Act await _sut.ApproveCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); // Assert A.CallTo(() => _portalService.AddNotification(A._, A._, NotificationTypeId.CREDENTIAL_APPROVAL, A._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) .MustHaveHappenedOnceExactly(); detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.ACTIVE); detail.DateLastChanged.Should().Be(now); + processData.Schema.Deserialize()!.IssuanceDate.Should().Be(now); + } + + private static string CreateSchema() + { + var schemaData = new FrameworkCredential( + Guid.NewGuid(), + Context, + new[] { "VerifiableCredential", VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL.ToString() }, + VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL.ToString(), + $"Framework Credential for UseCase {VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL}", + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow, + "issuer", + new FrameworkCredentialSubject( + "test", + "123", + "UseCaseFramework", + VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL.ToString(), + "template", + "1.0" + ), + new CredentialStatus( + "https://example.com/statusList", + "StatusList2021") + ); + + var schema = JsonSerializer.Serialize(schemaData, Options); + return schema; } #endregion @@ -408,7 +453,7 @@ public async Task RejectCredential_WithoutExistingSsiDetail_ThrowsNotFoundExcept // Assert ex.Message.Should().Be(CredentialErrors.SSI_DETAILS_NOT_FOUND.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -434,7 +479,7 @@ public async Task RejectCredential_WithNotPendingSsiDetail_ThrowsNotFoundExcepti // Assert ex.Message.Should().Be(CredentialErrors.CREDENTIAL_NOT_PENDING.ToString()); - A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); } @@ -463,7 +508,7 @@ public async Task RejectCredential_WithValidRequest_ReturnsExpected() await _sut.RejectCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); // Assert - A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _portalService.AddNotification(A._, A._, NotificationTypeId.CREDENTIAL_REJECTED, A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); @@ -496,7 +541,7 @@ public async Task RejectCredential_WithValidRequestAndPendingProcessStepIds_Retu await _sut.RejectCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); // Assert - A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _portalService.AddNotification(A._, A._, NotificationTypeId.CREDENTIAL_REJECTED, A._)).MustHaveHappenedOnceExactly(); A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); A.CallTo(() => _processStepRepository.AttachAndModifyProcessSteps(A? Initialize, Action Modify)>>._)).MustHaveHappenedOnceExactly(); @@ -520,7 +565,7 @@ public async Task GetCertificateTypes_ReturnsExpected() var result = await _sut.GetCertificateTypes().ToListAsync().ConfigureAwait(false); // Assert - result.Should().HaveCount(7); + result.Should().HaveCount(10); } #endregion @@ -637,7 +682,7 @@ public async Task CreateFrameworkCredential_WithVersionNotExisting_ThrowsControl var useCaseId = Guid.NewGuid(); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>()); + .Returns(new ValueTuple, DateTimeOffset>()); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -656,7 +701,7 @@ public async Task CreateFrameworkCredential_WithExpiryInPast_ThrowsControllerArg A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, null, null, Enumerable.Empty(), now.AddDays(-5))); + .Returns(new ValueTuple, DateTimeOffset>(true, null, null, Enumerable.Empty(), now.AddDays(-5))); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -675,7 +720,7 @@ public async Task CreateFrameworkCredential_WithEmptyVersion_ThrowsControllerArg A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, null, null, Enumerable.Empty(), now.AddDays(5))); + .Returns(new ValueTuple, DateTimeOffset>(true, null, null, Enumerable.Empty(), now.AddDays(5))); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -694,7 +739,7 @@ public async Task CreateFrameworkCredential_WithEmptyTemplate_ThrowsControllerAr A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", null, Enumerable.Empty(), now.AddDays(5))); + .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", null, Enumerable.Empty(), now.AddDays(5))); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -713,7 +758,7 @@ public async Task CreateFrameworkCredential_WithMoreThanOneUseCase_ThrowsControl A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", new[] { "test", "test" }, now.AddDays(5))); + .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", new[] { VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL }, now.AddDays(5))); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -732,7 +777,7 @@ public async Task CreateFrameworkCredential_WithNoUseCase_ThrowsControllerArgume A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); var data = new CreateFrameworkCredentialRequest("BPNL0012HOLDER", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", Enumerable.Empty(), now.AddDays(5))); + .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", Enumerable.Empty(), now.AddDays(5))); async Task Act() => await _sut.CreateFrameworkCredential(data, CancellationToken.None).ConfigureAwait(false); // Act @@ -754,7 +799,7 @@ public async Task CreateFrameworkCredential_ReturnsExpected() var data = new CreateFrameworkCredentialRequest("https://example.org/holder/BPNL12343546/did.json", Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, useCaseId, null, null); HttpRequestMessage? request = null; A.CallTo(() => _companySsiDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(useCaseId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK)) - .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", Enumerable.Repeat("Test", 1), now.AddDays(5))); + .Returns(new ValueTuple, DateTimeOffset>(true, "1.0.0", "https://example.org/tempalte", Enumerable.Repeat(VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, 1), now.AddDays(5))); ConfigureHttpClientFactoryFixture(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, diff --git a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/RevocationBusinessLogicTests.cs b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/RevocationBusinessLogicTests.cs new file mode 100644 index 00000000..e382cb78 --- /dev/null +++ b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/RevocationBusinessLogicTests.cs @@ -0,0 +1,239 @@ +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Tests.BusinessLogic; + +public class RevocationBusinessLogicTests +{ + private static readonly Guid CredentialId = Guid.NewGuid(); + private readonly IFixture _fixture; + private readonly IDocumentRepository _documentRepository; + private readonly ICredentialRepository _credentialRepository; + + private readonly IRevocationBusinessLogic _sut; + private readonly IIssuerRepositories _issuerRepositories; + private readonly IWalletService _walletService; + + public RevocationBusinessLogicTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _issuerRepositories = A.Fake(); + _documentRepository = A.Fake(); + _credentialRepository = A.Fake(); + _walletService = A.Fake(); + + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_documentRepository); + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_credentialRepository); + + _sut = new RevocationBusinessLogic(_issuerRepositories, _walletService); + } + + #region RevokeIssuerCredential + + [Fact] + public async Task RevokeIssuerCredential_WithNotExisting_ThrowsNotFoundException() + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>()); + async Task Act() => await _sut.RevokeIssuerCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(RevocationDataErrors.CREDENTIAL_NOT_FOUND.ToString()); + } + + [Fact] + public async Task RevokeIssuerCredential_WithExternalCredentialIdNotSet_ThrowsConflictException() + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, null, default, null!)); + async Task Act() => await _sut.RevokeIssuerCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(RevocationDataErrors.EXTERNAL_CREDENTIAL_ID_NOT_SET.ToString()); + } + + [Theory] + [InlineData(CompanySsiDetailStatusId.PENDING)] + [InlineData(CompanySsiDetailStatusId.REVOKED)] + [InlineData(CompanySsiDetailStatusId.INACTIVE)] + public async Task RevokeIssuerCredential_WithStatusNotActiveRevoked_DoesNothing(CompanySsiDetailStatusId statusId) + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, Guid.NewGuid(), statusId, null!)); + + // Act + await _sut.RevokeIssuerCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _walletService.RevokeCredentialForIssuer(A._, A._)).MustNotHaveHappened(); + } + + [Fact] + public async Task RevokeIssuerCredential_WithValid_CallsExpected() + { + // Arrange + var credential = new CompanySsiDetail(CredentialId, "Test", VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, "Test123", Guid.NewGuid(), DateTimeOffset.UtcNow) + { + ExternalCredentialId = Guid.NewGuid() + }; + var document = _fixture + .Build() + .With(x => x.DocumentStatusId, DocumentStatusId.ACTIVE) + .Create(); + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, credential.ExternalCredentialId, CompanySsiDetailStatusId.ACTIVE, Enumerable.Repeat(new ValueTuple(document.Id, document.DocumentStatusId), 1))); + A.CallTo(() => _documentRepository.AttachAndModifyDocuments(A? Initialize, Action Modify)>>._)) + .Invokes((IEnumerable<(Guid DocumentId, Action? Initialize, Action Modify)> data) => + { + data.Select(x => + { + x.Initialize?.Invoke(document); + return document; + } + ).ToImmutableArray(); + data.Select(x => + { + x.Modify(document); + return document; + } + ).ToImmutableArray(); + }); + A.CallTo(() => _credentialRepository.AttachAndModifyCredential(credential.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize?.Invoke(credential); + modify(credential); + }); + + // Act + await _sut.RevokeIssuerCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _walletService.RevokeCredentialForIssuer(A._, A._)).MustHaveHappenedOnceExactly(); + document.DocumentStatusId.Should().Be(DocumentStatusId.INACTIVE); + credential.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.REVOKED); + } + + #endregion + + #region RevokeHolderCredential + + [Fact] + public async Task RevokeHolderCredential_WithNotExisting_ThrowsNotFoundException() + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>()); + async Task Act() => await _sut.RevokeHolderCredential(CredentialId, _fixture.Create(), CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(RevocationDataErrors.CREDENTIAL_NOT_FOUND.ToString()); + } + + [Fact] + public async Task RevokeHolderCredential_WithExternalCredentialIdNotSet_ThrowsConflictException() + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, null, default, null!)); + async Task Act() => await _sut.RevokeHolderCredential(CredentialId, _fixture.Create(), CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(RevocationDataErrors.EXTERNAL_CREDENTIAL_ID_NOT_SET.ToString()); + } + + [Theory] + [InlineData(CompanySsiDetailStatusId.PENDING)] + [InlineData(CompanySsiDetailStatusId.REVOKED)] + [InlineData(CompanySsiDetailStatusId.INACTIVE)] + public async Task RevokeHolderCredential_WithStatusNotActiveRevoked_DoesNothing(CompanySsiDetailStatusId statusId) + { + // Arrange + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, Guid.NewGuid(), statusId, null!)); + + // Act + await _sut.RevokeHolderCredential(CredentialId, _fixture.Create(), CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _walletService.RevokeCredentialForHolder(A._, A._, A._, A._, A._)).MustNotHaveHappened(); + } + + [Fact] + public async Task RevokeHolderCredential_WithValid_CallsExpected() + { + // Arrange + var credential = new CompanySsiDetail(CredentialId, "Test", VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, "Test123", Guid.NewGuid(), DateTimeOffset.UtcNow) + { + ExternalCredentialId = Guid.NewGuid() + }; + var document = _fixture + .Build() + .With(x => x.DocumentStatusId, DocumentStatusId.ACTIVE) + .Create(); + A.CallTo(() => _credentialRepository.GetRevocationDataById(CredentialId)) + .Returns(new ValueTuple>>(true, credential.ExternalCredentialId, CompanySsiDetailStatusId.ACTIVE, Enumerable.Repeat(new ValueTuple(document.Id, document.DocumentStatusId), 1))); + A.CallTo(() => _documentRepository.AttachAndModifyDocuments(A? Initialize, Action Modify)>>._)) + .Invokes((IEnumerable<(Guid DocumentId, Action? Initialize, Action Modify)> data) => + { + data.Select(x => + { + x.Initialize?.Invoke(document); + return document; + } + ).ToImmutableArray(); + data.Select(x => + { + x.Modify(document); + return document; + } + ).ToImmutableArray(); + }); + A.CallTo(() => _credentialRepository.AttachAndModifyCredential(credential.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize?.Invoke(credential); + modify(credential); + }); + + // Act + await _sut.RevokeHolderCredential(CredentialId, _fixture.Create(), CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _walletService.RevokeCredentialForHolder(A._, A._, A._, A._, A._)).MustHaveHappenedOnceExactly(); + document.DocumentStatusId.Should().Be(DocumentStatusId.INACTIVE); + credential.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.REVOKED); + } + + #endregion +} diff --git a/tests/processes/CredentialProcess.Library.Tests/CredentialProcessHandlerTests.cs b/tests/processes/CredentialProcess.Library.Tests/CredentialCreationProcessHandlerTests.cs similarity index 97% rename from tests/processes/CredentialProcess.Library.Tests/CredentialProcessHandlerTests.cs rename to tests/processes/CredentialProcess.Library.Tests/CredentialCreationProcessHandlerTests.cs index d580a86b..ddf2f15e 100644 --- a/tests/processes/CredentialProcess.Library.Tests/CredentialProcessHandlerTests.cs +++ b/tests/processes/CredentialProcess.Library.Tests/CredentialCreationProcessHandlerTests.cs @@ -24,6 +24,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Models; using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; @@ -35,7 +36,7 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Tests; -public class CredentialProcessHandlerTests +public class CredentialCreationProcessHandlerTests { private readonly Guid _credentialId = Guid.NewGuid(); @@ -43,11 +44,11 @@ public class CredentialProcessHandlerTests private readonly IIssuerRepositories _issuerRepositories; private readonly ICredentialRepository _credentialRepository; - private readonly CredentialProcessHandler _sut; + private readonly CredentialCreationProcessHandler _sut; private readonly IFixture _fixture; private readonly ICallbackService _callbackService; - public CredentialProcessHandlerTests() + public CredentialCreationProcessHandlerTests() { _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); _fixture.Behaviors.OfType().ToList() @@ -62,7 +63,7 @@ public CredentialProcessHandlerTests() _walletBusinessLogic = A.Fake(); _callbackService = A.Fake(); - _sut = new CredentialProcessHandler(_issuerRepositories, _walletBusinessLogic, _callbackService); + _sut = new CredentialCreationProcessHandler(_issuerRepositories, _walletBusinessLogic, _callbackService); } #region CreateCredential diff --git a/tests/processes/CredentialProcess.Library.Tests/CredentialExpiryProcessHandlerTests.cs b/tests/processes/CredentialProcess.Library.Tests/CredentialExpiryProcessHandlerTests.cs new file mode 100644 index 00000000..abb4f261 --- /dev/null +++ b/tests/processes/CredentialProcess.Library.Tests/CredentialExpiryProcessHandlerTests.cs @@ -0,0 +1,209 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using FakeItEasy; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Collections.Immutable; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Tests; + +public class CredentialExpiryProcessHandlerTests +{ + private readonly Guid _credentialId = Guid.NewGuid(); + + private readonly IWalletService _walletService; + private readonly IIssuerRepositories _issuerRepositories; + private readonly ICredentialRepository _credentialRepository; + private readonly IPortalService _portalService; + + private readonly CredentialExpiryProcessHandler _sut; + private readonly IFixture _fixture; + private readonly IDocumentRepository _documentRepository; + + public CredentialExpiryProcessHandlerTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _issuerRepositories = A.Fake(); + _credentialRepository = A.Fake(); + _documentRepository = A.Fake(); + + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_credentialRepository); + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_documentRepository); + + _walletService = A.Fake(); + _portalService = A.Fake(); + + _sut = new CredentialExpiryProcessHandler(_issuerRepositories, _walletService, _portalService); + } + + #region RevokeCredential + + [Fact] + public async Task RevokeCredential_WithValidData_ReturnsExpected() + { + // Arrange + var externalCredentialId = Guid.NewGuid(); + var credential = new CompanySsiDetail(_credentialId, "Test", VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, "Test123", Guid.NewGuid(), DateTimeOffset.UtcNow); + var document = _fixture + .Build() + .With(x => x.DocumentStatusId, DocumentStatusId.ACTIVE) + .Create(); + A.CallTo(() => _credentialRepository.GetRevocationDataById(_credentialId)) + .Returns(new ValueTuple>>(true, externalCredentialId, credential.CompanySsiDetailStatusId, Enumerable.Repeat(new ValueTuple(document.Id, document.DocumentStatusId), 1))); + A.CallTo(() => _credentialRepository.AttachAndModifyCredential(credential.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize?.Invoke(credential); + modify(credential); + }); + + A.CallTo(() => _documentRepository.AttachAndModifyDocuments(A? Initialize, Action Modify)>>._)) + .Invokes((IEnumerable<(Guid DocumentId, Action? Initialize, Action Modify)> data) => + { + data.Select(x => + { + x.Initialize?.Invoke(document); + return document; + } + ).ToImmutableArray(); + data.Select(x => + { + x.Modify(document); + return document; + } + ).ToImmutableArray(); + }); + + // Act + var result = await _sut.RevokeCredential(_credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _walletService.RevokeCredentialForIssuer(externalCredentialId, A._)) + .MustHaveHappenedOnceExactly(); + + credential.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.REVOKED); + document.DocumentStatusId.Should().Be(DocumentStatusId.INACTIVE); + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.TRIGGER_NOTIFICATION); + } + + [Fact] + public async Task RevokeCredential_WithNotExisting_ThrowsNotFoundException() + { + // Arrange + var externalCredentialId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetRevocationDataById(_credentialId)) + .Returns(new ValueTuple>>(false, null, default, Enumerable.Empty>())); + async Task Act() => await _sut.RevokeCredential(_credentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be($"Credential {_credentialId} does not exist"); + A.CallTo(() => _walletService.RevokeCredentialForIssuer(externalCredentialId, A._)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task RevokeCredential_WithEmptyExternalCredentialId_ThrowsConflictException() + { + // Arrange + var externalCredentialId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetRevocationDataById(_credentialId)) + .Returns(new ValueTuple>>(true, null, default, Enumerable.Empty>())); + async Task Act() => await _sut.RevokeCredential(_credentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be($"External Credential Id must be set for {_credentialId}"); + A.CallTo(() => _walletService.RevokeCredentialForIssuer(externalCredentialId, A._)) + .MustNotHaveHappened(); + } + + #endregion + + #region TriggerNotification + + [Fact] + public async Task TriggerNotification_WithValid_CallsExpected() + { + // Arrange + var requesterId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetCredentialNotificationData(_credentialId)) + .Returns(new ValueTuple(VerifiedCredentialTypeId.PCF_FRAMEWORK, requesterId)); + + // Act + var result = await _sut.TriggerNotification(_credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _portalService.AddNotification(A._, requesterId, NotificationTypeId.CREDENTIAL_REJECTED, A._)) + .MustHaveHappenedOnceExactly(); + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.TRIGGER_MAIL); + } + + #endregion + + #region TriggerMail + + [Fact] + public async Task TriggerMail_WithValid_CallsExpected() + { + // Arrange + var requesterId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetCredentialNotificationData(_credentialId)) + .Returns(new ValueTuple(VerifiedCredentialTypeId.PCF_FRAMEWORK, requesterId)); + + // Act + var result = await _sut.TriggerMail(_credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", requesterId, A>._, A._)) + .MustHaveHappenedOnceExactly(); + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().BeNull(); + } + + #endregion +} diff --git a/tests/processes/CredentialProcess.Worker.Tests/CredentialProcessTypeExecutorTests.cs b/tests/processes/CredentialProcess.Worker.Tests/CredentialCreationProcessTypeExecutorTests.cs similarity index 90% rename from tests/processes/CredentialProcess.Worker.Tests/CredentialProcessTypeExecutorTests.cs rename to tests/processes/CredentialProcess.Worker.Tests/CredentialCreationProcessTypeExecutorTests.cs index 761d2289..4bfe9c34 100644 --- a/tests/processes/CredentialProcess.Worker.Tests/CredentialProcessTypeExecutorTests.cs +++ b/tests/processes/CredentialProcess.Worker.Tests/CredentialCreationProcessTypeExecutorTests.cs @@ -22,21 +22,22 @@ using FakeItEasy; using FluentAssertions; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Creation; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; using Xunit; -namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Tests; +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Tests; -public class CredentialProcessTypeExecutorTests +public class CredentialCreationProcessTypeExecutorTests { - private readonly CredentialProcessTypeExecutor _sut; - private readonly ICredentialProcessHandler _credentialProcessHandler; + private readonly CredentialCreationProcessTypeExecutor _sut; + private readonly ICredentialCreationProcessHandler _credentialCreationProcessHandler; private readonly ICredentialRepository _credentialRepository; - public CredentialProcessTypeExecutorTests() + public CredentialCreationProcessTypeExecutorTests() { var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); fixture.Behaviors.OfType().ToList() @@ -44,13 +45,13 @@ public CredentialProcessTypeExecutorTests() fixture.Behaviors.Add(new OmitOnRecursionBehavior()); var issuerRepositories = A.Fake(); - _credentialProcessHandler = A.Fake(); + _credentialCreationProcessHandler = A.Fake(); _credentialRepository = A.Fake(); A.CallTo(() => issuerRepositories.GetInstance()).Returns(_credentialRepository); - _sut = new CredentialProcessTypeExecutor(issuerRepositories, _credentialProcessHandler); + _sut = new CredentialCreationProcessTypeExecutor(issuerRepositories, _credentialCreationProcessHandler); } [Fact] @@ -155,7 +156,7 @@ public async Task ExecuteProcessStep_WithValidData_CallsExpected() initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - A.CallTo(() => _credentialProcessHandler.CreateCredential(credentialId, A._)) + A.CallTo(() => _credentialCreationProcessHandler.CreateCredential(credentialId, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); // Act @@ -186,7 +187,7 @@ public async Task ExecuteProcessStep_WithRecoverableServiceException_ReturnsToDo initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - A.CallTo(() => _credentialProcessHandler.CreateCredential(credentialId, A._)) + A.CallTo(() => _credentialCreationProcessHandler.CreateCredential(credentialId, A._)) .Throws(new ServiceException("this is a test", true)); // Act @@ -217,7 +218,7 @@ public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetrig initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - A.CallTo(() => _credentialProcessHandler.CreateCredential(credentialId, A._)) + A.CallTo(() => _credentialCreationProcessHandler.CreateCredential(credentialId, A._)) .Throws(new ServiceException("this is a test")); // Act diff --git a/tests/processes/CredentialProcess.Worker.Tests/CredentialExpiryProcessTypeExecutorTests.cs b/tests/processes/CredentialProcess.Worker.Tests/CredentialExpiryProcessTypeExecutorTests.cs new file mode 100644 index 00000000..724f9e46 --- /dev/null +++ b/tests/processes/CredentialProcess.Worker.Tests/CredentialExpiryProcessTypeExecutorTests.cs @@ -0,0 +1,236 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using FakeItEasy; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Creation; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.Expiry; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Creation; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Expiry; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.Tests; + +public class CredentialExpiryProcessTypeExecutorTests +{ + private readonly CredentialExpiryProcessTypeExecutor _sut; + private readonly ICredentialExpiryProcessHandler _credentialExpiryProcessHandler; + private readonly ICredentialRepository _credentialRepository; + + public CredentialExpiryProcessTypeExecutorTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + var issuerRepositories = A.Fake(); + _credentialExpiryProcessHandler = A.Fake(); + + _credentialRepository = A.Fake(); + + A.CallTo(() => issuerRepositories.GetInstance()).Returns(_credentialRepository); + + _sut = new CredentialExpiryProcessTypeExecutor(issuerRepositories, _credentialExpiryProcessHandler); + } + + [Fact] + public void GetProcessTypeId_ReturnsExpected() + { + // Assert + _sut.GetProcessTypeId().Should().Be(ProcessTypeId.DECLINE_CREDENTIAL); + } + + [Fact] + public void IsExecutableStepTypeId_WithValid_ReturnsExpected() + { + // Assert + _sut.IsExecutableStepTypeId(ProcessStepTypeId.REVOKE_CREDENTIAL).Should().BeTrue(); + } + + [Fact] + public void GetExecutableStepTypeIds_ReturnsExpected() + { + // Assert + _sut.GetExecutableStepTypeIds().Should().HaveCount(3).And.Satisfy( + x => x == ProcessStepTypeId.REVOKE_CREDENTIAL, + x => x == ProcessStepTypeId.TRIGGER_MAIL, + x => x == ProcessStepTypeId.TRIGGER_NOTIFICATION); + } + + [Fact] + public async Task IsLockRequested_ReturnsExpected() + { + // Act + var result = await _sut.IsLockRequested(ProcessStepTypeId.REVOKE_CREDENTIAL).ConfigureAwait(false); + + // Assert + result.Should().BeFalse(); + } + + #region InitializeProcess + + [Fact] + public async Task InitializeProcess_WithExistingProcess_ReturnsExpected() + { + // Arrange + var validProcessId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetDataForProcessId(validProcessId)) + .Returns(new ValueTuple(true, Guid.NewGuid())); + + // Act + var result = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + + // Assert + result.Modified.Should().BeFalse(); + result.ScheduleStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundException() + { + // Arrange + var validProcessId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetDataForProcessId(validProcessId)) + .Returns(new ValueTuple(false, Guid.Empty)); + + // Act + async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be($"process {validProcessId} does not exist or is not associated with an credential"); + } + + #endregion + + #region ExecuteProcessStep + + [Fact] + public async Task ExecuteProcessStep_WithoutRegistrationId_ThrowsUnexpectedConditionException() + { + // Act + async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.SIGN_CREDENTIAL, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be("credentialId should never be empty here"); + } + + [Fact] + public async Task ExecuteProcessStep_WithValidData_CallsExpected() + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetDataForProcessId(validProcessId)) + .Returns(new ValueTuple(true, credentialId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + A.CallTo(() => _credentialExpiryProcessHandler.RevokeCredential(credentialId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + + // Act + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + + // Assert + result.Modified.Should().BeFalse(); + result.ScheduleStepTypeIds.Should().BeNull(); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ProcessMessage.Should().BeNull(); + result.SkipStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task ExecuteProcessStep_WithRecoverableServiceException_ReturnsToDo() + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetDataForProcessId(validProcessId)) + .Returns(new ValueTuple(true, credentialId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + A.CallTo(() => _credentialExpiryProcessHandler.RevokeCredential(credentialId, A._)) + .Throws(new ServiceException("this is a test", true)); + + // Act + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + + // Assert + result.Modified.Should().BeTrue(); + result.ScheduleStepTypeIds.Should().BeNull(); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.TODO); + result.ProcessMessage.Should().Be("this is a test"); + result.SkipStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetriggerStep() + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + A.CallTo(() => _credentialRepository.GetDataForProcessId(validProcessId)) + .Returns(new ValueTuple(true, credentialId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + A.CallTo(() => _credentialExpiryProcessHandler.RevokeCredential(credentialId, A._)) + .Throws(new ServiceException("this is a test")); + + // Act + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + + // Assert + result.Modified.Should().BeTrue(); + result.ScheduleStepTypeIds.Should().BeNull(); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.FAILED); + result.ProcessMessage.Should().Be("this is a test"); + result.SkipStepTypeIds.Should().BeNull(); + } + + #endregion +}