From 609567a6131fdcb1f12ea8a6653b5dbc9963816e Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Mon, 25 Mar 2024 09:35:47 +0100 Subject: [PATCH 01/32] feat: add issuer component (#18) * feat: add issuer component * build: add helm chart for issuer component * feat(notification): adjust notification endpoint * fix(build): enable build of docker images (#21) * feat: add callback process step * chore: enable helm chart (#22) * fix: remove lint issue * fix: solve templating issues * chore: change setup of cronjobs: remove hooks * chore: change name setup of job resources * chore: add line breaks * chore: move placeholder value into resources * chore: change to unique templates for db subchart * chore: change secret setup * chore: move passwords from db dependency to according section * chore: remove upgrade env file * chore: change centralidp setup * chore: rearrange health checks * chore: rearrange values file * chore: change ingress to trg-5.04 * chore: fix container name and namespace * chore: change image tag retrieval * chore: change version * chore(db-dependency): change image tag to get latest minor updates * chore: set resource limits * chore: update readme files * chore: change credentialexpiry to camelcase * chore: fix helm chart, improve workflows and docs (#23) * chore(helm-test): fix image name and tag override at upgrade * chore: fix owasp scan * chore(helm-test and owasp): change set command * chore: re-arrange values file * chore(pre-checks): run only on changes to src/** * docs(CONTRIBUTING.md): update to contribution details * chore: fix db dependency secret name in cronjobs * chore(dependencies-check): align file naming and docs * chore: remove white space --------- Refs: #2 #3 #4 #5 #6 #7 #8 #9 #13 #21 #22 #23 Reviewed-by: Evelyn Gurschler Reviewed-by: Norbert Truchsess Co-authored-by: Evelyn Gurschler Co-authored-by: Norbert Truchsess --- .github/workflows/chart-test.yml | 32 +- .../credential-expiry-app-docker.yml | 91 ++ .github/workflows/dependencies.yml | 2 +- .github/workflows/migrations-docker.yml | 6 +- .github/workflows/owasp-zap.yml | 28 +- .github/workflows/processes-worker-docker.yml | 91 ++ .github/workflows/release.yml | 70 +- .github/workflows/release_candidate.yml | 118 +- .github/workflows/service-docker.yml | 8 +- .github/workflows/trivy-dev.yml | 71 +- .github/workflows/trivy.yml | 69 +- .github/workflows/unit.tests-formatting.yml | 4 + CONTRIBUTING.md | 6 +- DEPENDENCIES | 57 + FILEHEADER.md | 2 +- README.md | 15 +- charts/chart-testing-config.yaml | 23 + charts/ssi-credential-issuer/.helmignore | 27 + charts/ssi-credential-issuer/Chart.yaml | 31 + charts/ssi-credential-issuer/LICENSE | 201 +++ charts/ssi-credential-issuer/README.md | 162 ++ charts/ssi-credential-issuer/README.md.gotmpl | 37 + .../templates/_helpers.tpl | 109 ++ .../templates/configmap-postgres-init.yaml | 35 + .../templates/cronjob-expiry-app.yaml | 80 + .../templates/cronjob-issuer-processes.yaml | 125 ++ .../templates/deployment-issuer-service.yaml | 177 ++ .../templates/ingress.yaml | 80 + .../templates/job-issuer-migrations.yaml | 79 + .../templates/secret-external-db.yaml | 39 + .../templates/secret-postgres.yaml | 45 + .../templates/secret.yaml | 49 + .../templates/service-credential-issuer.yaml | 33 + charts/ssi-credential-issuer/values.yaml | 319 ++++ .../argocd-app-templates/appsetup-beta.yaml | 38 + .../argocd-app-templates/appsetup-dev.yaml | 41 + .../argocd-app-templates/appsetup-int.yaml | 38 + .../argocd-app-templates/appsetup-pen.yaml | 38 + .../argocd-app-templates/appsetup-rc.yaml | 38 + .../argocd-app-templates/appsetup-stable.yaml | 77 + .../appsetup-upgrade.yaml | 38 + consortia/environments/values-beta.yaml | 67 + consortia/environments/values-dev.yaml | 79 + consortia/environments/values-int.yaml | 67 + consortia/environments/values-pen.yaml | 67 + consortia/environments/values-rc.yaml | 79 + docker/Dockerfile-credential-expiry-app | 36 + .../Dockerfile-credential-issuer-migrations | 36 + ...kerfile-credential-issuer-processes-worker | 36 + docker/Dockerfile-credential-issuer-service | 37 + docker/notice-credential-expiry-app.md | 22 + docker/notice-credential-issuer-migrations.md | 22 + ...tice-credential-issuer-processes-worker.md | 22 + docker/notice-credential-issuer-service.md | 22 + scripts/check-dependencies.md | 2 +- ...ar => org.eclipse.dash.licenses-1.1.1.jar} | Bin src/Directory.Build.props | 25 + src/SsiCredentialIssuer.sln | 191 +++ .../ExpiryCheckServiceExtensions.cs | 45 + .../ExpiryCheckServiceSettings.cs | 40 + .../ExpiryCheckService.cs | 196 +++ .../SsiCredentialIssuer.Expiry.App/Program.cs | 71 + .../Properties/launchSettings.json | 11 + .../SsiCredentialIssuer.Expiry.App.csproj | 80 + .../appsettings.json | 33 + .../Extensions/MediaTypeIdExtensions.cs | 65 + .../IIssuerRepositories.cs | 38 + .../IssuerRepositories.cs | 78 + ...uerRepositoriesStartupServiceExtensions.cs | 40 + .../Models/CompanySsiDetailSorting.cs | 38 + .../Models/CredentialDetailData.cs | 34 + .../Models/CredentialExpiryData.cs | 41 + .../Models/DocumentData.cs | 28 + .../Models/EncryptionTransformationData.cs | 26 + .../Models/ExternalTypeDetailData.cs | 31 + .../Models/SsiApprovalData.cs | 38 + .../Models/SsiCertificateTransferData.cs | 34 + .../Models/TechnicalUserDetails.cs | 33 + .../UseCaseParticipationTransferData.cs | 44 + .../CompanySsiDetailsRepository.cs | 289 ++++ .../Repositories/CredentialRepository.cs | 79 + .../Repositories/DocumentRepository.cs | 63 + .../ICompanySsiDetailsRepository.cs | 98 ++ .../Repositories/ICredentialRepository.cs | 34 + .../Repositories/IDocumentRepository.cs | 43 + .../Repositories/IProcessStepRepository.cs | 37 + .../Repositories/ProcessStepRepository.cs | 100 ++ .../SsiCredentialIssuer.DbAccess.csproj | 42 + .../AuditCompanySsiDetail20240228.cs | 58 + .../AuditEntities/AuditDocument20240305.cs | 61 + .../Attributes/AuditEntityV1Attribute.cs | 43 + .../AuditInsertEditorV1Attribute.cs | 34 + .../Attributes/LastChangedV1Attribute.cs | 32 + .../Attributes/LastEditorV1Attribute.cs | 32 + .../AuditingDependencyInjection.cs | 34 + .../Auditing/Enums/AuditOperationId.cs | 41 + .../Auditing/Enums/AuditPropertyV1Names.cs | 28 + .../Auditing/Extensions/AuditExtensions.cs | 86 + .../EntityTypeBuilderV1Extension.cs | 129 ++ .../Auditing/Handler/AuditHandlerV1.cs | 109 ++ .../Auditing/Handler/IAuditHandler.cs | 28 + .../Auditing/IAuditEntityV1.cs | 52 + .../Auditing/IAuditableV1.cs | 31 + .../Auditing/IBaseEntity.cs | 28 + .../Auditing/Identity/IIdentityIdService.cs | 25 + .../Entities/CompanySsiDetail.cs | 78 + .../CompanySsiDetailAssignedDocument.cs | 16 + .../Entities/CompanySsiDetailStatus.cs | 47 + .../Entities/CompanySsiProcessData.cs | 51 + .../Entities/Document.cs | 83 + .../Entities/DocumentStatus.cs | 46 + .../Entities/DocumentType.cs | 46 + .../Entities/ExpiryCheckType.cs | 48 + .../Entities/MediaType.cs | 46 + .../Entities/Process.cs | 55 + .../Entities/ProcessStep.cs | 56 + .../Entities/ProcessStepStatus.cs | 46 + .../Entities/ProcessStepType.cs | 46 + .../Entities/ProcessType.cs | 46 + .../Entities/UseCase.cs | 50 + .../VerifiedCredentialExternalType.cs | 43 + ...fiedCredentialExternalTypeDetailVersion.cs | 41 + .../Entities/VerifiedCredentialType.cs | 52 + ...ifiedCredentialTypeAssignedExternalType.cs | 46 + .../VerifiedCredentialTypeAssignedKind.cs | 46 + .../VerifiedCredentialTypeAssignedUseCase.cs | 38 + .../Entities/VerifiedCredentialTypeKind.cs | 49 + .../Entities/VerifyProcessData.cs | 25 + .../Enums/CompanySsiDetailStatusId.cs | 28 + .../Enums/DocumentStatusId.cs | 36 + .../Enums/DocumentTypeId.cs | 27 + .../Enums/ExpiryCheckTypeId.cs | 27 + .../Enums/MediaTypeId.cs | 35 + .../Enums/ProcessStepStatusId.cs | 29 + .../Enums/ProcessStepTypeId.cs | 30 + .../Enums/ProcessTypeId.cs | 25 + .../Enums/VerifiedCredentialExternalTypeId.cs | 46 + .../Enums/VerifiedCredentialTypeId.cs | 46 + .../Enums/VerifiedCredentialTypeKindId.cs | 27 + .../IssuerDbContext.cs | 318 ++++ .../SsiCredentialIssuer.Entities.csproj | 46 + .../20240321135530_1.0.0-rc.1.Designer.cs | 1432 +++++++++++++++++ .../Migrations/20240321135530_1.0.0-rc.1.cs | 952 +++++++++++ .../IssuerDbContextModelSnapshot.cs | 1425 ++++++++++++++++ .../SsiCredentialIssuer.Migrations/Program.cs | 64 + .../Properties/launchSettings.json | 10 + .../Seeder/BatchInsertSeeder.cs | 108 ++ .../Seeder/BatchUpdateSeeder.cs | 103 ++ .../Seeder/Data/use_cases.json | 57 + ...ternal_type_detail_versions.consortia.json | 42 + ...dential_external_type_detail_versions.json | 26 + ...edential_type_assigned_external_types.json | 26 + ...rified_credential_type_assigned_kinds.json | 30 + ...ed_credential_type_assigned_use_cases.json | 22 + .../SsiCredentialIssuer.Migrations.csproj | 82 + .../appsettings.json | 39 + .../Callback.Service/Callback.Service.csproj | 35 + .../DependencyInjection/CallbackSettings.cs | 26 + .../ServiceCollectionExtensions.cs | 41 + .../Models/IssuerResponseData.cs | 33 + .../Services/CallbackService.cs | 55 + .../Services/ICallbackService.cs | 27 + .../DependencyInjection/PortalSettings.cs | 29 + .../ServiceCollectionExtensions.cs | 44 + .../Portal.Service/Models/MailData.cs | 28 + .../Models/NotificationRequest.cs | 28 + .../Models/NotificationTypeId.cs | 156 ++ .../Portal.Service/Portal.Service.csproj | 35 + .../Portal.Service/Services/IPortalService.cs | 28 + .../Portal.Service/Services/PortalService.cs | 60 + .../BusinessLogic/IWalletBusinessLogic.cs | 32 + .../BusinessLogic/WalletBusinessLogic.cs | 122 ++ .../ServiceCollectionExtensions.cs | 47 + .../DependencyInjection/WalletSettings.cs | 36 + .../Models/CreateCredentialResponse.cs | 36 + .../Wallet.Service/Models/CredentialData.cs | 31 + .../Models/DeriveCredentialData.cs | 35 + .../Models/EncryptionInformation.cs | 26 + .../Models/GetCredentialResponse.cs | 10 + .../Models/SignCredentialRequest.cs | 39 + .../Schemas/BPNCredential.schema.json | 59 + .../Schemas/FRAMEWORKCredential.schema.json | 65 + .../Schemas/MEMBERSHIPCredential.schema.json | 59 + .../Services/BasicAuthSettings.cs | 36 + .../Services/BasicAuthTokenService.cs | 72 + .../Services/IBasicAuthTokenService.cs | 25 + .../Wallet.Service/Services/IWalletService.cs | 30 + .../Wallet.Service/Services/WalletService.cs | 116 ++ .../Wallet.Service/Wallet.Service.csproj | 67 + .../Authentication/CustomClaimTypes.cs | 28 + .../KeycloakClaimsTransformation.cs | 73 + .../BusinessLogic/IIssuerBusinessLogic.cs | 43 + .../BusinessLogic/IssuerBusinessLogic.cs | 516 ++++++ .../BusinessLogic/IssuerSettings.cs | 65 + .../Controllers/IssuerController.cs | 174 ++ .../CredentialServiceCollectionExtensions.cs | 30 + .../CredentialErrorMessageContainer.cs | 92 ++ .../RouteHandlerBuilderExtensions.cs | 46 + .../Identity/ClaimTypes.cs | 26 + .../Identity/ClaimsIdentityDataBuilder.cs | 43 + .../Identity/ClaimsIdentityService.cs | 36 + ...aimsIdentityServiceCollectionExtensions.cs | 33 + .../Identity/IClaimsIdentityDataBuilder.cs | 35 + .../Identity/IIdentityData.cs | 26 + .../Identity/IIdentityService.cs | 25 + .../Identity/IdentityIdService.cs | 47 + .../Identity/MandatoryIdentityClaimHandler.cs | 99 ++ .../Identity/PolicyTypes.cs | 32 + .../Models/Constants.cs | 25 + .../Models/CreateBpnCredentialRequest.cs | 48 + .../Models/CredentialData.cs | 84 + .../Models/DidDocument.cs | 26 + .../UseCaseParticipationCreationData.cs | 36 + .../Models/UseCaseParticipationData.cs | 51 + .../SsiCredentialIssuer.Service/Program.cs | 64 + .../Properties/launchSettings.json | 40 + .../SsiCredentialIssuer.Service.csproj | 73 + .../appsettings.json | 71 + .../CredentialProcess.Library.csproj | 36 + .../CredentialProcessHandler.cs | 132 ++ .../CredentialHandlerExtensions.cs | 36 + .../ICredentialProcessHandler.cs | 31 + .../CredentialProcess.Worker.csproj | 35 + .../CredentialProcessTypeExecutor.cs | 115 ++ .../CredentialProcessCollectionExtensions.cs | 33 + .../ManualProcessStepData.cs | 31 + .../ManualProcessStepDataExtensions.cs | 131 ++ .../Processes.Library.csproj | 40 + .../ServiceCollectionExtensions.cs | 34 + .../IProcessExecutor.cs | 35 + .../IProcessTypeExecutor.cs | 46 + .../ProcessExecutionService.cs | 158 ++ .../ProcessExecutionServiceExtensions.cs | 36 + .../ProcessExecutionServiceSettings.cs | 31 + .../ProcessExecutor.cs | 213 +++ .../ProcessIdentityIdService.cs | 16 + .../Processes.Worker.Library.csproj | 45 + .../Processes.Worker/Processes.Worker.csproj | 56 + src/processes/Processes.Worker/Program.cs | 72 + .../Properties/launchSettings.json | 10 + .../Processes.Worker/appsettings.json | 59 + src/settings-coverage.xml | 114 ++ .../Tests.Shared/Extensions/HttpExtensions.cs | 52 + tests/Tests.Shared/FakeIdentity.cs | 28 + tests/Tests.Shared/FakeIdentityIdService.cs | 29 + tests/Tests.Shared/FakeIdentityService.cs | 28 + tests/Tests.Shared/HttpMessageHandlerMock.cs | 61 + tests/Tests.Shared/NoAuditHandler.cs | 33 + tests/Tests.Shared/Tests.Shared.csproj | 33 + .../ExpiryCheckServiceTests.cs | 196 +++ .../GlobalUsings.cs | 23 + ...siCredentialIssuer.Expiry.App.Tests.csproj | 55 + .../CompanySsiDetailsRepositoryTests.cs | 588 +++++++ .../ContentTypeMapperExtensionsTests.cs | 83 + .../CredentialRepositoryTests.cs | 148 ++ .../DocumentRepositoryTests.cs | 116 ++ .../IssuerDbContextTests.cs | 117 ++ .../IssuerRepositoriesTests.cs | 168 ++ .../ProcessStepRepositoryTests.cs | 359 +++++ .../Seeder/Data/company_ssi_details.test.json | 95 ++ .../Data/company_ssi_process_datas.test.json | 12 + .../Seeder/Data/documents.test.json | 79 + .../Seeder/Data/process_steps.test.json | 26 + .../Seeder/Data/processes.test.json | 8 + ...al_external_type_detail_versions.test.json | 34 + ...edential_type_assigned_use_cases.test.json | 10 + .../Setup/TestDbFixture.cs | 114 ++ .../SsiCredentialIssuer.DbAccess.Tests.csproj | 67 + .../Callback.Service.Tests.csproj | 60 + .../CallbackServiceTests.cs | 122 ++ .../Portal.Service.Tests.csproj | 60 + .../PortalServiceTests.cs | 178 ++ .../BusinessLogic/WalletBusinessLogicTests.cs | 238 +++ .../Services/BasicAuthTokenServiceTests.cs | 127 ++ .../Services/WalletServiceTests.cs | 274 ++++ .../Wallet.Service.Tests.csproj | 61 + .../KeycloakClaimsTransformationTests.cs | 97 ++ .../BusinessLogic/IssuerBusinessLogicTests.cs | 815 ++++++++++ .../Controllers/IssuerControllerTests.cs | 77 + .../MandatoryIdentityClaimHandlerTests.cs | 128 ++ .../Setup/AsyncEnumerableStub.cs | 41 + .../Setup/AsyncEnumeratorStub.cs | 43 + .../Setup/AsyncQueryProviderStub.cs | 70 + .../Setup/FakePolicyEvaluator.cs | 35 + .../Setup/IntegrationTestFactory.cs | 139 ++ .../SsiCredentialIssuer.Service.Tests.csproj | 67 + .../Usings.cs | 24 + .../appsettings.IntegrationTests.json | 39 + .../CredentialProcess.Library.Tests.csproj | 48 + .../CredentialProcessHandlerTests.cs | 304 ++++ .../CredentialProcess.Worker.Tests.csproj | 47 + .../CredentialProcessTypeExecutorTests.cs | 235 +++ .../ManualProcessDataExtensionsTests.cs | 453 ++++++ .../Processes.Library.Tests.csproj | 47 + .../Processes.Library.Tests/Usings.cs | 24 + .../MockLogger.cs | 52 + .../ProcessExecutionServiceTests.cs | 513 ++++++ .../ProcessExecutorTests.cs | 880 ++++++++++ .../Processes.Worker.Library.Tests.csproj | 47 + .../Processes.Worker.Library.Tests/Usings.cs | 24 + 300 files changed, 25349 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/credential-expiry-app-docker.yml create mode 100644 .github/workflows/processes-worker-docker.yml create mode 100644 charts/chart-testing-config.yaml create mode 100644 charts/ssi-credential-issuer/.helmignore create mode 100644 charts/ssi-credential-issuer/Chart.yaml create mode 100644 charts/ssi-credential-issuer/LICENSE create mode 100644 charts/ssi-credential-issuer/README.md create mode 100644 charts/ssi-credential-issuer/README.md.gotmpl create mode 100644 charts/ssi-credential-issuer/templates/_helpers.tpl create mode 100644 charts/ssi-credential-issuer/templates/configmap-postgres-init.yaml create mode 100644 charts/ssi-credential-issuer/templates/cronjob-expiry-app.yaml create mode 100644 charts/ssi-credential-issuer/templates/cronjob-issuer-processes.yaml create mode 100644 charts/ssi-credential-issuer/templates/deployment-issuer-service.yaml create mode 100644 charts/ssi-credential-issuer/templates/ingress.yaml create mode 100644 charts/ssi-credential-issuer/templates/job-issuer-migrations.yaml create mode 100644 charts/ssi-credential-issuer/templates/secret-external-db.yaml create mode 100644 charts/ssi-credential-issuer/templates/secret-postgres.yaml create mode 100644 charts/ssi-credential-issuer/templates/secret.yaml create mode 100644 charts/ssi-credential-issuer/templates/service-credential-issuer.yaml create mode 100644 charts/ssi-credential-issuer/values.yaml create mode 100644 consortia/argocd-app-templates/appsetup-beta.yaml create mode 100644 consortia/argocd-app-templates/appsetup-dev.yaml create mode 100644 consortia/argocd-app-templates/appsetup-int.yaml create mode 100644 consortia/argocd-app-templates/appsetup-pen.yaml create mode 100644 consortia/argocd-app-templates/appsetup-rc.yaml create mode 100644 consortia/argocd-app-templates/appsetup-stable.yaml create mode 100644 consortia/argocd-app-templates/appsetup-upgrade.yaml create mode 100644 consortia/environments/values-beta.yaml create mode 100644 consortia/environments/values-dev.yaml create mode 100644 consortia/environments/values-int.yaml create mode 100644 consortia/environments/values-pen.yaml create mode 100644 consortia/environments/values-rc.yaml create mode 100644 docker/Dockerfile-credential-expiry-app create mode 100644 docker/Dockerfile-credential-issuer-migrations create mode 100644 docker/Dockerfile-credential-issuer-processes-worker create mode 100644 docker/Dockerfile-credential-issuer-service create mode 100644 docker/notice-credential-expiry-app.md create mode 100644 docker/notice-credential-issuer-migrations.md create mode 100644 docker/notice-credential-issuer-processes-worker.md create mode 100644 docker/notice-credential-issuer-service.md rename scripts/download/{org.eclipse.dash.licenses-1.1.1-20240213.065029-71.jar => org.eclipse.dash.licenses-1.1.1.jar} (100%) create mode 100644 src/Directory.Build.props create mode 100644 src/SsiCredentialIssuer.sln create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceExtensions.cs create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceSettings.cs create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/Program.cs create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/Properties/launchSettings.json create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/SsiCredentialIssuer.Expiry.App.csproj create mode 100644 src/credentials/SsiCredentialIssuer.Expiry.App/appsettings.json create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Extensions/MediaTypeIdExtensions.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/IIssuerRepositories.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/IssuerRepositories.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/IssuerRepositoriesStartupServiceExtensions.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/CompanySsiDetailSorting.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/CredentialDetailData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/CredentialExpiryData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/EncryptionTransformationData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/ExternalTypeDetailData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/SsiCertificateTransferData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/TechnicalUserDetails.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Models/UseCaseParticipationTransferData.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/IProcessStepRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/Repositories/ProcessStepRepository.cs create mode 100644 src/database/SsiCredentialIssuer.DbAccess/SsiCredentialIssuer.DbAccess.csproj create mode 100644 src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditCompanySsiDetail20240228.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditDocument20240305.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditEntityV1Attribute.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditInsertEditorV1Attribute.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastChangedV1Attribute.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastEditorV1Attribute.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/DependencyInjection/AuditingDependencyInjection.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditOperationId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditPropertyV1Names.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/AuditExtensions.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/EntityTypeBuilderV1Extension.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Handler/AuditHandlerV1.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Handler/IAuditHandler.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/IAuditEntityV1.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/IAuditableV1.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/IBaseEntity.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Auditing/Identity/IIdentityIdService.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetail.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailAssignedDocument.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailStatus.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiProcessData.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/Document.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/DocumentStatus.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/DocumentType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/ExpiryCheckType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/MediaType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/Process.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/ProcessStep.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepStatus.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/ProcessType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/UseCase.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalTypeDetailVersion.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedExternalType.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedKind.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedUseCase.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeKind.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Entities/VerifyProcessData.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/CompanySsiDetailStatusId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/DocumentStatusId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/DocumentTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/ExpiryCheckTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/MediaTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepStatusId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeKindId.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/IssuerDbContext.cs create mode 100644 src/database/SsiCredentialIssuer.Entities/SsiCredentialIssuer.Entities.csproj create mode 100644 src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.Designer.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Program.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Properties/launchSettings.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/BatchInsertSeeder.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/BatchUpdateSeeder.cs create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json create mode 100644 src/database/SsiCredentialIssuer.Migrations/SsiCredentialIssuer.Migrations.csproj create mode 100644 src/database/SsiCredentialIssuer.Migrations/appsettings.json create mode 100644 src/externalservices/Callback.Service/Callback.Service.csproj create mode 100644 src/externalservices/Callback.Service/DependencyInjection/CallbackSettings.cs create mode 100644 src/externalservices/Callback.Service/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/externalservices/Callback.Service/Models/IssuerResponseData.cs create mode 100644 src/externalservices/Callback.Service/Services/CallbackService.cs create mode 100644 src/externalservices/Callback.Service/Services/ICallbackService.cs create mode 100644 src/externalservices/Portal.Service/DependencyInjection/PortalSettings.cs create mode 100644 src/externalservices/Portal.Service/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/externalservices/Portal.Service/Models/MailData.cs create mode 100644 src/externalservices/Portal.Service/Models/NotificationRequest.cs create mode 100644 src/externalservices/Portal.Service/Models/NotificationTypeId.cs create mode 100644 src/externalservices/Portal.Service/Portal.Service.csproj create mode 100644 src/externalservices/Portal.Service/Services/IPortalService.cs create mode 100644 src/externalservices/Portal.Service/Services/PortalService.cs create mode 100644 src/externalservices/Wallet.Service/BusinessLogic/IWalletBusinessLogic.cs create mode 100644 src/externalservices/Wallet.Service/BusinessLogic/WalletBusinessLogic.cs create mode 100644 src/externalservices/Wallet.Service/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/externalservices/Wallet.Service/DependencyInjection/WalletSettings.cs create mode 100644 src/externalservices/Wallet.Service/Models/CreateCredentialResponse.cs create mode 100644 src/externalservices/Wallet.Service/Models/CredentialData.cs create mode 100644 src/externalservices/Wallet.Service/Models/DeriveCredentialData.cs create mode 100644 src/externalservices/Wallet.Service/Models/EncryptionInformation.cs create mode 100644 src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs create mode 100644 src/externalservices/Wallet.Service/Models/SignCredentialRequest.cs create mode 100644 src/externalservices/Wallet.Service/Schemas/BPNCredential.schema.json create mode 100644 src/externalservices/Wallet.Service/Schemas/FRAMEWORKCredential.schema.json create mode 100644 src/externalservices/Wallet.Service/Schemas/MEMBERSHIPCredential.schema.json create mode 100644 src/externalservices/Wallet.Service/Services/BasicAuthSettings.cs create mode 100644 src/externalservices/Wallet.Service/Services/BasicAuthTokenService.cs create mode 100644 src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs create mode 100644 src/externalservices/Wallet.Service/Services/IWalletService.cs create mode 100644 src/externalservices/Wallet.Service/Services/WalletService.cs create mode 100644 src/externalservices/Wallet.Service/Wallet.Service.csproj create mode 100644 src/issuer/SsiCredentialIssuer.Service/Authentication/CustomClaimTypes.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Authentication/KeycloakClaimsTransformation.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerSettings.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/DependencyInjection/CredentialServiceCollectionExtensions.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Extensions/RouteHandlerBuilderExtensions.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/ClaimTypes.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityDataBuilder.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityService.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityServiceCollectionExtensions.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/IClaimsIdentityDataBuilder.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityData.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityService.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/IdentityIdService.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/MandatoryIdentityClaimHandler.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Identity/PolicyTypes.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/Constants.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/CreateBpnCredentialRequest.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/DidDocument.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationCreationData.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationData.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Program.cs create mode 100644 src/issuer/SsiCredentialIssuer.Service/Properties/launchSettings.json create mode 100644 src/issuer/SsiCredentialIssuer.Service/SsiCredentialIssuer.Service.csproj create mode 100644 src/issuer/SsiCredentialIssuer.Service/appsettings.json create mode 100644 src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj create mode 100644 src/processes/CredentialProcess.Library/CredentialProcessHandler.cs create mode 100644 src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs create mode 100644 src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs create mode 100644 src/processes/CredentialProcess.Worker/CredentialProcess.Worker.csproj create mode 100644 src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs create mode 100644 src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs create mode 100644 src/processes/Processes.Library/ManualProcessStepData.cs create mode 100644 src/processes/Processes.Library/ManualProcessStepDataExtensions.cs create mode 100644 src/processes/Processes.Library/Processes.Library.csproj create mode 100644 src/processes/Processes.Worker.Library/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/processes/Processes.Worker.Library/IProcessExecutor.cs create mode 100644 src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs create mode 100644 src/processes/Processes.Worker.Library/ProcessExecutionService.cs create mode 100644 src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs create mode 100644 src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs create mode 100644 src/processes/Processes.Worker.Library/ProcessExecutor.cs create mode 100644 src/processes/Processes.Worker.Library/ProcessIdentityIdService.cs create mode 100644 src/processes/Processes.Worker.Library/Processes.Worker.Library.csproj create mode 100644 src/processes/Processes.Worker/Processes.Worker.csproj create mode 100644 src/processes/Processes.Worker/Program.cs create mode 100644 src/processes/Processes.Worker/Properties/launchSettings.json create mode 100644 src/processes/Processes.Worker/appsettings.json create mode 100644 src/settings-coverage.xml create mode 100644 tests/Tests.Shared/Extensions/HttpExtensions.cs create mode 100644 tests/Tests.Shared/FakeIdentity.cs create mode 100644 tests/Tests.Shared/FakeIdentityIdService.cs create mode 100644 tests/Tests.Shared/FakeIdentityService.cs create mode 100644 tests/Tests.Shared/HttpMessageHandlerMock.cs create mode 100644 tests/Tests.Shared/NoAuditHandler.cs create mode 100644 tests/Tests.Shared/Tests.Shared.csproj create mode 100644 tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs create mode 100644 tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/GlobalUsings.cs create mode 100644 tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/SsiCredentialIssuer.Expiry.App.Tests.csproj create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/ContentTypeMapperExtensionsTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerDbContextTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerRepositoriesTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/ProcessStepRepositoryTests.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_details.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_process_datas.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/documents.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/process_steps.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/processes.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/Setup/TestDbFixture.cs create mode 100644 tests/database/SsiCredentialIssuer.DbAccess.Tests/SsiCredentialIssuer.DbAccess.Tests.csproj create mode 100644 tests/externalservices/Callback.Service.Tests/Callback.Service.Tests.csproj create mode 100644 tests/externalservices/Callback.Service.Tests/CallbackServiceTests.cs create mode 100644 tests/externalservices/Portal.Service.Tests/Portal.Service.Tests.csproj create mode 100644 tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs create mode 100644 tests/externalservices/Wallet.Service.Tests/BusinessLogic/WalletBusinessLogicTests.cs create mode 100644 tests/externalservices/Wallet.Service.Tests/Services/BasicAuthTokenServiceTests.cs create mode 100644 tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs create mode 100644 tests/externalservices/Wallet.Service.Tests/Wallet.Service.Tests.csproj create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Controllers/IssuerControllerTests.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Identity/MandatoryIdentityClaimHandlerTests.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Setup/AsyncEnumerableStub.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Setup/AsyncEnumeratorStub.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Setup/AsyncQueryProviderStub.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Setup/FakePolicyEvaluator.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Setup/IntegrationTestFactory.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/SsiCredentialIssuer.Service.Tests.csproj create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/Usings.cs create mode 100644 tests/issuer/SsiCredentialIssuer.Service.Tests/appsettings.IntegrationTests.json create mode 100644 tests/processes/CredentialProcess.Library.Tests/CredentialProcess.Library.Tests.csproj create mode 100644 tests/processes/CredentialProcess.Library.Tests/CredentialProcessHandlerTests.cs create mode 100644 tests/processes/CredentialProcess.Worker.Tests/CredentialProcess.Worker.Tests.csproj create mode 100644 tests/processes/CredentialProcess.Worker.Tests/CredentialProcessTypeExecutorTests.cs create mode 100644 tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs create mode 100644 tests/processes/Processes.Library.Tests/Processes.Library.Tests.csproj create mode 100644 tests/processes/Processes.Library.Tests/Usings.cs create mode 100644 tests/processes/Processes.Worker.Library.Tests/MockLogger.cs create mode 100644 tests/processes/Processes.Worker.Library.Tests/ProcessExecutionServiceTests.cs create mode 100644 tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs create mode 100644 tests/processes/Processes.Worker.Library.Tests/Processes.Worker.Library.Tests.csproj create mode 100644 tests/processes/Processes.Worker.Library.Tests/Usings.cs diff --git a/.github/workflows/chart-test.yml b/.github/workflows/chart-test.yml index 4c828b8c..887c0c1c 100644 --- a/.github/workflows/chart-test.yml +++ b/.github/workflows/chart-test.yml @@ -65,19 +65,37 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: docker/Dockerfile-migrations + file: docker/Dockerfile-credential-issuer-migrations push: true - tags: kind-registry:5000/migrations:testing + tags: kind-registry:5000/credential-issuer-migrations:testing - name: Build service image id: build-service-image uses: docker/build-push-action@v3 with: context: . - file: docker/Dockerfile-service + file: docker/Dockerfile-credential-issuer-service push: true - tags: kind-registry:5000/service:testing + tags: kind-registry:5000/credential-issuer-service:testing + - name: Build expiry app + id: build-expiry-app-image + uses: docker/build-push-action@v3 + with: + context: . + file: docker/Dockerfile-credential-expiry-app + push: true + tags: kind-registry:5000/credential-expiry-app:testing + + - name: Build processes worker + id: build-processes-worker-image + uses: docker/build-push-action@v3 + with: + context: . + file: docker/Dockerfile-credential-issuer-processes-worker + push: true + tags: kind-registry:5000/credential-issuer-processes-worker:testing + - name: Set up Helm uses: azure/setup-helm@v3 with: @@ -102,8 +120,8 @@ jobs: - name: Run chart-testing (lint) run: ct lint --validate-maintainers=false --check-version-increment=false --target-branch ${{ github.event.repository.default_branch }} - - name: Run service chart-testing (install) - run: ct install --charts charts/ssi-credential-issuer --config charts/chart-testing-config.yaml --helm-extra-set-args "--set=issuer.image=kind-registry:5000/service:testing --set=issuermigrations.image=kind-registry:5000/migrations:testing" + - name: Run chart-testing (install) + run: ct install --charts charts/ssi-credential-issuer --config charts/chart-testing-config.yaml --helm-extra-set-args "--set issuer.image.name=kind-registry:5000/credential-issuer-service --set issuer.image.tag=testing --set issuermigrations.image.name=kind-registry:5000/credential-issuer-migrations --set issuermigrations.image.tag=testing --set processesworker.image.name=kind-registry:5000/credential-issuer-processes-worker --set processesworker.image.tag=testing --set credentialExpiry.image.name=kind-registry:5000/credential-expiry-app --set credentialExpiry.image.tag=testing" if: github.event_name != 'pull_request' || steps.list-changed.outputs.changed == 'true' # TODO: re-add the step after the first version release @@ -115,5 +133,5 @@ jobs: # helm repo add tractusx-dev https://eclipse-tractusx.github.io/charts/dev # helm install ssi-credential-issuer tractusx-dev/ssi-credential-issuer --version ${{ github.event.inputs.upgrade_from || 'tbd' }} --namespace upgrade --create-namespace # helm dependency update charts/ssi-credential-issuer - # helm upgrade ssi-credential-issuer charts/ssi-credential-issuer --set issuer.image=kind-registry:5000/service:testing --set=issuermigrations.image=kind-registry:5000/migrations:testing --namespace upgrade + # helm upgrade ssi-credential-issuer charts/ssi-credential-issuer --set issuer.image.name=kind-registry:5000/credential-issuer-service --set issuer.image.tag=testing --set issuermigrations.image.name=kind-registry:5000/credential-issuer-migrations --set issuermigrations.image.tag=testing --set processesworker.image.name=kind-registry:5000/credential-issuer-processes-worker --set processesworker.image.tag=testing --set credentialExpiry.image.name=kind-registry:5000/credential-expiry-app --set credentialExpiry.image.tag=testing --namespace upgrade # if: github.event_name != 'pull_request' || steps.list-changed.outputs.changed == 'true' diff --git a/.github/workflows/credential-expiry-app-docker.yml b/.github/workflows/credential-expiry-app-docker.yml new file mode 100644 index 00000000..30104c63 --- /dev/null +++ b/.github/workflows/credential-expiry-app-docker.yml @@ -0,0 +1,91 @@ +############################################################### +# 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 +############################################################### + +name: Build Credential Expiry App Image + +on: + push: + paths: + # service and transitive paths + - 'src/**' + # workflow file + - '.github/workflows/credential-expiry-app-docker.yml' + # dockerfile + - 'docker/Dockerfile-credential-expiry-app' + + branches: + - 'dev' + workflow_dispatch: + +env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "credential-expiry-app" + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=dev + type=raw,value=${{ github.sha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-credential-expiry-app + platforms: linux/amd64, linux/arm64 + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + readme-filepath: "./docker/notice-credential-expiry-app.md" diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 0bd63ce8..44c45d97 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -57,7 +57,7 @@ jobs: run: dotnet list src package --include-transitive --interactive | grep ">" | grep -Pv "\s(Org.Eclipse.TractusX|Microsoft|NuGet|System|runtime|docker|Docker|NETStandard)" | sed -E -e "s/\s+> ([a-zA-Z\.\-]+).+\s([0-9]+\.[0-9]+\.[0-9]+)\s*/nuget\/nuget\/\-\/\1\/\2/g" | awk '!seen[$0]++' > PACKAGES - name: Generate Dependencies file - run: java -jar ./scripts/download/org.eclipse.dash.licenses-1.1.1-20240213.065029-71.jar PACKAGES -project automotive.tractusx -summary DEPENDENCIES || true + run: java -jar ./scripts/download/org.eclipse.dash.licenses-1.1.1.jar PACKAGES -project automotive.tractusx -summary DEPENDENCIES || true - name: Check if dependencies were changed id: dependencies-changed diff --git a/.github/workflows/migrations-docker.yml b/.github/workflows/migrations-docker.yml index a6786d58..3e835715 100644 --- a/.github/workflows/migrations-docker.yml +++ b/.github/workflows/migrations-docker.yml @@ -17,7 +17,7 @@ # SPDX-License-Identifier: Apache-2.0 ############################################################### -name: Migrations +name: Build Migrations Image on: push: @@ -28,7 +28,7 @@ on: # workflow file - '.github/workflows/migrations-docker.yml' # dockerfile - - 'docker/Dockerfile-migrations' + - 'docker/Dockerfile-credential-issuer-migrations' branches: - 'dev' @@ -74,7 +74,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-migrations + file: docker/Dockerfile-credential-issuer-migrations platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/owasp-zap.yml b/.github/workflows/owasp-zap.yml index f1cff0e8..89b956b9 100644 --- a/.github/workflows/owasp-zap.yml +++ b/.github/workflows/owasp-zap.yml @@ -65,19 +65,37 @@ jobs: uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 with: context: . - file: docker/Dockerfile-migrations + file: docker/Dockerfile-credential-issuer-migrations push: true - tags: kind-registry:5000/migrations:testing + tags: kind-registry:5000/credential-issuer-migrations:testing - name: Build service image id: build-service-image uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 with: context: . - file: docker/Dockerfile-service + file: docker/Dockerfile-credential-issuer-service push: true - tags: kind-registry:5000/service:testing + tags: kind-registry:5000/credential-issuer-service:testing + - name: Build Worker image + id: build-worker-image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + context: . + file: docker/Dockerfile-credential-issuer-processes-worker + push: true + tags: kind-registry:5000/credential-issuer-processes-worker:testing + + - name: Build Expiry image + id: build-expiry-image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + context: . + file: docker/Dockerfile-credential-expiry-app + push: true + tags: kind-registry:5000/credential-expiry-app:testing + - name: Add bitnami repo run: | helm repo add bitnami https://charts.bitnami.com/bitnami @@ -89,7 +107,7 @@ jobs: helm dependency build - name: Install the chart on KinD cluster - run: helm install testing -n apps --create-namespace --wait --set issuer.image=kind-registry:5000/service:testing --set=issuermigrations.image=kind-registry:5000/migrations:testing --set=issuer.swaggerEnabled=true charts/ssi-credential-issuer + run: helm install testing -n apps --create-namespace --wait --set issuer.image.name=kind-registry:5000/credential-issuer-service --set issuer.image.tag=testing --set issuermigrations.image.name=kind-registry:5000/credential-issuer-migrations --set issuermigrations.image.tag=testing --set processesworker.image.name=kind-registry:5000/credential-issuer-processes-worker --set processesworker.image.tag=testing --set credentialExpiry.image.name=kind-registry:5000/credential-expiry-app --set credentialExpiry.image.tag=testing --set issuer.swaggerEnabled=true charts/ssi-credential-issuer - name: Configure port forward to app in KinD run: | diff --git a/.github/workflows/processes-worker-docker.yml b/.github/workflows/processes-worker-docker.yml new file mode 100644 index 00000000..128c4648 --- /dev/null +++ b/.github/workflows/processes-worker-docker.yml @@ -0,0 +1,91 @@ +############################################################### +# 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 +############################################################### + +name: Build Processes Worker Image + +on: + push: + paths: + # service and transitive paths + - 'src/**' + # workflow file + - '.github/workflows/process-worker-docker.yml' + # dockerfile + - 'docker/Dockerfile-credential-issuer-process-worker' + + branches: + - 'dev' + workflow_dispatch: + +env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "credential-issuer-process-worker" + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=dev + type=raw,value=${{ github.sha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-credential-issuer-process-worker + platforms: linux/amd64, linux/arm64 + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + readme-filepath: "./docker/notice-credential-issuer-process-worker.md" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f7c7c75..aeb29277 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,8 @@ env: IMAGE_NAMESPACE: "tractusx" IMAGE_NAME_SERVICE: "service" IMAGE_NAME_MIGRATIONS: "migrations" + IMAGE_NAME_WORKER: "credential-issuer-processes-worker" + IMAGE_NAME_EXPIRY: "credential-expiry-app" jobs: service-release: @@ -74,7 +76,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-service + file: docker/Dockerfile-credential-issuer-service platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} @@ -89,7 +91,7 @@ jobs: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_SERVICE }} - readme-filepath: "./docker/notice-service.md" + readme-filepath: "./docker/notice-credential-issuer-service.md" migrations-release: runs-on: ubuntu-latest @@ -134,7 +136,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-migrations + file: docker/Dockerfile-credential-issuer-migrations platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} @@ -149,4 +151,64 @@ jobs: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_MIGRATIONS }} - readme-filepath: "./docker/notice-migrations.md" + readme-filepath: "./docker/notice-credential-issuer-migrations.md" + + expiry-release: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + # Create SemVer or ref tags dependent of trigger event + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_EXPIRY }} + # Automatically prepare image tags; See action docs for more examples. + # semver patter will generate tags like these for example :1 :1.2 :1.2.3 + tags: | + type=ref,event=branch + type=ref,event=pr + type=raw,value=latest + type=semver,pattern={{version}} + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-credential-expiry-app + platforms: linux/amd64, linux/arm64 + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_EXPIRY }} + readme-filepath: "./docker/notice-credential-expiry-app.md" diff --git a/.github/workflows/release_candidate.yml b/.github/workflows/release_candidate.yml index ffe2f55a..e04ddd62 100644 --- a/.github/workflows/release_candidate.yml +++ b/.github/workflows/release_candidate.yml @@ -27,8 +27,10 @@ on: env: IMAGE_NAMESPACE: "tractusx" - IMAGE_NAME_SERVICE: "service" - IMAGE_NAME_MIGRATIONS: "migrations" + IMAGE_NAME_SERVICE: "credential-issuer-service" + IMAGE_NAME_MIGRATIONS: "credential-issuer-migrations" + IMAGE_NAME_WORKER: "credential-issuer-processes-worker" + IMAGE_NAME_EXPIRY: "credential-expiry-app" jobs: service-release: @@ -66,7 +68,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-service + file: docker/Dockerfile-credential-issuer-service platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} @@ -81,7 +83,7 @@ jobs: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_SERVICE }} - readme-filepath: "./docker/notice-ervice.md" + readme-filepath: "./docker/notice-credential-issuer-service.md" migrations-release: runs-on: ubuntu-latest @@ -118,7 +120,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-migrations + file: docker/Dockerfile-credential-issuer-migrations platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} @@ -133,4 +135,108 @@ jobs: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_MIGRATIONS }} - readme-filepath: "./docker/notice-migrations.md" + readme-filepath: "./docker/notice-credential-issuer-migrations.md" + + expiry-release: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_EXPIRY }} + tags: | + type=raw,value=rc + type=raw,value=${{ github.sha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-credential-expiry-app + platforms: linux/amd64, linux/arm64 + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_EXPIRY }} + readme-filepath: "./docker/notice-credential-expiry-app.md" + + worker-release: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_WORKER }} + tags: | + type=raw,value=rc + type=raw,value=${{ github.sha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-credential-issuer-processes-worker + platforms: linux/amd64, linux/arm64 + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME_WORKER }} + readme-filepath: "./docker/notice-credential-issuer-processes-worker.md" diff --git a/.github/workflows/service-docker.yml b/.github/workflows/service-docker.yml index f2bd5e7f..7c73e3bb 100644 --- a/.github/workflows/service-docker.yml +++ b/.github/workflows/service-docker.yml @@ -17,7 +17,7 @@ # SPDX-License-Identifier: Apache-2.0 ############################################################### -name: Service +name: Build Service Image on: push: @@ -27,7 +27,7 @@ on: # workflow file - '.github/workflows/service-docker.yml' # dockerfile - - 'docker/Dockerfile-service' + - 'docker/Dockerfile-credential-issuer-service' branches: - 'dev' @@ -35,7 +35,7 @@ on: env: IMAGE_NAMESPACE: "tractusx" - IMAGE_NAME: "service" + IMAGE_NAME: "credential-issuer-service" jobs: build-and-push-image: @@ -73,7 +73,7 @@ jobs: uses: docker/build-push-action@v4 with: context: . - file: docker/Dockerfile-service + file: docker/Dockerfile-credential-issuer-service platforms: linux/amd64, linux/arm64 pull: true push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/trivy-dev.yml b/.github/workflows/trivy-dev.yml index 9439f8bb..8e2af291 100644 --- a/.github/workflows/trivy-dev.yml +++ b/.github/workflows/trivy-dev.yml @@ -89,7 +89,7 @@ jobs: uses: aquasecurity/trivy-action@0.14.0 with: # Path to Docker image - image-ref: "${{ env.IMAGE_NAMESPACE }}/service:dev" + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-service:dev" format: "sarif" output: "trivy-results3.sarif" vuln-type: "os,library" @@ -121,7 +121,7 @@ jobs: uses: aquasecurity/trivy-action@0.14.0 with: # Path to Docker image - image-ref: "${{ env.IMAGE_NAMESPACE }}/migrations:dev" + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-migrations:dev" format: "sarif" output: "trivy-results9.sarif" vuln-type: "os,library" @@ -132,3 +132,70 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: "trivy-results9.sarif" + + analyze-ssi-credential-expiry-app: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@0.14.0 + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-expiry-app:dev" + format: "sarif" + output: "trivy-results9.sarif" + vuln-type: "os,library" + skip-dirs: "docs/" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results9.sarif" + + analyze-ssi-credential-issuer-processes-worker: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@0.14.0 + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-processes-worker:dev" + format: "sarif" + output: "trivy-results9.sarif" + vuln-type: "os,library" + skip-dirs: "docs/" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results9.sarif" + \ No newline at end of file diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 38353107..0bb744ee 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -90,7 +90,7 @@ jobs: uses: aquasecurity/trivy-action@0.14.0 with: # Path to Docker image - image-ref: "${{ env.IMAGE_NAMESPACE }}/service:latest" + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-service:latest" format: "sarif" output: "trivy-results3.sarif" vuln-type: "os,library" @@ -122,7 +122,7 @@ jobs: uses: aquasecurity/trivy-action@0.14.0 with: # Path to Docker image - image-ref: "${{ env.IMAGE_NAMESPACE }}/migrations:latest" + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-migrations:latest" format: "sarif" output: "trivy-results9.sarif" vuln-type: "os,library" @@ -132,3 +132,68 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: "trivy-results9.sarif" + + analyze-ssi-credential-expiry-app: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@0.14.0 + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-expiry-app:latest" + format: "sarif" + output: "trivy-results9.sarif" + vuln-type: "os,library" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results9.sarif" + + analyze-ssi-credential-issuer-processes-worker: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@0.14.0 + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/credential-issuer-processes-worker:latest" + format: "sarif" + output: "trivy-results9.sarif" + vuln-type: "os,library" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results9.sarif" + \ No newline at end of file diff --git a/.github/workflows/unit.tests-formatting.yml b/.github/workflows/unit.tests-formatting.yml index f22c07ec..69858154 100644 --- a/.github/workflows/unit.tests-formatting.yml +++ b/.github/workflows/unit.tests-formatting.yml @@ -22,8 +22,12 @@ name: Unit-Tests and Formatting on: push: branches: [main, dev] + paths: + - 'src/**' pull_request: types: [opened, synchronize, reopened] + paths: + - 'src/**' workflow_dispatch: jobs: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1915f50a..39c38126 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ The companies involved want to increase the automotive industry's competitiveness, improve efficiency through industry-specific cooperation and accelerate company processes through standardization and access to information and data. A special focus is also on SMEs, whose active participation is of -central importance for the network’s success. That is why Catena-X has been +central importance for the network's success. That is why Catena-X has been conceived from the outset as an open network with solutions ready for SMEs, where these companies will be able to participate quickly and with little IT infrastructure investment. Tractus-X is meant to be the PoC project of the @@ -51,6 +51,10 @@ fulfills the DCO's requirement that you sign-off on your contributions. For more information, please see the Eclipse Committer Handbook: https://www.eclipse.org/projects/handbook/#resources-commit +## How To Contribute + +For more practical information, please refer to [Contribution details](/docs/technical-documentation/dev-process/How%20to%20contribute.md). + ## Contact Contact the project developers via the project's "dev" list. diff --git a/DEPENDENCIES b/DEPENDENCIES index e69de29b..fcae7a8a 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -0,0 +1,57 @@ +nuget/nuget/-/AutoFixture.AutoFakeItEasy/4.18.0, MIT, approved, #10064 +nuget/nuget/-/AutoFixture.Xunit/4.18.0, MIT, approved, #10082 +nuget/nuget/-/AutoFixture/4.18.0, MIT, approved, #10057 +nuget/nuget/-/Castle.Core/4.3.1, Apache-2.0, approved, clearlydefined +nuget/nuget/-/EFCore.NamingConventions/7.0.2, Apache-2.0, approved, #10067 +nuget/nuget/-/FakeItEasy/7.4.0, MIT, approved, #10080 +nuget/nuget/-/Fare/2.1.1, MIT, approved, clearlydefined +nuget/nuget/-/FluentAssertions/6.11.0, Apache-2.0 AND MIT, approved, #10061 +nuget/nuget/-/Flurl.Http.Signed/3.2.4, MIT, approved, #3503 +nuget/nuget/-/Flurl.Signed/3.0.6, MIT, approved, #3501 +nuget/nuget/-/Humanizer.Core/2.14.1, MIT, approved, #10060 +nuget/nuget/-/Json.More.Net/2.0.0, MIT, approved, clearlydefined +nuget/nuget/-/JsonPointer.Net/4.0.0, MIT, approved, clearlydefined +nuget/nuget/-/JsonSchema.Net/6.0.3, MIT AND OFL-1.1 AND CC-BY-SA-4.0, approved, #13370 +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 +nuget/nuget/-/Npgsql/7.0.6, PostgreSQL, approved, #10062 +nuget/nuget/-/PasswordGenerator/2.1.0, MIT, approved, #3192 +nuget/nuget/-/Portable.BouncyCastle/1.9.0, MIT, approved, clearlydefined +nuget/nuget/-/SSH.NET/2020.0.2, MIT AND ISC AND LicenseRef-Public-domain AND (MIT AND MS-PL), approved, #10073 +nuget/nuget/-/Serilog.AspNetCore/7.0.0, Apache-2.0 AND MIT, approved, #10084 +nuget/nuget/-/Serilog.Enrichers.CorrelationId/3.0.1, MIT, approved, clearlydefined +nuget/nuget/-/Serilog.Enrichers.Environment/2.2.0, Apache-2.0, approved, clearlydefined +nuget/nuget/-/Serilog.Enrichers.Process/2.0.2, Apache-2.0, approved, clearlydefined +nuget/nuget/-/Serilog.Enrichers.Sensitive/1.7.3, MIT, approved, clearlydefined +nuget/nuget/-/Serilog.Enrichers.Thread/3.1.0, Apache-2.0, approved, clearlydefined +nuget/nuget/-/Serilog.Extensions.Hosting/7.0.0, Apache-2.0, approved, #10078 +nuget/nuget/-/Serilog.Extensions.Logging/7.0.0, Apache-2.0, approved, #10070 +nuget/nuget/-/Serilog.Formatting.Compact/1.1.0, Apache-2.0, approved, #11115 +nuget/nuget/-/Serilog.Settings.Configuration/7.0.0, Apache-2.0, approved, #10069 +nuget/nuget/-/Serilog.Sinks.Console/4.1.0, Apache-2.0, approved, #8434 +nuget/nuget/-/Serilog.Sinks.Debug/2.0.0, Apache-2.0, approved, clearlydefined +nuget/nuget/-/Serilog.Sinks.File/5.0.0, Apache-2.0, approved, #11116 +nuget/nuget/-/Serilog/3.0.1, Apache-2.0, approved, #10063 +nuget/nuget/-/SharpZipLib/1.4.2, MIT AND GFDL-1.3-or-later AND (Apache-2.0 AND MIT) AND WTFPL AND bzip2-1.0.6 AND LicenseRef-Permissive-license-with-conditions AND LicenseRef-Permission-Notice, approved, #10058 +nuget/nuget/-/SshNet.Security.Cryptography/1.3.0, MIT, approved, clearlydefined +nuget/nuget/-/Swashbuckle.AspNetCore.Swagger/6.5.0, MIT AND Apache-2.0, approved, #7160 +nuget/nuget/-/Swashbuckle.AspNetCore.SwaggerGen/6.5.0, MIT AND Apache-2.0, approved, #7156 +nuget/nuget/-/Swashbuckle.AspNetCore.SwaggerUI/6.5.0, MIT AND Apache-2.0, approved, #7158 +nuget/nuget/-/Swashbuckle.AspNetCore/6.5.0, MIT AND Apache-2.0, approved, #7159 +nuget/nuget/-/Testcontainers.PostgreSql/3.4.0, MIT, approved, #10056 +nuget/nuget/-/Testcontainers/3.4.0, MIT, approved, #10083 +nuget/nuget/-/Xunit.Extensions.AssemblyFixture/2.4.1, MIT, approved, #3502 +nuget/nuget/-/coverlet.collector/6.0.0, MIT, approved, #10075 +nuget/nuget/-/xunit.abstractions/2.0.3, Apache-2.0, approved, clearlydefined +nuget/nuget/-/xunit.analyzers/1.2.0, Apache-2.0 AND MIT, approved, #10068 +nuget/nuget/-/xunit.assert/2.5.0, Apache-2.0 AND MIT, approved, #10071 +nuget/nuget/-/xunit.core/2.5.0, Apache-2.0 AND MIT, approved, #10059 +nuget/nuget/-/xunit.extensibility.core/2.5.0, Apache-2.0 AND MIT, approved, #10077 +nuget/nuget/-/xunit.extensibility.execution/2.5.0, Apache-2.0 AND MIT, approved, #10074 +nuget/nuget/-/xunit.runner.visualstudio/2.5.0, Apache-2.0 AND MIT, approved, #10065 +nuget/nuget/-/xunit/2.5.0, Apache-2.0 AND MIT, approved, #10072 diff --git a/FILEHEADER.md b/FILEHEADER.md index 79d8879b..7b905c2c 100644 --- a/FILEHEADER.md +++ b/FILEHEADER.md @@ -12,4 +12,4 @@ Every time you create a new file or edit a file that you created and doesn't yet Currently the following templates are available: * cx_header_default -* cx_header_with_# \ No newline at end of file +* cx_header_with_# diff --git a/README.md b/README.md index 2c922dea..bfd1b8ad 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This repository contains the backend code for the SSI Credential Issuer written ## How to build and run -Install [the .NET 8.0 SDK](https://www.microsoft.com/net/download). +Install the [.NET 7.0 SDK](https://www.microsoft.com/net/download). Run the following command from the CLI: @@ -19,7 +19,18 @@ Run the following command from the CLI in the directory of the service you want dotnet run ``` +## Notice for Docker image + +This application provides container images for demonstration purposes. + +See Docker notice files for more information: + +- [credential-issuer-service](./docker//notice-credential-issuer-service.md) +- [credential-issuer-processes-worker](./docker/notice-credential-issuer-processes-worker.md) +- [notice-credential-expiry-app](./docker/notice-credential-expiry-app.md) +- [credential-issuer-migrations](./docker/notice-credential-issuer-migrations.md) + ## License Distributed under the Apache 2.0 License. -See [LICENSE](./LICENSE) for more information \ No newline at end of file +See [LICENSE](./LICENSE) for more information diff --git a/charts/chart-testing-config.yaml b/charts/chart-testing-config.yaml new file mode 100644 index 00000000..f70d8a6a --- /dev/null +++ b/charts/chart-testing-config.yaml @@ -0,0 +1,23 @@ +############################################################### +# 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 +############################################################### + +validate-maintainers: false +chart-repos: + - bitnami=https://charts.bitnami.com/bitnami + - tractusx-dev=https://eclipse-tractusx.github.io/charts/dev diff --git a/charts/ssi-credential-issuer/.helmignore b/charts/ssi-credential-issuer/.helmignore new file mode 100644 index 00000000..0bffc69f --- /dev/null +++ b/charts/ssi-credential-issuer/.helmignore @@ -0,0 +1,27 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ + +# Custom dirs and files +argocd/ +*.gotmpl diff --git a/charts/ssi-credential-issuer/Chart.yaml b/charts/ssi-credential-issuer/Chart.yaml new file mode 100644 index 00000000..678f6b2d --- /dev/null +++ b/charts/ssi-credential-issuer/Chart.yaml @@ -0,0 +1,31 @@ +############################################################### +# 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 +############################################################### + +apiVersion: v2 +name: ssi-credential-issuer +type: application +version: 1.0.0-rc.1 +appVersion: 1.0.0-rc.1 +description: Helm chart for Catena-X SSI Credential Issuer +home: https://github.com/eclipse-tractusx/ssi-credential-issuer +dependencies: + - condition: postgresql.enabled + name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.12.x diff --git a/charts/ssi-credential-issuer/LICENSE b/charts/ssi-credential-issuer/LICENSE new file mode 100644 index 00000000..f49a4e16 --- /dev/null +++ b/charts/ssi-credential-issuer/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://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. \ No newline at end of file diff --git a/charts/ssi-credential-issuer/README.md b/charts/ssi-credential-issuer/README.md new file mode 100644 index 00000000..59756a05 --- /dev/null +++ b/charts/ssi-credential-issuer/README.md @@ -0,0 +1,162 @@ +# Helm chart for Catena-X SSI Credential Issuer + +This helm chart installs the Catena-X SSI Credential Issuer application. + +For further information please refer to [Technical Documentation](./docs/technical-documentation). + +The referenced container images are for demonstration purposes only. + +## Installation + +To install the chart with the release name `ssi-credential-issuer`: + +```shell +$ helm repo add tractusx-dev https://eclipse-tractusx.github.io/charts/dev +$ helm install ssi-credential-issuer tractusx-dev/ssi-credential-issuer +``` + +To install the helm chart into your cluster with your values: + +```shell +$ helm install -f your-values.yaml ssi-credential-issuer tractusx-dev/ssi-credential-issuer +``` + +To use the helm chart as a dependency: + +```yaml +dependencies: + - name: ssi-credential-issuer + repository: https://eclipse-tractusx.github.io/charts/dev + version: 1.0.0-rc.1 +``` + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| https://charts.bitnami.com/bitnami | postgresql | 12.12.x | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| issuer.image.name | string | `"docker.io/tractusx/credential-issuer-service"` | | +| issuer.image.tag | string | `""` | | +| issuer.imagePullPolicy | string | `"IfNotPresent"` | | +| issuer.resources | object | `{"limits":{"cpu":"45m","memory":"400M"},"requests":{"cpu":"15m","memory":"300M"}}` | We recommend to review the default resource limits as this should a conscious choice. | +| issuer.logging.businessLogic | string | `"Information"` | | +| issuer.logging.default | string | `"Information"` | | +| issuer.healthChecks.startup.path | string | `"/health/startup"` | | +| issuer.healthChecks.startup.tags[0].name | string | `"HEALTHCHECKS__0__TAGS__1"` | | +| issuer.healthChecks.startup.tags[0].value | string | `"issuerdb"` | | +| issuer.healthChecks.liveness.path | string | `"/healthz"` | | +| issuer.healthChecks.readyness.path | string | `"/ready"` | | +| issuer.swaggerEnabled | bool | `false` | | +| issuer.portal.scope | string | `"openid"` | | +| issuer.portal.grantType | string | `"client_credentials"` | | +| issuer.portal.clientId | string | `"portal-client-id"` | Provide portal client-id from CX IAM centralidp. | +| issuer.portal.clientSecret | string | `""` | Client-secret for portal client-id. Secret-key 'portal-client-secret'. | +| issuer.credential.issuerDid | string | `""` | | +| issuer.credential.encryptionConfigIndex | int | `0` | | +| issuer.credential.encryptionConfigs.index0.index | int | `0` | | +| issuer.credential.encryptionConfigs.index0.cipherMode | string | `"CBC"` | | +| issuer.credential.encryptionConfigs.index0.paddingMode | string | `"PKCS7"` | | +| issuer.credential.encryptionConfigs.index0.encryptionKey | string | `""` | EncryptionKey for wallet. Secret-key 'credential-encryption-key0'. Expected format is 256 bit (64 digits) hex. | +| issuermigrations.name | string | `"migrations"` | | +| issuermigrations.image.name | string | `"docker.io/tractusx/credential-issuer-migrations"` | | +| issuermigrations.image.tag | string | `""` | | +| issuermigrations.imagePullPolicy | string | `"IfNotPresent"` | | +| issuermigrations.resources | object | `{"limits":{"cpu":"45m","memory":"105M"},"requests":{"cpu":"15m","memory":"105M"}}` | We recommend to review the default resource limits as this should a conscious choice. | +| issuermigrations.seeding.testDataEnvironments | string | `""` | | +| issuermigrations.seeding.testDataPaths | string | `"Seeder/Data"` | | +| issuermigrations.logging.default | string | `"Information"` | | +| processesworker.name | string | `"processesworker"` | | +| processesworker.image.name | string | `"docker.io/tractusx/credential-issuer-processes-worker"` | | +| processesworker.image.tag | string | `""` | | +| processesworker.imagePullPolicy | string | `"IfNotPresent"` | | +| processesworker.resources | object | `{"limits":{"cpu":"45m","memory":"105M"},"requests":{"cpu":"15m","memory":"105M"}}` | We recommend to review the default resource limits as this should a conscious choice. | +| processesworker.logging.default | string | `"Information"` | | +| processesworker.portal.scope | string | `"openid"` | | +| processesworker.portal.grantType | string | `"client_credentials"` | | +| processesworker.portal.clientId | string | `"portal-client-id"` | Provide portal client-id from CX IAM centralidp. | +| processesworker.portal.clientSecret | string | `""` | Client-secret for portal client-id. Secret-key 'portal-client-secret'. | +| processesworker.processIdentity.identityId | string | `"d21d2e8a-fe35-483c-b2b8-4100ed7f0953"` | | +| processesworker.wallet.scope | string | `"openid"` | | +| processesworker.wallet.grantType | string | `"client_credentials"` | | +| processesworker.wallet.clientId | string | `"wallet-client-id"` | Provide wallet client-id from CX IAM centralidp. | +| processesworker.wallet.clientSecret | string | `""` | Client-secret for wallet client-id. Secret-key 'wallet-client-secret'. | +| processesworker.wallet.encryptionConfigIndex | int | `0` | | +| processesworker.wallet.encryptionConfigs.index0.index | int | `0` | | +| processesworker.wallet.encryptionConfigs.index0.cipherMode | string | `"CBC"` | | +| processesworker.wallet.encryptionConfigs.index0.paddingMode | string | `"PKCS7"` | | +| processesworker.wallet.encryptionConfigs.index0.encryptionKey | string | `""` | EncryptionKey for wallet. Secret-key 'process-wallet-encryption-key0'. Expected format is 256 bit (64 digits) hex. | +| credentialExpiry.name | string | `"expiry"` | | +| credentialExpiry.image.name | string | `"docker.io/tractusx/credential-expiry-app"` | | +| credentialExpiry.image.tag | string | `""` | | +| credentialExpiry.imagePullPolicy | string | `"IfNotPresent"` | | +| credentialExpiry.resources | object | `{"limits":{"cpu":"45m","memory":"105M"},"requests":{"cpu":"15m","memory":"105M"}}` | We recommend to review the default resource limits as this should a conscious choice. | +| credentialExpiry.processIdentity.identityId | string | `"d21d2e8a-fe35-483c-b2b8-4100ed7f0953"` | | +| credentialExpiry.logging.default | string | `"Information"` | | +| credentialExpiry.expiry.expiredVcsToDeleteInMonth | int | `12` | | +| credentialExpiry.expiry.inactiveVcsToDeleteInWeeks | int | `12` | | +| existingSecret | string | `""` | Secret containing the client-secrets for the connection to portal and wallet as well as encryptionKeys for issuer.credential and processesworker.wallet | +| dotnetEnvironment | string | `"Production"` | | +| centralidp.address | string | `"https://centralidp.example.org"` | Provide centralidp base address (CX IAM), without trailing '/auth'. | +| centralidp.authRealm | string | `"CX-Central"` | | +| centralidp.jwtBearerOptions.requireHttpsMetadata | string | `"true"` | | +| centralidp.jwtBearerOptions.metadataPath | string | `"/auth/realms/CX-Central/.well-known/openid-configuration"` | | +| centralidp.jwtBearerOptions.tokenValidationParameters.validIssuerPath | string | `"/auth/realms/CX-Central"` | | +| centralidp.jwtBearerOptions.tokenValidationParameters.validAudience | string | `"ClXX-CX-SSI"` | TODO: Add Client | +| centralidp.jwtBearerOptions.refreshInterval | string | `"00:00:30"` | | +| centralidp.tokenPath | string | `"/auth/realms/CX-Central/protocol/openid-connect/token"` | | +| centralidp.useAuthTrail | bool | `true` | Flag if the api should be used with an leading /auth path | +| ingress.enabled | bool | `false` | SSI Credential Issuer ingress parameters, enable ingress record generation for ssi-credential-issuer. | +| ingress.tls[0] | object | `{"hosts":[""],"secretName":""}` | Provide tls secret. | +| ingress.tls[0].hosts | list | `[""]` | Provide host for tls secret. | +| ingress.hosts[0] | object | `{"host":"","paths":[{"backend":{"port":8080},"path":"/api/issuer","pathType":"Prefix"}]}` | Provide default path for the ingress record. | +| dbConnection.schema | string | `"issuer"` | | +| dbConnection.sslMode | string | `"Disable"` | | +| postgresql.enabled | bool | `true` | PostgreSQL chart configuration; default configurations: host: "issuer-postgresql-primary", port: 5432; Switch to enable or disable the PostgreSQL helm chart. | +| postgresql.image | object | `{"tag":"15-debian-12"}` | Setting image tag to major to get latest minor updates | +| postgresql.commonLabels."app.kubernetes.io/version" | string | `"15"` | | +| postgresql.auth.username | string | `"issuer"` | Non-root username. | +| postgresql.auth.database | string | `"issuer"` | Database name. | +| postgresql.auth.existingSecret | string | `"{{ .Release.Name }}-issuer-postgres"` | Secret containing the passwords for root usernames postgres and non-root username issuer. Should not be changed without changing the "issuer-postgresSecretName" template as well. | +| postgresql.auth.postgrespassword | string | `""` | Password for the root username 'postgres'. Secret-key 'postgres-password'. | +| postgresql.auth.password | string | `""` | Password for the non-root username 'issuer'. Secret-key 'password'. | +| postgresql.auth.replicationPassword | string | `""` | Password for the non-root username 'repl_user'. Secret-key 'replication-password'. | +| postgresql.architecture | string | `"replication"` | | +| postgresql.audit.pgAuditLog | string | `"write, ddl"` | | +| postgresql.audit.logLinePrefix | string | `"%m %u %d "` | | +| postgresql.primary.extendedConfiguration | string | `""` | Extended PostgreSQL Primary configuration (increase of max_connections recommended - default is 100) | +| postgresql.primary.initdb.scriptsConfigMap | string | `"{{ .Release.Name }}-issuer-cm-postgres"` | | +| postgresql.readReplicas.extendedConfiguration | string | `""` | Extended PostgreSQL read only replicas configuration (increase of max_connections recommended - default is 100) | +| externalDatabase.host | string | `"issuer-postgres-ext"` | External PostgreSQL configuration IMPORTANT: non-root db user needs to be created beforehand on external database. And the init script (02-init-db.sql) available in templates/configmap-postgres-init.yaml needs to be executed beforehand. Database host ('-primary' is added as postfix). | +| externalDatabase.port | int | `5432` | Database port number. | +| externalDatabase.user | string | `"issuer"` | Non-root username for issuer. | +| externalDatabase.database | string | `"issuer"` | Database name. | +| externalDatabase.password | string | `""` | Password for the non-root username (default 'issuer'). Secret-key 'password'. | +| externalDatabase.existingSecret | string | `"issuer-external-db"` | Secret containing the password non-root username, (default 'issuer'). | +| externalDatabase.existingSecretPasswordKey | string | `"password"` | Name of an existing secret key containing the database credentials. | +| portContainer | int | `8080` | | +| portService | int | `8080` | | +| replicaCount | int | `3` | | +| nodeSelector | object | `{}` | Node labels for pod assignment | +| tolerations | list | `[]` | Tolerations for pod assignment | +| affinity.podAntiAffinity | object | `{"preferredDuringSchedulingIgnoredDuringExecution":[{"podAffinityTerm":{"labelSelector":{"matchExpressions":[{"key":"app.kubernetes.io/name","operator":"DoesNotExist"}]},"topologyKey":"kubernetes.io/hostname"},"weight":100}]}` | Following Catena-X Helm Best Practices, [reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity). | +| updateStrategy.type | string | `"RollingUpdate"` | Update strategy type, rolling update configuration parameters, [reference](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies). | +| updateStrategy.rollingUpdate.maxSurge | int | `1` | | +| updateStrategy.rollingUpdate.maxUnavailable | int | `0` | | +| startupProbe | object | `{"failureThreshold":30,"initialDelaySeconds":10,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1}` | Following Catena-X Helm Best Practices, [reference](https://github.com/helm/charts/blob/master/stable/nginx-ingress/values.yaml#L210). | +| livenessProbe.failureThreshold | int | `3` | | +| livenessProbe.initialDelaySeconds | int | `10` | | +| livenessProbe.periodSeconds | int | `10` | | +| livenessProbe.successThreshold | int | `1` | | +| livenessProbe.timeoutSeconds | int | `10` | | +| readinessProbe.failureThreshold | int | `3` | | +| readinessProbe.initialDelaySeconds | int | `10` | | +| readinessProbe.periodSeconds | int | `10` | | +| readinessProbe.successThreshold | int | `1` | | +| readinessProbe.timeoutSeconds | int | `1` | | + +Autogenerated with [helm docs](https://github.com/norwoodj/helm-docs) diff --git a/charts/ssi-credential-issuer/README.md.gotmpl b/charts/ssi-credential-issuer/README.md.gotmpl new file mode 100644 index 00000000..e352f42b --- /dev/null +++ b/charts/ssi-credential-issuer/README.md.gotmpl @@ -0,0 +1,37 @@ +# {{ template "chart.description" . }} + +This helm chart installs the Catena-X SSI Credential Issuer application. + +For further information please refer to [Technical Documentation](./docs/technical-documentation). + +The referenced container images are for demonstration purposes only. + +## Installation + +To install the chart with the release name `{{ template "chart.name" . }}`: + +```shell +$ helm repo add tractusx-dev https://eclipse-tractusx.github.io/charts/dev +$ helm install {{ template "chart.name" . }} tractusx-dev/{{ template "chart.name" . }} +``` + +To install the helm chart into your cluster with your values: + +```shell +$ helm install -f your-values.yaml {{ template "chart.name" . }} tractusx-dev/{{ template "chart.name" . }} +``` + +To use the helm chart as a dependency: + +```yaml +dependencies: + - name: {{ template "chart.name" . }} + repository: https://eclipse-tractusx.github.io/charts/dev + version: {{ template "chart.version" . }} +``` + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +Autogenerated with [helm docs](https://github.com/norwoodj/helm-docs) diff --git a/charts/ssi-credential-issuer/templates/_helpers.tpl b/charts/ssi-credential-issuer/templates/_helpers.tpl new file mode 100644 index 00000000..82f4dc42 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/_helpers.tpl @@ -0,0 +1,109 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "issuer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "issuer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "issuer.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Determine secret name. +*/}} +{{- define "issuer.secretName" -}} +{{- if .Values.existingSecret -}} +{{- .Values.existingSecret }} +{{- else -}} +{{- include "issuer.fullname" . -}} +{{- end -}} +{{- end -}} + +{{/* +Define secret name of postgres dependency. +*/}} +{{- define "issuer.postgresSecretName" -}} +{{- printf "%s-%s" .Release.Name "issuer-postgres" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "issuer.labels" -}} +helm.sh/chart: {{ include "issuer.chart" . }} +{{ include "issuer.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "issuer.selectorLabels" -}} +app.kubernetes.io/name: {{ include "issuer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "issuer.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "issuer.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Determine database hostname for subchart +*/}} + +{{- define "issuer.postgresql.primary.fullname" -}} +{{- if eq .Values.postgresql.architecture "replication" }} +{{- printf "%s-primary" (include "issuer.chart.name.postgresql.dependency" .) | trunc 63 | trimSuffix "-" -}} +{{- else -}} + {{- include "issuer.chart.name.postgresql.dependency" . -}} +{{- end -}} +{{- end -}} + +{{- define "issuer.postgresql.readReplica.fullname" -}} +{{- printf "%s-read" (include "issuer.chart.name.postgresql.dependency" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "issuer.chart.name.postgresql.dependency" -}} +{{- if .Values.postgresql.fullnameOverride -}} +{{- .Values.postgresql.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "postgresql" .Values.postgresql.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/ssi-credential-issuer/templates/configmap-postgres-init.yaml b/charts/ssi-credential-issuer/templates/configmap-postgres-init.yaml new file mode 100644 index 00000000..22b8db28 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/configmap-postgres-init.yaml @@ -0,0 +1,35 @@ +{{- /* +* 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 +*/}} + +{{- if .Values.postgresql.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-issuer-cm-postgres + namespace: {{ .Release.Namespace }} +data: + 02-init-db.sql: | + CREATE SCHEMA issuer; + ALTER SCHEMA issuer OWNER TO issuer; + CREATE TABLE public.__efmigrations_history_issuer ( + migration_id character varying(150) NOT NULL, + product_version character varying(32) NOT NULL + ); + ALTER TABLE public.__efmigrations_history_issuer OWNER TO issuer; +{{- end -}} diff --git a/charts/ssi-credential-issuer/templates/cronjob-expiry-app.yaml b/charts/ssi-credential-issuer/templates/cronjob-expiry-app.yaml new file mode 100644 index 00000000..ceef60a9 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/cronjob-expiry-app.yaml @@ -0,0 +1,80 @@ +############################################################### +# 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 +############################################################### + +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.credentialExpiry.name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "issuer.labels" . | nindent 4 }} +spec: + schedule: "0 0 * * *" + concurrencyPolicy: Forbid + jobTemplate: + metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.credentialExpiry.name }} + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: {{ include "issuer.fullname" . }}-{{ .Values.credentialExpiry.name }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + image: "{{ .Values.credentialExpiry.image.name }}:{{ .Values.credentialExpiry.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.credentialExpiry.imagePullPolicy }}" + env: + - name: DOTNET_ENVIRONMENT + value: "{{ .Values.dotnetEnvironment }}" + {{- if .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.postgresSecretName" . }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ template "issuer.postgresql.primary.fullname" . }};Database={{ .Values.postgresql.auth.database }};Port={{ .Values.postgresql.auth.port }};User Id={{ .Values.postgresql.auth.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + {{- if not .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ .Values.externalDatabase.secret }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ .Values.externalDatabase.host }};Database={{ .Values.externalDatabase.database }};Port={{ .Values.externalDatabase.port }};User Id={{ .Values.externalDatabase.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + - name: "EXPIRY__EXPIREDVCSTODELETEINMONTH" + value: "{{ .Values.credentialExpiry.expiry.expiredVcsToDeleteInMonth }}" + - name: "EXPIRY__INACTIVEVCSTODELETEINWEEKS" + value: "{{ .Values.credentialExpiry.expiry.inactiveVcsToDeleteInWeeks }}" + - name: "PROCESSES__IDENTITYID" + value: "{{ .Values.credentialExpiry.processIdentity.identityId }}" + ports: + - name: http + containerPort: {{ .Values.portContainer }} + protocol: TCP + resources: + {{- toYaml .Values.credentialExpiry.resources | nindent 14 }} diff --git a/charts/ssi-credential-issuer/templates/cronjob-issuer-processes.yaml b/charts/ssi-credential-issuer/templates/cronjob-issuer-processes.yaml new file mode 100644 index 00000000..a9b72ee0 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/cronjob-issuer-processes.yaml @@ -0,0 +1,125 @@ +############################################################### +# 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 +############################################################### + +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.processesworker.name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "issuer.labels" . | nindent 4 }} +spec: + schedule: "*/5 * * * *" + concurrencyPolicy: Forbid + jobTemplate: + metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.processesworker.name }} + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: {{ include "issuer.fullname" . }}-{{ .Values.processesworker.name }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + image: "{{ .Values.processesworker.image.name }}:{{ .Values.processesworker.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.processesworker.imagePullPolicy }}" + env: + - name: DOTNET_ENVIRONMENT + value: "{{ .Values.dotnetEnvironment }}" + {{- if .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.postgresSecretName" . }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ template "issuer.postgresql.primary.fullname" . }};Database={{ .Values.postgresql.auth.database }};Port={{ .Values.postgresql.auth.port }};User Id={{ .Values.postgresql.auth.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + {{- if not .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ .Values.externalDatabase.secret }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ .Values.externalDatabase.host }};Database={{ .Values.externalDatabase.database }};Port={{ .Values.externalDatabase.port }};User Id={{ .Values.externalDatabase.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + - name: "PORTAL__CLIENTSECRET" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.secretName" . }}" + key: "portal-client-secret" + - name: "PORTAL__GRANTTYPE" + value: "{{ .Values.processesworker.portal.grantType }}" + - name: "PORTAL__TOKENADDRESS" + value: "{{ .Values.centralidp.address }}{{ .Values.centralidp.tokenPath }}" + - name: "PORTAL__PASSWORD" + value: "empty" + - name: "PORTAL__SCOPE" + value: "{{ .Values.processesworker.portal.scope }}" + - name: "PORTAL__USERNAME" + value: "empty" + - name: "WALLET__BASEADDRESS" + value: "{{ .Values.walletAddress }}" + - name: "WALLET__CLIENTID" + value: "{{ .Values.processesworker.wallet.clientId }}" + - name: "WALLET__CLIENTSECRET" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.secretName" . }}" + key: "wallet-client-secret" + - name: "WALLET__GRANTTYPE" + value: "{{ .Values.processesworker.wallet.grantType }}" + - name: "WALLET__TOKENADDRESS" + value: "{{ .Values.walletTokenAddress }}" + - name: "WALLET__PASSWORD" + value: "empty" + - name: "WALLET__SCOPE" + value: "{{ .Values.processesworker.wallet.scope }}" + - name: "WALLET__USERNAME" + value: "empty" + - name: "WALLET__ENCRYPTIONCONFIG__ENCRYPTIONCONFIGINDEX" + value: "{{ .Values.processesworker.wallet.encryptionConfigIndex }}" + - name: "WALLET__ENCRYPTIONCONFIGS__0__INDEX" + value: "{{ .Values.processesworker.wallet.encryptionConfigs.index0.index}}" + - name: "WALLET__ENCRYPTIONCONFIGS__0__CIPHERMODE" + value: "{{ .Values.processesworker.wallet.encryptionConfigs.index0.cipherMode}}" + - name: "WALLET__ENCRYPTIONCONFIGS__0__PADDINGMODE" + value: "{{ .Values.processesworker.wallet.encryptionConfigs.index0.paddingMode}}" + - name: "WALLET__ENCRYPTIONCONFIGS__0__ENCRYPTIONKEY" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.secretName" . }}" + key: "process-wallet-encryption-key0" + - name: "SERILOG__MINIMUMLEVEL__Default" + value: "{{ .Values.processesworker.logging.default }}" + - name: "PROCESSES__IDENTITYID" + value: "{{ .Values.processesworker.processIdentity.identityId }}" + ports: + - name: http + containerPort: {{ .Values.portContainer }} + protocol: TCP + resources: + {{- toYaml .Values.processesworker.resources | nindent 14 }} diff --git a/charts/ssi-credential-issuer/templates/deployment-issuer-service.yaml b/charts/ssi-credential-issuer/templates/deployment-issuer-service.yaml new file mode 100644 index 00000000..6409468f --- /dev/null +++ b/charts/ssi-credential-issuer/templates/deployment-issuer-service.yaml @@ -0,0 +1,177 @@ +############################################################### +# 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 +############################################################### + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "issuer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "issuer.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + {{- toYaml .Values.updateStrategy | nindent 4 }} + selector: + matchLabels: + {{- include "issuer.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "issuer.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: {{ include "issuer.fullname" . }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + image: "{{ .Values.issuer.image.name }}:{{ .Values.issuer.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.issuer.imagePullPolicy }}" + env: + - name: DOTNET_ENVIRONMENT + value: "{{ .Values.dotnetEnvironment }}" + {{- if .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.postgresSecretName" . }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ template "issuer.postgresql.primary.fullname" . }};Database={{ .Values.postgresql.auth.database }};Port={{ .Values.postgresql.auth.port }};User Id={{ .Values.postgresql.auth.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + {{- if not .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ .Values.externalDatabase.existingSecret }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ .Values.externalDatabase.host }};Database={{ .Values.externalDatabase.database }};Port={{ .Values.externalDatabase.port }};User Id={{ .Values.externalDatabase.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + - name: "HEALTHCHECKS__0__PATH" + value: "{{ .Values.issuer.healthChecks.startup.path}}" + {{- if .Values.issuer.healthChecks.startup.tags }} + {{- toYaml .Values.issuer.healthChecks.startup.tags | nindent 8 }} + {{- end }} + - name: "HEALTHCHECKS__1__PATH" + value: "{{ .Values.issuer.healthChecks.readyness.path}}" + - name: "HEALTHCHECKS__2__PATH" + value: "{{ .Values.issuer.healthChecks.liveness.path}}" + - name: "JWTBEAREROPTIONS__METADATAADDRESS" + value: "{{ .Values.centralidp.address }}{{ .Values.centralidp.jwtBearerOptions.metadataPath }}" + - name: "JWTBEAREROPTIONS__REQUIREHTTPSMETADATA" + value: "{{ .Values.centralidp.jwtBearerOptions.requireHttpsMetadata }}" + - name: "JWTBEAREROPTIONS__TOKENVALIDATIONPARAMETERS__VALIDAUDIENCE" + value: "{{ .Values.centralidp.jwtBearerOptions.tokenValidationParameters.validAudience }}" + - name: "JWTBEAREROPTIONS__TOKENVALIDATIONPARAMETERS__VALIDISSUER" + value: "{{ .Values.centralidp.address }}{{ .Values.centralidp.jwtBearerOptions.tokenValidationParameters.validIssuerPath }}" + - name: "JWTBEAREROPTIONS__REFRESHINTERVAL" + value: "{{ .Values.centralidp.jwtBearerOptions.refreshInterval }}" + - name: "SERILOG__MINIMUMLEVEL__Default" + value: "{{ .Values.issuer.logging.default }}" + - name: "SERILOG__MINIMUMLEVEL__OVERRIDE__Org.Eclipse.TractusX.SsiCredentialIssuer.Service" + value: "{{ .Values.issuer.logging.businessLogic }}" + - name: "SWAGGERENABLED" + value: "{{ .Values.issuer.swaggerEnabled }}" + - name: "PORTAL__CLIENTSECRET" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.secretName" . }}" + key: "portal-client-secret" + - name: "PORTAL__GRANTTYPE" + value: "{{ .Values.issuer.portal.grantType }}" + - name: "PORTAL__TOKENADDRESS" + value: "{{ .Values.centralidp.address }}{{ .Values.centralidp.tokenPath }}" + - name: "PORTAL__PASSWORD" + value: "empty" + - name: "PORTAL__SCOPE" + value: "{{ .Values.issuer.portal.scope }}" + - name: "PORTAL__USERNAME" + value: "empty" + - name: "CREDENTIAL__ISSUERDID" + value: "{{ .Values.issuer.credential.issuerDid }}" + - name: "CREDENTIAL__ISSUERBPN" + value: "{{ .Values.issuer.credential.issuerBpn }}" + - name: "CREDENTIAL__STATUSLISTURL" + value: "{{ .Values.issuer.credential.statusListUrl }}" + - name: "CREDENTIAL__ENCRYPTIONCONFIG__ENCRYPTIONCONFIGINDEX" + value: "{{ .Values.issuer.credential.encryptionConfigIndex }}" + - name: "CREDENTIAL__ENCRYPTIONCONFIGS__0__INDEX" + value: "{{ .Values.issuer.credential.encryptionConfigs.index0.index}}" + - name: "CREDENTIAL__ENCRYPTIONCONFIGS__0__CIPHERMODE" + value: "{{ .Values.issuer.credential.encryptionConfigs.index0.cipherMode}}" + - name: "CREDENTIAL__ENCRYPTIONCONFIGS__0__PADDINGMODE" + value: "{{ .Values.issuer.credential.encryptionConfigs.index0.paddingMode}}" + - name: "CREDENTIAL__ENCRYPTIONCONFIGS__0__ENCRYPTIONKEY" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.secretName" . }}" + key: "credential-encryption-key0" + ports: + - name: http + containerPort: {{ .Values.portContainer }} + protocol: TCP + startupProbe: + httpGet: + path: {{ .Values.issuer.healthChecks.startup.path }} + port: {{ .Values.portContainer }} + scheme: HTTP + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + livenessProbe: + httpGet: + path: {{ .Values.issuer.healthChecks.liveness.path }} + port: {{ .Values.portContainer }} + scheme: HTTP + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + readinessProbe: + httpGet: + path: {{ .Values.issuer.healthChecks.readyness.path }} + port: {{ .Values.portContainer }} + scheme: HTTP + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + resources: + {{- toYaml .Values.issuer.resources | nindent 10 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/ssi-credential-issuer/templates/ingress.yaml b/charts/ssi-credential-issuer/templates/ingress.yaml new file mode 100644 index 00000000..260cf005 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/ingress.yaml @@ -0,0 +1,80 @@ +{{- /* +* 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 +*/}} + +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "issuer.fullname" . -}} +{{- $svcPort := .Values.portService -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "issuer.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ .backend.port }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/ssi-credential-issuer/templates/job-issuer-migrations.yaml b/charts/ssi-credential-issuer/templates/job-issuer-migrations.yaml new file mode 100644 index 00000000..89717e57 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/job-issuer-migrations.yaml @@ -0,0 +1,79 @@ +############################################################### +# 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 +############################################################### + +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.issuermigrations.name }} + annotations: + "batch.kubernetes.io/job-tracking": "true" + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" +spec: + template: + metadata: + name: {{ include "issuer.fullname" . }}-{{ .Values.issuermigrations.name }} + spec: + restartPolicy: Never + containers: + - name: {{ include "issuer.fullname" . }}-{{ .Values.issuermigrations.name }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + image: "{{ .Values.issuermigrations.image.name }}:{{ .Values.issuermigrations.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.issuermigrations.imagePullPolicy }}" + env: + - name: DOTNET_ENVIRONMENT + value: "{{ .Values.dotnetEnvironment }}" + {{- if .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ template "issuer.postgresSecretName" . }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ template "issuer.postgresql.primary.fullname" . }};Database={{ .Values.postgresql.auth.database }};Port={{ .Values.postgresql.auth.port }};User Id={{ .Values.postgresql.auth.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + {{- if not .Values.postgresql.enabled }} + - name: "ISSUER_PASSWORD" + valueFrom: + secretKeyRef: + name: "{{ .Values.externalDatabase.existingSecret }}" + key: "password" + - name: "CONNECTIONSTRINGS__ISSUERDB" + value: "Server={{ .Values.externalDatabase.host }};Database={{ .Values.externalDatabase.database }};Port={{ .Values.externalDatabase.port }};User Id={{ .Values.externalDatabase.username }};Password=$(ISSUER_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" + {{- end }} + - name: "SEEDING__TESTDATAENVIRONMENTS__0" + value: "{{ .Values.issuermigrations.seeding.testDataEnvironments }}" + - name: "SEEDING__DATAPATHS__0" + value: "{{ .Values.issuermigrations.seeding.testDataPaths }}" + - name: "SERILOG__MINIMUMLEVEL__Default" + value: "{{ .Values.issuermigrations.logging.default }}" + - name: "PROCESSIDENTITY__IDENTITYID" + value: "{{ .Values.issuermigrations.processIdentity.identityId }}" + ports: + - name: http + containerPort: {{ .Values.portContainer }} + protocol: TCP + resources: + {{- toYaml .Values.issuermigrations.resources | nindent 10 }} diff --git a/charts/ssi-credential-issuer/templates/secret-external-db.yaml b/charts/ssi-credential-issuer/templates/secret-external-db.yaml new file mode 100644 index 00000000..e0449e7d --- /dev/null +++ b/charts/ssi-credential-issuer/templates/secret-external-db.yaml @@ -0,0 +1,39 @@ +{{- /* +* 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 +*/}} + +{{- if not .Values.postgresql.enabled -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.externalDatabase.existingSecret }} + namespace: {{ .Release.Namespace }} +type: Opaque +# use lookup function to check if secret exists +{{- $secret := (lookup "v1" "Secret" .Release.Namespace .Values.externalDatabase.existingSecret) }} +{{ if $secret -}} +data: + # if secret exists, use value provided from values file (to cover update scenario) or existing value from secret + # use data map instead of stringData to prevent base64 encoding of already base64-encoded existing value from secret + password: {{ ( .Values.externalDatabase.password | b64enc ) | default $secret.data.password | quote }} +{{ else -}} +stringData: + # if secret doesn't exist, use provided value from values file or generate a random one + password: {{ .Values.externalDatabase.password | default ( randAlphaNum 32 ) | quote }} +{{ end }} +{{- end -}} diff --git a/charts/ssi-credential-issuer/templates/secret-postgres.yaml b/charts/ssi-credential-issuer/templates/secret-postgres.yaml new file mode 100644 index 00000000..c1058572 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/secret-postgres.yaml @@ -0,0 +1,45 @@ +{{- /* +* 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 +*/}} + +{{- if .Values.postgresql.enabled -}} +{{- $secretName := include "issuer.postgresSecretName" . -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ .Release.Namespace }} +type: Opaque +# use lookup function to check if secret exists +{{- $secret := (lookup "v1" "Secret" .Release.Namespace $secretName) }} +{{ if $secret -}} +data: + # if secret exists, use value provided from values file (to cover update scenario) or existing value from secret + # use data map instead of stringData to prevent base64 encoding of already base64-encoded existing value from secret + # use index function for secret keys with hyphen otherwise '$secret.data.secretKey' works too + postgres-password: {{ ( .Values.postgresql.auth.postgrespassword | b64enc ) | default ( index $secret.data "postgres-password" ) | quote }} + password: {{ ( .Values.postgresql.auth.password | b64enc ) | default $secret.data.password | quote }} + replication-password: {{ ( .Values.postgresql.auth.replicationPassword | b64enc ) | default ( index $secret.data "replication-password" ) | quote}} +{{ else -}} +stringData: + # if secret doesn't exist, use provided value from values file or generate a random one + postgres-password: {{ .Values.postgresql.auth.postgrespassword | default ( randAlphaNum 32 ) | quote }} + password: {{ .Values.postgresql.auth.password | default ( randAlphaNum 32 ) | quote }} + replication-password: {{ .Values.postgresql.auth.replicationPassword | default ( randAlphaNum 32 ) | quote }} +{{ end }} +{{- end -}} diff --git a/charts/ssi-credential-issuer/templates/secret.yaml b/charts/ssi-credential-issuer/templates/secret.yaml new file mode 100644 index 00000000..e3315e49 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/secret.yaml @@ -0,0 +1,49 @@ +{{- /* +* 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 +*/}} + +{{- if not .Values.existingSecret }} +{{- $secretName := include "issuer.secretName" . -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "issuer.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "issuer.labels" . | nindent 4 }} +type: Opaque +# use lookup function to check if secret exists +{{- $secret := (lookup "v1" "Secret" .Release.Namespace $secretName) }} +{{ if $secret -}} +data: + # if secret exists, use value provided from values file (to cover update scenario) or existing value from secret or generate a random one (if keys are added later on) + # use data map instead of stringData to prevent base64 encoding of already base64-encoded existing value from secret + # use index function for secret keys with hyphen otherwise '$secret.data.secretKey' works too + portal-client-secret: {{ coalesce ( .Values.processesworker.portal.clientSecret | b64enc ) ( index $secret.data "portal-client-secret" ) | default ( randAlphaNum 32 ) | quote }} + wallet-client-secret: {{ coalesce ( .Values.processesworker.wallet.clientSecret | b64enc ) ( index $secret.data "wallet-client-secret" ) | default ( randAlphaNum 32 ) | quote }} + credential-encryption-key0: {{ coalesce ( .Values.issuer.credential.encryptionConfigs.index0.encryptionKey | b64enc ) ( index $secret.data "credential-encryption-key" ) | default ( randAlphaNum 32 ) | quote }} + process-wallet-encryption-key0: {{ coalesce ( .Values.processesworker.wallet.encryptionConfigs.index0.encryptionKey | b64enc ) ( index $secret.data "process-wallet-encryption-key" ) | default ( randAlphaNum 32 ) | quote }} +{{ else -}} +stringData: + # if secret doesn't exist, use provided value from values file or generate a random one + portal-client-secret: {{ .Values.processesworker.portal.clientSecret | default ( randAlphaNum 32 ) | quote }} + wallet-client-secret: {{ .Values.processesworker.wallet.clientSecret | default ( randAlphaNum 32 ) | quote }} + credential-encryption-key0: {{ .Values.issuer.credential.encryptionConfigs.index0.encryptionKey | default ( randAlphaNum 32 ) | quote }} + process-wallet-encryption-key0: {{ .Values.processesworker.wallet.encryptionConfigs.index0.encryptionKey | default ( randAlphaNum 32 ) | quote }} +{{ end }} +{{- end -}} diff --git a/charts/ssi-credential-issuer/templates/service-credential-issuer.yaml b/charts/ssi-credential-issuer/templates/service-credential-issuer.yaml new file mode 100644 index 00000000..1adab307 --- /dev/null +++ b/charts/ssi-credential-issuer/templates/service-credential-issuer.yaml @@ -0,0 +1,33 @@ +############################################################### +# 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 +############################################################### + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "issuer.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "issuer.labels" . | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: {{ .Values.portService }} + targetPort: {{ .Values.portContainer }} + selector: + {{- include "issuer.selectorLabels" . | nindent 4 }} diff --git a/charts/ssi-credential-issuer/values.yaml b/charts/ssi-credential-issuer/values.yaml new file mode 100644 index 00000000..e05d9792 --- /dev/null +++ b/charts/ssi-credential-issuer/values.yaml @@ -0,0 +1,319 @@ +############################################################### +# 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 +############################################################### + +issuer: + image: + name: "docker.io/tractusx/credential-issuer-service" + tag: "" + imagePullPolicy: "IfNotPresent" + # -- We recommend to review the default resource limits as this should a conscious choice. + resources: + requests: + cpu: 15m + memory: 400M + limits: + cpu: 45m + memory: 400M + logging: + businessLogic: "Information" + default: "Information" + healthChecks: + startup: + path: "/health/startup" + tags: + - name: "HEALTHCHECKS__0__TAGS__1" + value: "issuerdb" + liveness: + path: "/healthz" + readyness: + path: "/ready" + swaggerEnabled: false + portal: + scope: "openid" + grantType: "client_credentials" + # -- Provide portal client-id from CX IAM centralidp. + clientId: "portal-client-id" + # -- Client-secret for portal client-id. Secret-key 'portal-client-secret'. + clientSecret: "" + credential: + issuerDid: "" + issuerBpn: "" + statusListUrl: "" + encryptionConfigIndex: 0 + encryptionConfigs: + index0: + index: 0 + cipherMode: "CBC" + paddingMode: "PKCS7" + # -- EncryptionKey for wallet. Secret-key 'credential-encryption-key0'. + # Expected format is 256 bit (64 digits) hex. + encryptionKey: "" + +issuermigrations: + name: "migrations" + image: + name: "docker.io/tractusx/credential-issuer-migrations" + tag: "" + imagePullPolicy: "IfNotPresent" + # -- We recommend to review the default resource limits as this should a conscious choice. + resources: + requests: + cpu: 15m + memory: 105M + limits: + cpu: 45m + memory: 105M + seeding: + testDataEnvironments: "" + testDataPaths: "Seeder/Data" + logging: + default: "Information" + processIdentity: + identityId: ac1cf001-7fbc-1f2f-817f-bce058020006 + +processesworker: + name: "processesworker" + image: + name: "docker.io/tractusx/credential-issuer-processes-worker" + tag: "" + imagePullPolicy: "IfNotPresent" + # -- We recommend to review the default resource limits as this should a conscious choice. + resources: + requests: + cpu: 15m + memory: 105M + limits: + cpu: 45m + memory: 105M + logging: + default: "Information" + portal: + scope: "openid" + grantType: "client_credentials" + # -- Provide portal client-id from CX IAM centralidp. + clientId: "portal-client-id" + # -- Client-secret for portal client-id. Secret-key 'portal-client-secret'. + clientSecret: "" + processIdentity: + identityId: ac1cf001-7fbc-1f2f-817f-bce058020006 + wallet: + scope: "openid" + grantType: "client_credentials" + # -- Provide wallet client-id from CX IAM centralidp. + clientId: "wallet-client-id" + # -- Client-secret for wallet client-id. Secret-key 'wallet-client-secret'. + clientSecret: "" + encryptionConfigIndex: 0 + encryptionConfigs: + index0: + index: 0 + cipherMode: "CBC" + paddingMode: "PKCS7" + # -- EncryptionKey for wallet. Secret-key 'process-wallet-encryption-key0'. + # Expected format is 256 bit (64 digits) hex. + encryptionKey: "" + +credentialExpiry: + name: "expiry" + image: + name: "docker.io/tractusx/credential-expiry-app" + tag: "" + imagePullPolicy: "IfNotPresent" + # -- We recommend to review the default resource limits as this should a conscious choice. + resources: + requests: + cpu: 15m + memory: 105M + limits: + cpu: 45m + memory: 105M + processIdentity: + identityId: ac1cf001-7fbc-1f2f-817f-bce058020006 + logging: + default: "Information" + expiry: + expiredVcsToDeleteInMonth: 12 + inactiveVcsToDeleteInWeeks: 12 + +# -- Secret containing the client-secrets for the connection to portal and wallet +# as well as encryptionKeys for issuer.credential and processesworker.wallet +existingSecret: "" + +dotnetEnvironment: "Production" + +dbConnection: + schema: "issuer" + sslMode: "Disable" + +postgresql: + # -- PostgreSQL chart configuration; + # default configurations: + # host: "issuer-postgresql-primary", + # port: 5432; + # Switch to enable or disable the PostgreSQL helm chart. + enabled: true + # -- Setting image tag to major to get latest minor updates + image: + tag: "15-debian-12" + commonLabels: + app.kubernetes.io/version: "15" + auth: + # -- Non-root username. + username: issuer + # -- Database name. + database: issuer + # -- Secret containing the passwords for root usernames postgres and non-root username issuer. + # Should not be changed without changing the "issuer-postgresSecretName" template as well. + existingSecret: "{{ .Release.Name }}-issuer-postgres" + # -- Password for the root username 'postgres'. Secret-key 'postgres-password'. + postgrespassword: "" + # -- Password for the non-root username 'issuer'. Secret-key 'password'. + password: "" + # -- Password for the non-root username 'repl_user'. Secret-key 'replication-password'. + replicationPassword: "" + architecture: replication + audit: + pgAuditLog: "write, ddl" + logLinePrefix: "%m %u %d " + primary: + # -- Extended PostgreSQL Primary configuration (increase of max_connections recommended - default is 100) + extendedConfiguration: "" + initdb: + scriptsConfigMap: "{{ .Release.Name }}-issuer-cm-postgres" + readReplicas: + # -- Extended PostgreSQL read only replicas configuration (increase of max_connections recommended - default is 100) + extendedConfiguration: "" + +externalDatabase: + # -- External PostgreSQL configuration + # IMPORTANT: non-root db user needs to be created beforehand on external database. + # And the init script (02-init-db.sql) available in templates/configmap-postgres-init.yaml + # needs to be executed beforehand. + # Database host ('-primary' is added as postfix). + host: "issuer-postgres-ext" + # -- Database port number. + port: 5432 + # -- Non-root username for issuer. + username: "issuer" + # -- Database name. + database: "issuer" + # -- Password for the non-root username (default 'issuer'). Secret-key 'password'. + password: "" + # -- Secret containing the password non-root username, (default 'issuer'). + existingSecret: "issuer-external-db" + # -- Name of an existing secret key containing the database credentials. + existingSecretPasswordKey: "password" + +# -- Provide details about centralidp (CX IAM) Keycloak instance. +centralidp: + # -- Provide centralidp base address (CX IAM), without trailing '/auth'. + address: "https://centralidp.example.org" + authRealm: "CX-Central" + jwtBearerOptions: + requireHttpsMetadata: "true" + metadataPath: "/auth/realms/CX-Central/.well-known/openid-configuration" + tokenValidationParameters: + validIssuerPath: "/auth/realms/CX-Central" + validAudience: "Cl24-CX-SSI-CredentialIssuer" + refreshInterval: "00:00:30" + tokenPath: "/auth/realms/CX-Central/protocol/openid-connect/token" + # -- Flag if the api should be used with an leading /auth path + useAuthTrail: true + +ingress: + # -- SSI Credential Issuer ingress parameters, + # enable ingress record generation for ssi-credential-issuer. + enabled: false + # className: "nginx" + ## Optional annotations when using the nginx ingress class + # annotations: + # nginx.ingress.kubernetes.io/use-regex: "true" + # nginx.ingress.kubernetes.io/enable-cors: "true" + # nginx.ingress.kubernetes.io/proxy-body-size: "8m" + # # -- Provide CORS allowed origin. + # nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.example.org" + tls: + # -- Provide tls secret. + - secretName: "" + # -- Provide host for tls secret. + hosts: + - "" + hosts: + # -- Provide default path for the ingress record. + - host: "" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +portContainer: 8080 + +portService: 8080 + +replicaCount: 3 + +# -- Node labels for pod assignment +nodeSelector: {} + +# -- Tolerations for pod assignment +tolerations: [] + +affinity: +# -- Following Catena-X Helm Best Practices, +# [reference](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity). + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: DoesNotExist + topologyKey: kubernetes.io/hostname + +updateStrategy: +# -- Update strategy type, +# rolling update configuration parameters, +# [reference](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies). + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + +# -- Following Catena-X Helm Best Practices, +# [reference](https://github.com/helm/charts/blob/master/stable/nginx-ingress/values.yaml#L210). +startupProbe: + failureThreshold: 30 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 +livenessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 10 +readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 diff --git a/consortia/argocd-app-templates/appsetup-beta.yaml b/consortia/argocd-app-templates/appsetup-beta.yaml new file mode 100644 index 00000000..e2c1676b --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-beta.yaml @@ -0,0 +1,38 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer +spec: + destination: + namespace: product-portal + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: ssi-credential-issuer-1.0.0 + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-beta.yaml' + project: project-portal diff --git a/consortia/argocd-app-templates/appsetup-dev.yaml b/consortia/argocd-app-templates/appsetup-dev.yaml new file mode 100644 index 00000000..26edc4fa --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-dev.yaml @@ -0,0 +1,41 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer +spec: + destination: + namespace: product-portal + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: dev + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-dev.yaml' + project: project-portal + syncPolicy: + automated: + prune: true diff --git a/consortia/argocd-app-templates/appsetup-int.yaml b/consortia/argocd-app-templates/appsetup-int.yaml new file mode 100644 index 00000000..45a42a45 --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-int.yaml @@ -0,0 +1,38 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer +spec: + destination: + namespace: product-portal + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: ssi-credential-issuer-1.0.0-rc.1 + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-int.yaml' + project: project-portal diff --git a/consortia/argocd-app-templates/appsetup-pen.yaml b/consortia/argocd-app-templates/appsetup-pen.yaml new file mode 100644 index 00000000..b869af9b --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-pen.yaml @@ -0,0 +1,38 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer-pen +spec: + destination: + namespace: product-portal-pen + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: ssi-credential-issuer-1.0.0-rc.1 + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-pen.yaml' + project: project-portal diff --git a/consortia/argocd-app-templates/appsetup-rc.yaml b/consortia/argocd-app-templates/appsetup-rc.yaml new file mode 100644 index 00000000..5809c12a --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-rc.yaml @@ -0,0 +1,38 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer-rc +spec: + destination: + namespace: product-iam + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: release-candidate + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-rc.yaml' + project: project-portal diff --git a/consortia/argocd-app-templates/appsetup-stable.yaml b/consortia/argocd-app-templates/appsetup-stable.yaml new file mode 100644 index 00000000..6152c557 --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-stable.yaml @@ -0,0 +1,77 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer +spec: + destination: + name: '' + namespace: product-portal + server: 'https://kubernetes.default.svc' + source: + path: '' + repoURL: 'https://eclipse-tractusx.github.io/charts/dev' + targetRevision: ssi-credential-issuer-1.0.0-rc.1 + plugin: + env: + - name: HELM_VALUES + value: | + issuerBackendAddress: "https://ssi-credential-issuer.stable.demo.catena-x.net" + ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.stable.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer.stable.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer.stable.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + service: "issuer-service" + port: 8080 + issuermigrations: + logging: + default: "Debug" + postgresql: + primary: + extendedConfiguration: | + max_connections = 200 + readReplicas: + extendedConfiguration: | + max_connections = 200 + secrets: + postgresql: + auth: + existingSecret: + postgrespassword: "" + password: "" + replicationPassword: "" + chart: ssi-credential-issuer + sources: [] + project: project-portal diff --git a/consortia/argocd-app-templates/appsetup-upgrade.yaml b/consortia/argocd-app-templates/appsetup-upgrade.yaml new file mode 100644 index 00000000..1b6c83fd --- /dev/null +++ b/consortia/argocd-app-templates/appsetup-upgrade.yaml @@ -0,0 +1,38 @@ +############################################################### +# 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 +############################################################### + +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: ssi-credential-issuer +spec: + destination: + namespace: product-portal + server: 'https://kubernetes.default.svc' + source: + path: charts/ssi-credential-issuer + repoURL: 'https://github.com/eclipse-tractusx/ssi-credential-issuer.git' + targetRevision: ssi-credential-issuer-1.0.0 + plugin: + env: + - name: AVP_SECRET + value: vault-secret + - name: helm_args + value: '-f values.yaml -f ../../consortia/environments/values-upgrade.yaml' + project: project-portal diff --git a/consortia/environments/values-beta.yaml b/consortia/environments/values-beta.yaml new file mode 100644 index 00000000..2ac0d801 --- /dev/null +++ b/consortia/environments/values-beta.yaml @@ -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 +############################################################### + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.beta.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer.beta.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer.beta.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +issuer: + swaggerEnabled: true + credential: + issuerBpn: "BPNL00000003CRHK" + +issuermigrations: + logging: + default: "Debug" + +processesworker: + logging: + default: "Debug" + +credentialexpiry: + logging: + default: "Debug" + +centralidp: + address: "https://centralidp.beta.demo.catena-x.net" + jwtBearerOptions: + tokenValidationParameters: + validAudience: "Cl24-CX-SSI-CredentialIssuer" + +postgresql: + auth: + postgrespassword: "" + password: "" + replicationPassword: "" diff --git a/consortia/environments/values-dev.yaml b/consortia/environments/values-dev.yaml new file mode 100644 index 00000000..03fad9f1 --- /dev/null +++ b/consortia/environments/values-dev.yaml @@ -0,0 +1,79 @@ +############################################################### +# 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 +############################################################### + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.dev.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer.dev.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer.dev.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +issuer: + image: + tag: "dev" + imagePullPolicy: "Always" + swaggerEnabled: true + credential: + issuerBpn: "BPNL00000003CRHK" + +issuermigrations: + image: + tag: "dev" + imagePullPolicy: "Always" + logging: + default: "Debug" + +processesworker: + image: + tag: "dev" + imagePullPolicy: "Always" + logging: + default: "Debug" + +credentialexpiry: + image: + tag: "dev" + imagePullPolicy: "Always" + logging: + default: "Debug" + +centralidp: + address: "https://centralidp.dev.demo.catena-x.net" + jwtBearerOptions: + tokenValidationParameters: + validAudience: "Cl24-CX-SSI-CredentialIssuer" + +postgresql: + auth: + postgrespassword: "" + password: "" + replicationPassword: "" diff --git a/consortia/environments/values-int.yaml b/consortia/environments/values-int.yaml new file mode 100644 index 00000000..12a895cb --- /dev/null +++ b/consortia/environments/values-int.yaml @@ -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 +############################################################### + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.int.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer.int.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer.int.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +issuer: + swaggerEnabled: true + credential: + issuerBpn: "BPNL00000003CRHK" + +issuermigrations: + logging: + default: "Debug" + +processesworker: + logging: + default: "Debug" + +credentialexpiry: + logging: + default: "Debug" + +centralidp: + address: "https://centralidp.int.demo.catena-x.net" + jwtBearerOptions: + tokenValidationParameters: + validAudience: "Cl24-CX-SSI-CredentialIssuer" + +postgresql: + auth: + postgrespassword: "" + password: "" + replicationPassword: "" diff --git a/consortia/environments/values-pen.yaml b/consortia/environments/values-pen.yaml new file mode 100644 index 00000000..bd27861d --- /dev/null +++ b/consortia/environments/values-pen.yaml @@ -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 +############################################################### + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.dev.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer-backend-pen.dev.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer-backend-pen.dev.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +issuer: + swaggerEnabled: true + credential: + issuerBpn: "BPNL00000003CRHK" + +issuermigrations: + logging: + default: "Debug" + +processesworker: + logging: + default: "Debug" + +credentialexpiry: + logging: + default: "Debug" + +centralidp: + address: "https://centralidp-pen.dev.demo.catena-x.net" + jwtBearerOptions: + tokenValidationParameters: + validAudience: "Cl24-CX-SSI-CredentialIssuer" + +postgresql: + auth: + postgrespassword: "" + password: "" + replicationPassword: "" diff --git a/consortia/environments/values-rc.yaml b/consortia/environments/values-rc.yaml new file mode 100644 index 00000000..fabbf41e --- /dev/null +++ b/consortia/environments/values-rc.yaml @@ -0,0 +1,79 @@ +############################################################### +# 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 +############################################################### + +ingress: + enabled: true + className: "nginx" + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "8m" + nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000, https://*.dev.demo.catena-x.net" + tls: + - secretName: "tls-secret" + hosts: + - "ssi-credential-issuer-backend-rc.dev.demo.catena-x.net" + hosts: + - host: "ssi-credential-issuer-backend-rc.dev.demo.catena-x.net" + paths: + - path: "/api/issuer" + pathType: "Prefix" + backend: + port: 8080 + +issuer: + image: + tag: "rc" + imagePullPolicy: "Always" + swaggerEnabled: true + credential: + issuerBpn: "BPNL00000003CRHK" + +issuermigrations: + image: + tag: "rc" + imagePullPolicy: "Always" + logging: + default: "Debug" + +processesworker: + image: + tag: "rc" + imagePullPolicy: "Always" + logging: + default: "Debug" + +credentialexpiry: + image: + tag: "rc" + imagePullPolicy: "Always" + logging: + default: "Debug" + +centralidp: + address: "https://centralidp.dev.demo.catena-x.net" + jwtBearerOptions: + tokenValidationParameters: + validAudience: "Cl24-CX-SSI-CredentialIssuer" + +postgresql: + auth: + postgrespassword: "" + password: "" + replicationPassword: "" diff --git a/docker/Dockerfile-credential-expiry-app b/docker/Dockerfile-credential-expiry-app new file mode 100644 index 00000000..7749fff2 --- /dev/null +++ b/docker/Dockerfile-credential-expiry-app @@ -0,0 +1,36 @@ +############################################################### +# 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 +############################################################### + +FROM mcr.microsoft.com/dotnet/runtime:7.0-alpine AS base + +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine-amd64 AS publish +WORKDIR / +COPY LICENSE NOTICE.md DEPENDENCIES / +COPY src/ src/ +RUN dotnet restore "src/credentials/SsiCredentialIssuer.Expiry.App/SsiCredentialIssuer.Expiry.App.csproj" +WORKDIR /src/credentials/SsiCredentialIssuer.Expiry.App +RUN dotnet publish "SsiCredentialIssuer.Expiry.App.csproj" -c Release -o /app/publish + +FROM base AS final +ENV COMPlus_EnableDiagnostics=0 +WORKDIR /app +COPY --from=publish /app/publish . +RUN chown -R 1000:3000 /app +USER 1000:3000 +ENTRYPOINT ["dotnet", "Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.dll"] diff --git a/docker/Dockerfile-credential-issuer-migrations b/docker/Dockerfile-credential-issuer-migrations new file mode 100644 index 00000000..c5fb1365 --- /dev/null +++ b/docker/Dockerfile-credential-issuer-migrations @@ -0,0 +1,36 @@ +############################################################### +# 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 +############################################################### + +FROM mcr.microsoft.com/dotnet/runtime:7.0-alpine AS base + +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine-amd64 AS publish +WORKDIR / +COPY LICENSE NOTICE.md DEPENDENCIES / +COPY /src/database /src/database +COPY /src/processes/Processes.Worker.Library /src/processes/Processes.Worker.Library +WORKDIR /src/database/SsiCredentialIssuer.Migrations +RUN dotnet publish "SsiCredentialIssuer.Migrations.csproj" -c Release -o /migrations/publish + +FROM base AS final +ENV COMPlus_EnableDiagnostics=0 +WORKDIR /migrations +COPY --from=publish /migrations/publish . +RUN chown -R 1000:3000 /migrations +USER 1000:3000 +ENTRYPOINT ["dotnet", "Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.dll"] diff --git a/docker/Dockerfile-credential-issuer-processes-worker b/docker/Dockerfile-credential-issuer-processes-worker new file mode 100644 index 00000000..d61ff7a7 --- /dev/null +++ b/docker/Dockerfile-credential-issuer-processes-worker @@ -0,0 +1,36 @@ +############################################################### +# 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 +############################################################### + +FROM mcr.microsoft.com/dotnet/runtime:7.0-alpine AS base + +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine-amd64 AS publish +WORKDIR / +COPY LICENSE NOTICE.md DEPENDENCIES / +COPY src/ src/ +RUN dotnet restore "src/processes/Processes.Worker/Processes.Worker.csproj" +WORKDIR /src/processes/Processes.Worker +RUN dotnet publish "Processes.Worker.csproj" -c Release -o /app/publish + +FROM base AS final +ENV COMPlus_EnableDiagnostics=0 +WORKDIR /app +COPY --from=publish /app/publish . +RUN chown -R 1000:3000 /app +USER 1000:3000 +ENTRYPOINT ["dotnet", "Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.dll"] diff --git a/docker/Dockerfile-credential-issuer-service b/docker/Dockerfile-credential-issuer-service new file mode 100644 index 00000000..6747b1e0 --- /dev/null +++ b/docker/Dockerfile-credential-issuer-service @@ -0,0 +1,37 @@ +############################################################### +# 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 +############################################################### + +FROM mcr.microsoft.com/dotnet/aspnet:7.0-alpine AS base + +FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine-amd64 AS publish +WORKDIR / +COPY LICENSE NOTICE.md DEPENDENCIES / +COPY src/ src/ +WORKDIR /src/issuer/SsiCredentialIssuer.Service +RUN dotnet publish "SsiCredentialIssuer.Service.csproj" -c Release -o /app/publish + +FROM base AS final +ENV COMPlus_EnableDiagnostics=0 +WORKDIR /app +COPY --from=publish /app/publish . +ENV ASPNETCORE_URLS http://+:8080 +EXPOSE 8080 +RUN chown -R 1000:3000 /app +USER 1000:3000 +ENTRYPOINT ["dotnet", "Org.Eclipse.TractusX.SsiCredentialIssuer.Service.dll"] diff --git a/docker/notice-credential-expiry-app.md b/docker/notice-credential-expiry-app.md new file mode 100644 index 00000000..3cecccb3 --- /dev/null +++ b/docker/notice-credential-expiry-app.md @@ -0,0 +1,22 @@ +## Notice for Docker image + +DockerHub: [https://hub.docker.com/r/tractusx/credential-expiry-app](https://hub.docker.com/r/tractusx/credential-expiry-app) + +Eclipse Tractus-X product(s) installed within the image: + +__Credential Expiry App__ + +- GitHub: https://github.com/eclipse-tractusx/ssi-credential-issuer +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/docker/Dockerfile-credential-expiry-app +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/LICENSE) + +__Used base images__ + +- Dockerfile: [mcr.microsoft.com/dotnet/runtime:7.0-alpine](https://github.com/dotnet/dotnet-docker/blob/main/src/runtime/7.0/alpine3.19/amd64/Dockerfile) +- GitHub project: [https://github.com/dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker) +- DockerHub: [https://hub.docker.com/_/microsoft-dotnet-runtime](https://hub.docker.com/_/microsoft-dotnet-runtime) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. diff --git a/docker/notice-credential-issuer-migrations.md b/docker/notice-credential-issuer-migrations.md new file mode 100644 index 00000000..41bc069c --- /dev/null +++ b/docker/notice-credential-issuer-migrations.md @@ -0,0 +1,22 @@ +## Notice for Docker image + +DockerHub: [https://hub.docker.com/r/tractusx/credential-issuer-migrations](https://hub.docker.com/r/tractusx/credential-issuer-migrations) + +Eclipse Tractus-X product(s) installed within the image: + +__Credential Issuer Migrations__ + +- GitHub: https://github.com/eclipse-tractusx/ssi-credential-issuer +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/docker/Dockerfile-credential-issuer-migrations +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/LICENSE) + +__Used base images__ + +- Dockerfile: [mcr.microsoft.com/dotnet/runtime:7.0-alpine](https://github.com/dotnet/dotnet-docker/blob/main/src/runtime/7.0/alpine3.19/amd64/Dockerfile) +- GitHub project: [https://github.com/dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker) +- DockerHub: [https://hub.docker.com/_/microsoft-dotnet-runtime](https://hub.docker.com/_/microsoft-dotnet-runtime) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. diff --git a/docker/notice-credential-issuer-processes-worker.md b/docker/notice-credential-issuer-processes-worker.md new file mode 100644 index 00000000..451c764d --- /dev/null +++ b/docker/notice-credential-issuer-processes-worker.md @@ -0,0 +1,22 @@ +## Notice for Docker image + +DockerHub: [https://hub.docker.com/r/tractusx/credential-issuer-processes-worker](https://hub.docker.com/r/tractusx/credential-issuer-processes-worker) + +Eclipse Tractus-X product(s) installed within the image: + +Issuer Checklist Worker__ + +- GitHub: https://github.com/eclipse-tractusx/ssi-credential-issuer +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/docker/Dockerfile-credential-issuer-processes-worker +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/LICENSE) + +__Used base images__ + +- Dockerfile: [mcr.microsoft.com/dotnet/runtime:7.0-alpine](https://github.com/dotnet/dotnet-docker/blob/main/src/runtime/7.0/alpine3.17/amd64/Dockerfile) +- GitHub project: [https://github.com/dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker) +- DockerHub: [https://hub.docker.com/_/microsoft-dotnet-runtime](https://hub.docker.com/_/microsoft-dotnet-runtime) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. diff --git a/docker/notice-credential-issuer-service.md b/docker/notice-credential-issuer-service.md new file mode 100644 index 00000000..138a4597 --- /dev/null +++ b/docker/notice-credential-issuer-service.md @@ -0,0 +1,22 @@ +## Notice for Docker image + +DockerHub: [https://hub.docker.com/r/tractusx/ssi-credential-issuer-service](https://hub.docker.com/r/tractusx/ssi-credential-issuer-service) + +Eclipse Tractus-X product(s) installed within the image: + +__Policy Hub Service__ + +- GitHub: https://github.com/eclipse-tractusx/ssi-credential-issuer +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/docker/Dockerfile-credential-issuer-service +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/ssi-credential-issuer/blob/main/LICENSE) + +__Used base images__ + +- Dockerfile: [mcr.microsoft.com/dotnet/aspnet:7.0-alpine](https://github.com/dotnet/dotnet-docker/blob/main/src/aspnet/7.0/alpine3.19/amd64/Dockerfile) +- GitHub project: [https://github.com/dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker) +- DockerHub: [https://hub.docker.com/_/microsoft-dotnet-aspnet](https://hub.docker.com/_/microsoft-dotnet-aspnet) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. diff --git a/scripts/check-dependencies.md b/scripts/check-dependencies.md index 615880b7..ef25c0e9 100644 --- a/scripts/check-dependencies.md +++ b/scripts/check-dependencies.md @@ -6,7 +6,7 @@ This workflow uses the executable jar in the download directory. In order to update the executable jar run the following command from the root directory: - curl -L --output ./scripts/download/org.eclipse.dash.licenses-1.1.1.jar 'https://repo.eclipse.org/service/local/artifact/maven/redirect?r=dash-licenses&g=org.eclipse.dash&a=org.eclipse.dash.licenses&v=1.1.1' + curl -L --output ./scripts/download/org.eclipse.dash.licenses-1.1.1.jar 'https://repo.eclipse.org/service/local/artifact/maven/redirect?r=dash-licenses&g=org.eclipse.dash&a=org.eclipse.dash.licenses&v=LATEST' ## NOTICE diff --git a/scripts/download/org.eclipse.dash.licenses-1.1.1-20240213.065029-71.jar b/scripts/download/org.eclipse.dash.licenses-1.1.1.jar similarity index 100% rename from scripts/download/org.eclipse.dash.licenses-1.1.1-20240213.065029-71.jar rename to scripts/download/org.eclipse.dash.licenses-1.1.1.jar diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..ae93adb6 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,25 @@ + + + + + 1.0.0 + rc.1 + + diff --git a/src/SsiCredentialIssuer.sln b/src/SsiCredentialIssuer.sln new file mode 100644 index 00000000..7a965431 --- /dev/null +++ b/src/SsiCredentialIssuer.sln @@ -0,0 +1,191 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "database", "database", "{E9E08CE9-985A-4507-BBD3-9470623986CF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issuer", "issuer", "{32D0AE23-BFAA-4D65-AF9D-2DF951BA5A3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Service", "issuer\SsiCredentialIssuer.Service\SsiCredentialIssuer.Service.csproj", "{6905B6DF-722B-4882-A2CB-5E6BFD0244F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Entities", "database\SsiCredentialIssuer.Entities\SsiCredentialIssuer.Entities.csproj", "{5627AA4F-A64B-4878-A139-F3ED6734BDEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.DbAccess", "database\SsiCredentialIssuer.DbAccess\SsiCredentialIssuer.DbAccess.csproj", "{55F32819-B53D-4908-A4C1-F663871474C3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "credentials", "credentials", "{A79FF417-08E7-4175-8089-5F21054F5BDE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Migrations", "database\SsiCredentialIssuer.Migrations\SsiCredentialIssuer.Migrations.csproj", "{F74763E9-231A-46C9-A160-72863963D17F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "externalservices", "externalservices", "{A37A220C-4242-4FB2-98ED-EF4B602EF6C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wallet.Service", "externalservices\Wallet.Service\Wallet.Service.csproj", "{73705ADF-C348-483D-931C-5CDFAB848596}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "processes", "processes", "{A4745993-CEC3-48E9-9765-0401F5B0516C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processes.Library", "processes\Processes.Library\Processes.Library.csproj", "{1D005706-65BF-4A99-A847-69E656CF7529}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processes.Worker", "processes\Processes.Worker\Processes.Worker.csproj", "{DA615C88-8859-41CD-A696-0BE542117008}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processes.Worker.Library", "processes\Processes.Worker.Library\Processes.Worker.Library.csproj", "{7F6B407F-A698-4568-A711-42FD53F06129}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialProcess.Worker", "processes\CredentialProcess.Worker\CredentialProcess.Worker.csproj", "{30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialProcess.Library", "processes\CredentialProcess.Library\CredentialProcess.Library.csproj", "{28919880-0F47-4D54-B253-E1381984C840}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portal.Service", "externalservices\Portal.Service\Portal.Service.csproj", "{8AF8FC7D-3448-422A-8739-0690AA700DAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Expiry.App", "credentials\SsiCredentialIssuer.Expiry.App\SsiCredentialIssuer.Expiry.App.csproj", "{9F9FFBEE-4256-494F-BEF7-8856145D45EC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Expiry.App.Tests", "..\tests\credentials\SsiCredentialIssuer.Expiry.App.Tests\SsiCredentialIssuer.Expiry.App.Tests.csproj", "{FFB1423F-C41F-46A9-8D5D-115B3BC46682}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processes.Library.Tests", "..\tests\processes\Processes.Library.Tests\Processes.Library.Tests.csproj", "{3AADCF6D-746E-49C8-89DC-96474F70B130}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processes.Worker.Library.Tests", "..\tests\processes\Processes.Worker.Library.Tests\Processes.Worker.Library.Tests.csproj", "{5CEA19ED-04F0-4CC6-8D5D-C54629436EDA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.Service.Tests", "..\tests\issuer\SsiCredentialIssuer.Service.Tests\SsiCredentialIssuer.Service.Tests.csproj", "{D88007E0-603A-49B7-B4E4-AD255B06D252}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portal.Service.Tests", "..\tests\externalservices\Portal.Service.Tests\Portal.Service.Tests.csproj", "{5A0321D9-F716-47AF-AA96-E5AADC0C26EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Shared", "..\tests\Tests.Shared\Tests.Shared.csproj", "{6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wallet.Service.Tests", "..\tests\externalservices\Wallet.Service.Tests\Wallet.Service.Tests.csproj", "{F46B828C-3E95-49D0-A3D2-F5A083F5FE7A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SsiCredentialIssuer.DbAccess.Tests", "..\tests\database\SsiCredentialIssuer.DbAccess.Tests\SsiCredentialIssuer.DbAccess.Tests.csproj", "{3967B3E7-093B-438C-81FE-A7CAE5FFAD31}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialProcess.Worker.Tests", "..\tests\processes\CredentialProcess.Worker.Tests\CredentialProcess.Worker.Tests.csproj", "{2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialProcess.Library.Tests", "..\tests\processes\CredentialProcess.Library.Tests\CredentialProcess.Library.Tests.csproj", "{0A9C65F7-62B6-421F-ADA5-709A1EE10901}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Callback.Service", "externalservices\Callback.Service\Callback.Service.csproj", "{E1821527-A5F9-4D56-BAB0-6F45FE6F3299}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Callback.Service.Tests", "..\tests\externalservices\Callback.Service.Tests\Callback.Service.Tests.csproj", "{61DB2ADF-DBC1-4647-AAD2-A8E992E75B37}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6905B6DF-722B-4882-A2CB-5E6BFD0244F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6905B6DF-722B-4882-A2CB-5E6BFD0244F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6905B6DF-722B-4882-A2CB-5E6BFD0244F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6905B6DF-722B-4882-A2CB-5E6BFD0244F2}.Release|Any CPU.Build.0 = Release|Any CPU + {5627AA4F-A64B-4878-A139-F3ED6734BDEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5627AA4F-A64B-4878-A139-F3ED6734BDEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5627AA4F-A64B-4878-A139-F3ED6734BDEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5627AA4F-A64B-4878-A139-F3ED6734BDEC}.Release|Any CPU.Build.0 = Release|Any CPU + {55F32819-B53D-4908-A4C1-F663871474C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55F32819-B53D-4908-A4C1-F663871474C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55F32819-B53D-4908-A4C1-F663871474C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55F32819-B53D-4908-A4C1-F663871474C3}.Release|Any CPU.Build.0 = Release|Any CPU + {F74763E9-231A-46C9-A160-72863963D17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F74763E9-231A-46C9-A160-72863963D17F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F74763E9-231A-46C9-A160-72863963D17F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F74763E9-231A-46C9-A160-72863963D17F}.Release|Any CPU.Build.0 = Release|Any CPU + {73705ADF-C348-483D-931C-5CDFAB848596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73705ADF-C348-483D-931C-5CDFAB848596}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73705ADF-C348-483D-931C-5CDFAB848596}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73705ADF-C348-483D-931C-5CDFAB848596}.Release|Any CPU.Build.0 = Release|Any CPU + {1D005706-65BF-4A99-A847-69E656CF7529}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D005706-65BF-4A99-A847-69E656CF7529}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D005706-65BF-4A99-A847-69E656CF7529}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D005706-65BF-4A99-A847-69E656CF7529}.Release|Any CPU.Build.0 = Release|Any CPU + {DA615C88-8859-41CD-A696-0BE542117008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA615C88-8859-41CD-A696-0BE542117008}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA615C88-8859-41CD-A696-0BE542117008}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA615C88-8859-41CD-A696-0BE542117008}.Release|Any CPU.Build.0 = Release|Any CPU + {7F6B407F-A698-4568-A711-42FD53F06129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F6B407F-A698-4568-A711-42FD53F06129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F6B407F-A698-4568-A711-42FD53F06129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F6B407F-A698-4568-A711-42FD53F06129}.Release|Any CPU.Build.0 = Release|Any CPU + {30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76}.Release|Any CPU.Build.0 = Release|Any CPU + {28919880-0F47-4D54-B253-E1381984C840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28919880-0F47-4D54-B253-E1381984C840}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28919880-0F47-4D54-B253-E1381984C840}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28919880-0F47-4D54-B253-E1381984C840}.Release|Any CPU.Build.0 = Release|Any CPU + {8AF8FC7D-3448-422A-8739-0690AA700DAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8AF8FC7D-3448-422A-8739-0690AA700DAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AF8FC7D-3448-422A-8739-0690AA700DAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8AF8FC7D-3448-422A-8739-0690AA700DAD}.Release|Any CPU.Build.0 = Release|Any CPU + {9F9FFBEE-4256-494F-BEF7-8856145D45EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F9FFBEE-4256-494F-BEF7-8856145D45EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F9FFBEE-4256-494F-BEF7-8856145D45EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F9FFBEE-4256-494F-BEF7-8856145D45EC}.Release|Any CPU.Build.0 = Release|Any CPU + {FFB1423F-C41F-46A9-8D5D-115B3BC46682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFB1423F-C41F-46A9-8D5D-115B3BC46682}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFB1423F-C41F-46A9-8D5D-115B3BC46682}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFB1423F-C41F-46A9-8D5D-115B3BC46682}.Release|Any CPU.Build.0 = Release|Any CPU + {3AADCF6D-746E-49C8-89DC-96474F70B130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AADCF6D-746E-49C8-89DC-96474F70B130}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AADCF6D-746E-49C8-89DC-96474F70B130}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AADCF6D-746E-49C8-89DC-96474F70B130}.Release|Any CPU.Build.0 = Release|Any CPU + {5CEA19ED-04F0-4CC6-8D5D-C54629436EDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CEA19ED-04F0-4CC6-8D5D-C54629436EDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CEA19ED-04F0-4CC6-8D5D-C54629436EDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CEA19ED-04F0-4CC6-8D5D-C54629436EDA}.Release|Any CPU.Build.0 = Release|Any CPU + {D88007E0-603A-49B7-B4E4-AD255B06D252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D88007E0-603A-49B7-B4E4-AD255B06D252}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D88007E0-603A-49B7-B4E4-AD255B06D252}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D88007E0-603A-49B7-B4E4-AD255B06D252}.Release|Any CPU.Build.0 = Release|Any CPU + {5A0321D9-F716-47AF-AA96-E5AADC0C26EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A0321D9-F716-47AF-AA96-E5AADC0C26EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A0321D9-F716-47AF-AA96-E5AADC0C26EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A0321D9-F716-47AF-AA96-E5AADC0C26EE}.Release|Any CPU.Build.0 = Release|Any CPU + {6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A}.Release|Any CPU.Build.0 = Release|Any CPU + {F46B828C-3E95-49D0-A3D2-F5A083F5FE7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F46B828C-3E95-49D0-A3D2-F5A083F5FE7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F46B828C-3E95-49D0-A3D2-F5A083F5FE7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F46B828C-3E95-49D0-A3D2-F5A083F5FE7A}.Release|Any CPU.Build.0 = Release|Any CPU + {3967B3E7-093B-438C-81FE-A7CAE5FFAD31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3967B3E7-093B-438C-81FE-A7CAE5FFAD31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3967B3E7-093B-438C-81FE-A7CAE5FFAD31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3967B3E7-093B-438C-81FE-A7CAE5FFAD31}.Release|Any CPU.Build.0 = Release|Any CPU + {2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87}.Release|Any CPU.Build.0 = Release|Any CPU + {0A9C65F7-62B6-421F-ADA5-709A1EE10901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A9C65F7-62B6-421F-ADA5-709A1EE10901}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A9C65F7-62B6-421F-ADA5-709A1EE10901}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A9C65F7-62B6-421F-ADA5-709A1EE10901}.Release|Any CPU.Build.0 = Release|Any CPU + {E1821527-A5F9-4D56-BAB0-6F45FE6F3299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1821527-A5F9-4D56-BAB0-6F45FE6F3299}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1821527-A5F9-4D56-BAB0-6F45FE6F3299}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1821527-A5F9-4D56-BAB0-6F45FE6F3299}.Release|Any CPU.Build.0 = Release|Any CPU + {61DB2ADF-DBC1-4647-AAD2-A8E992E75B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61DB2ADF-DBC1-4647-AAD2-A8E992E75B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61DB2ADF-DBC1-4647-AAD2-A8E992E75B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61DB2ADF-DBC1-4647-AAD2-A8E992E75B37}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {6905B6DF-722B-4882-A2CB-5E6BFD0244F2} = {32D0AE23-BFAA-4D65-AF9D-2DF951BA5A3B} + {5627AA4F-A64B-4878-A139-F3ED6734BDEC} = {E9E08CE9-985A-4507-BBD3-9470623986CF} + {55F32819-B53D-4908-A4C1-F663871474C3} = {E9E08CE9-985A-4507-BBD3-9470623986CF} + {F74763E9-231A-46C9-A160-72863963D17F} = {E9E08CE9-985A-4507-BBD3-9470623986CF} + {73705ADF-C348-483D-931C-5CDFAB848596} = {A37A220C-4242-4FB2-98ED-EF4B602EF6C1} + {1D005706-65BF-4A99-A847-69E656CF7529} = {A4745993-CEC3-48E9-9765-0401F5B0516C} + {DA615C88-8859-41CD-A696-0BE542117008} = {A4745993-CEC3-48E9-9765-0401F5B0516C} + {7F6B407F-A698-4568-A711-42FD53F06129} = {A4745993-CEC3-48E9-9765-0401F5B0516C} + {30C2CCFC-6C76-4A68-8CA1-BA6A4EA28E76} = {A4745993-CEC3-48E9-9765-0401F5B0516C} + {28919880-0F47-4D54-B253-E1381984C840} = {A4745993-CEC3-48E9-9765-0401F5B0516C} + {8AF8FC7D-3448-422A-8739-0690AA700DAD} = {A37A220C-4242-4FB2-98ED-EF4B602EF6C1} + {9F9FFBEE-4256-494F-BEF7-8856145D45EC} = {A79FF417-08E7-4175-8089-5F21054F5BDE} + {FFB1423F-C41F-46A9-8D5D-115B3BC46682} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {3AADCF6D-746E-49C8-89DC-96474F70B130} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {5CEA19ED-04F0-4CC6-8D5D-C54629436EDA} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {D88007E0-603A-49B7-B4E4-AD255B06D252} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {5A0321D9-F716-47AF-AA96-E5AADC0C26EE} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {6A1D91F3-B9EC-40CD-9239-1D80FADF8E1A} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {F46B828C-3E95-49D0-A3D2-F5A083F5FE7A} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {3967B3E7-093B-438C-81FE-A7CAE5FFAD31} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {2D42186B-5BC8-4F60-92D3-FD1FF0F5FF87} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {0A9C65F7-62B6-421F-ADA5-709A1EE10901} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + {E1821527-A5F9-4D56-BAB0-6F45FE6F3299} = {A37A220C-4242-4FB2-98ED-EF4B602EF6C1} + {61DB2ADF-DBC1-4647-AAD2-A8E992E75B37} = {A2FC3E0F-5AFE-44FA-909B-B8016DD1EB44} + EndGlobalSection +EndGlobal diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceExtensions.cs b/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceExtensions.cs new file mode 100644 index 00000000..94b691b5 --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceExtensions.cs @@ -0,0 +1,45 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.DependencyInjection; + +/// +/// Extension method to register the expiry check service and dependent services +/// +public static class ExpiryCheckServiceExtensions +{ + /// + /// Adds the expiry check service + /// + /// the services + /// the configuration section to setup the settings + /// the enriched service collection + public static IServiceCollection AddExpiryCheckService(this IServiceCollection services, IConfigurationSection section) + { + services.AddOptions().Bind(section); + services + .AddTransient() + .AddTransient(); + return services; + } +} diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceSettings.cs b/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceSettings.cs new file mode 100644 index 00000000..e544c53c --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/DependencyInjection/ExpiryCheckServiceSettings.cs @@ -0,0 +1,40 @@ +/******************************************************************************** + * 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.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.DependencyInjection; + +/// +/// Settings for the ExpiryCheckService +/// +public class ExpiryCheckServiceSettings +{ + /// + /// Vcs which are older than the given value will be deleted when expired + /// + [Required] + public int ExpiredVcsToDeleteInMonth { get; init; } + + /// + /// Vcs which are older than the given value will be deleted when declined + /// + [Required] + public int InactiveVcsToDeleteInWeeks { get; init; } +} diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs b/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs new file mode 100644 index 00000000..729d7e4a --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/ExpiryCheckService.cs @@ -0,0 +1,196 @@ +/******************************************************************************** + * 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.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +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.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +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 System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App; + +/// +/// Service to delete the pending and inactive documents as well as the depending consents from the database +/// +public class ExpiryCheckService +{ + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly ILogger _logger; + private readonly ExpiryCheckServiceSettings _settings; + + /// + /// Creates a new instance of + /// + /// access to the services + /// the logger + /// The options + public ExpiryCheckService( + IServiceScopeFactory serviceScopeFactory, + ILogger logger, + IOptions options) + { + _serviceScopeFactory = serviceScopeFactory; + _logger = logger; + _settings = options.Value; + } + + /// + /// Handles the + /// + /// Cancellation Token + public async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (!stoppingToken.IsCancellationRequested) + { + try + { + using var processServiceScope = _serviceScopeFactory.CreateScope(); + var repositories = processServiceScope.ServiceProvider.GetRequiredService(); + var dateTimeProvider = processServiceScope.ServiceProvider.GetRequiredService(); + var portalService = processServiceScope.ServiceProvider.GetRequiredService(); + + using var outerLoopScope = _serviceScopeFactory.CreateScope(); + var outerLoopRepositories = outerLoopScope.ServiceProvider.GetRequiredService(); + + var now = dateTimeProvider.OffsetNow; + var companySsiDetailsRepository = repositories.GetInstance(); + var inactiveVcsToDelete = now.AddDays(-(_settings.InactiveVcsToDeleteInWeeks * 7)); + var expiredVcsToDelete = now.AddMonths(-_settings.ExpiredVcsToDeleteInMonth); + + var credentials = outerLoopRepositories.GetInstance() + .GetExpiryData(now, inactiveVcsToDelete, expiredVcsToDelete); + await foreach (var credential in credentials.WithCancellation(stoppingToken).ConfigureAwait(false)) + { + await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService, + stoppingToken); + } + } + catch (Exception ex) + { + Environment.ExitCode = 1; + _logger.LogError("Verified Credential expiry check failed with error: {Errors}", ex.Message); + } + } + } + + private static async Task ProcessCredentials( + CredentialExpiryData data, + ICompanySsiDetailsRepository companySsiDetailsRepository, + IIssuerRepositories repositories, + IPortalService portalService, + CancellationToken cancellationToken) + { + if (data.ScheduleData.IsVcToDelete) + { + companySsiDetailsRepository.RemoveSsiDetail(data.Id); + } + else if (data.ScheduleData.IsVcToDecline) + { + await HandleDecline(data, companySsiDetailsRepository, portalService, cancellationToken).ConfigureAwait(false); + } + else + { + await HandleNotification(data, companySsiDetailsRepository, portalService, cancellationToken).ConfigureAwait(false); + } + + // Saving here to make sure the each credential is handled by there own + await repositories.SaveAsync().ConfigureAwait(false); + } + + private static async ValueTask HandleDecline( + CredentialExpiryData data, + ICompanySsiDetailsRepository companySsiDetailsRepository, + IPortalService portalService, + CancellationToken cancellationToken) + { + 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 => + { + c.CompanySsiDetailStatusId = data.CompanySsiDetailStatusId; + }, + c => + { + c.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + }); + + 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( + CredentialExpiryData data, + ICompanySsiDetailsRepository companySsiDetailsRepository, + IPortalService portalService, + CancellationToken cancellationToken) + { + var newExpiryCheckTypeId = data.ScheduleData switch + { + { IsOneDayNotification: true } => ExpiryCheckTypeId.ONE_DAY, + { IsTwoWeeksNotification: true } => ExpiryCheckTypeId.TWO_WEEKS, + { IsOneMonthNotification: true } => ExpiryCheckTypeId.ONE_MONTH, + _ => throw new UnexpectedConditionException("one of IsVcToDelete, IsOneDayNotification, IsTwoWeeksNotification, IsOneMonthNotification, IsVcToDecline is expected to be true") + }; + + companySsiDetailsRepository.AttachAndModifyCompanySsiDetails( + data.Id, + csd => + { + csd.ExpiryCheckTypeId = data.ExpiryCheckTypeId; + }, + csd => + { + csd.ExpiryCheckTypeId = newExpiryCheckTypeId; + }); + + var content = JsonSerializer.Serialize(new + { + Type = data.VerifiedCredentialTypeId, + ExpiryDate = data.ExpiryDate?.ToString("O") ?? throw new ConflictException("Expiry Date must be set here"), + Version = data.DetailVersion, + CredentialId = data.Id, + ExpiryCheckTypeId = newExpiryCheckTypeId + }, 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 + { + { "typeId", typeValue }, + { "version", data.DetailVersion ?? "no version" }, + { "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/credentials/SsiCredentialIssuer.Expiry.App/Program.cs b/src/credentials/SsiCredentialIssuer.Expiry.App/Program.cs new file mode 100644 index 00000000..b6bdd269 --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/Program.cs @@ -0,0 +1,71 @@ +/******************************************************************************** + * 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.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library.DependencyInjection; +using Serilog; + +LoggingExtensions.EnsureInitialized(); +Log.Information("Building worker"); +try +{ + var host = Host + .CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services + .AddTransient() + .AddProcessIdentity(hostContext.Configuration.GetSection("ProcessIdentity")) + .AddExpiryCheckService(hostContext.Configuration.GetSection("Expiry")) + .AddPortalService(hostContext.Configuration.GetSection("Portal")) + .AddIssuerRepositories(hostContext.Configuration); + }) + .AddLogging() + .Build(); + Log.Information("Building worker completed"); + + using var tokenSource = new CancellationTokenSource(); + Console.CancelKeyPress += (s, e) => + { + Log.Information("Canceling..."); + tokenSource.Cancel(); + e.Cancel = true; + }; + + Log.Information("Start processing"); + var workerInstance = host.Services.GetRequiredService(); + await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(false); + Log.Information("Execution finished shutting down"); +} +catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) +{ + Log.Fatal(ex, "Unhandled exception"); +} +finally +{ + Log.Information("Server Shutting down"); + Log.CloseAndFlush(); +} diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/Properties/launchSettings.json b/src/credentials/SsiCredentialIssuer.Expiry.App/Properties/launchSettings.json new file mode 100644 index 00000000..8480e0aa --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "SsiCredentialIssuer.Expiry.App": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/SsiCredentialIssuer.Expiry.App.csproj b/src/credentials/SsiCredentialIssuer.Expiry.App/SsiCredentialIssuer.Expiry.App.csproj new file mode 100644 index 00000000..45ffe379 --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/SsiCredentialIssuer.Expiry.App.csproj @@ -0,0 +1,80 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App + Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App + net7.0 + enable + enable + 37a22764-0a60-4c2e-a692-d59c4f14abbf + Exe + Linux + ..\..\.. + True + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + Always + + + + + + Program.cs + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + diff --git a/src/credentials/SsiCredentialIssuer.Expiry.App/appsettings.json b/src/credentials/SsiCredentialIssuer.Expiry.App/appsettings.json new file mode 100644 index 00000000..f699a71d --- /dev/null +++ b/src/credentials/SsiCredentialIssuer.Expiry.App/appsettings.json @@ -0,0 +1,33 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Org.Eclipse.TractusX.SsiCredentialIssuer": "Information" + } + }, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "FromLogContext" + ], + "Properties": { + "Application": "Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App" + } + }, + "ConnectionStrings": { + "PortalDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" + }, + "Expiry": { + "ExpiredVcsToDeleteInMonth": 12, + "InactiveVcsToDeleteInWeeks": 12 + }, + "ProcessIdentity": { + "ProcessUserId": "" + } +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Extensions/MediaTypeIdExtensions.cs b/src/database/SsiCredentialIssuer.DbAccess/Extensions/MediaTypeIdExtensions.cs new file mode 100644 index 00000000..c96345d8 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Extensions/MediaTypeIdExtensions.cs @@ -0,0 +1,65 @@ +/******************************************************************************** + * 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.Entities.Enums; +using System.Net.Mime; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Extensions; + +public static class MediaTypeIdExtensions +{ + public static string MapToMediaType(this MediaTypeId mediaTypeId) + { + return mediaTypeId switch + { + MediaTypeId.JPEG => MediaTypeNames.Image.Jpeg, + MediaTypeId.GIF => MediaTypeNames.Image.Gif, + MediaTypeId.PNG => "image/png", + MediaTypeId.SVG => "image/svg+xml", + MediaTypeId.TIFF => MediaTypeNames.Image.Tiff, + MediaTypeId.PDF => MediaTypeNames.Application.Pdf, + MediaTypeId.JSON => MediaTypeNames.Application.Json, + MediaTypeId.PEM => "application/x-pem-file", + MediaTypeId.CA_CERT => "application/x-x509-ca-cert", + MediaTypeId.PKX_CER => "application/pkix-cert", + MediaTypeId.OCTET => MediaTypeNames.Application.Octet, + _ => throw new ConflictException($"document mediatype {mediaTypeId} is not supported") + }; + } + + public static MediaTypeId ParseMediaTypeId(this string mediaType) + { + return mediaType.ToLower() switch + { + MediaTypeNames.Image.Jpeg => MediaTypeId.JPEG, + "image/png" => MediaTypeId.PNG, + MediaTypeNames.Image.Gif => MediaTypeId.GIF, + "image/svg+xml" => MediaTypeId.SVG, + MediaTypeNames.Image.Tiff => MediaTypeId.TIFF, + MediaTypeNames.Application.Pdf => MediaTypeId.PDF, + MediaTypeNames.Application.Json => MediaTypeId.JSON, + "application/x-pem-file" => MediaTypeId.PEM, + "application/x-x509-ca-cert" => MediaTypeId.CA_CERT, + "application/pkix-cert" => MediaTypeId.PKX_CER, + MediaTypeNames.Application.Octet => MediaTypeId.OCTET, + _ => throw new UnsupportedMediaTypeException($"mediaType '{mediaType}' is not supported") + }; + } +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/IIssuerRepositories.cs b/src/database/SsiCredentialIssuer.DbAccess/IIssuerRepositories.cs new file mode 100644 index 00000000..e0f19e3e --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/IIssuerRepositories.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; + +public interface IIssuerRepositories +{ + /// + /// Attaches the given Entity to the database + /// + /// the entity that should be attached to the database + /// + /// Type of the entity + /// Returns the attached entity + TEntity Attach(TEntity entity, Action? setOptionalParameters = null) + where TEntity : class; + + public T GetInstance(); + + public Task SaveAsync(); + void Clear(); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositories.cs b/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositories.cs new file mode 100644 index 00000000..20699e59 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositories.cs @@ -0,0 +1,78 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; + +public class IssuerRepositories : IIssuerRepositories +{ + private readonly IssuerDbContext _dbContext; + + private static readonly IReadOnlyDictionary> Types = new Dictionary> { + { typeof(ICompanySsiDetailsRepository), context => new CompanySsiDetailsRepository(context) }, + { typeof(ICredentialRepository), context => new CredentialRepository(context) }, + { typeof(IDocumentRepository), context => new DocumentRepository(context) }, + { typeof(IProcessStepRepository), context => new ProcessStepRepository(context) }, + }.ToImmutableDictionary(); + + public IssuerRepositories(IssuerDbContext dbContext) + { + _dbContext = dbContext; + } + + public RepositoryType GetInstance() + { + object? repository = default; + + if (Types.TryGetValue(typeof(RepositoryType), out var createFunc)) + { + repository = createFunc(_dbContext); + } + + return (RepositoryType)(repository ?? throw new ArgumentException($"unexpected type {typeof(RepositoryType).Name}", nameof(RepositoryType))); + } + + /// + public TEntity Attach(TEntity entity, Action? setOptionalParameters = null) where TEntity : class + { + var attachedEntity = _dbContext.Attach(entity).Entity; + setOptionalParameters?.Invoke(attachedEntity); + + return attachedEntity; + } + + public Task SaveAsync() + { + try + { + return _dbContext.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException e) + { + throw new ConflictException("while processing a concurrent update was saved to the database (reason could also be data to be deleted is no longer existing)", e); + } + } + + public void Clear() => _dbContext.ChangeTracker.Clear(); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositoriesStartupServiceExtensions.cs b/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositoriesStartupServiceExtensions.cs new file mode 100644 index 00000000..99ae8871 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/IssuerRepositoriesStartupServiceExtensions.cs @@ -0,0 +1,40 @@ +/******************************************************************************** + * 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; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.DependencyInjection; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; + +public static class IssuerRepositoriesStartupServiceExtensions +{ + public static IServiceCollection AddIssuerRepositories(this IServiceCollection services, IConfiguration configuration) + { + services.AddScoped() + .AddDbAuditing() + .AddDbContext(o => o + .UseNpgsql(configuration.GetConnectionString("IssuerDb"))) + .AddHealthChecks() + .AddDbContextCheck("IssuerDbContext", tags: new[] { "issuerdb" }); + return services; + } +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/CompanySsiDetailSorting.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/CompanySsiDetailSorting.cs new file mode 100644 index 00000000..b1fa5105 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/CompanySsiDetailSorting.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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.Entities; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; + +/// +/// Possible sorting options for the pagination +/// +public enum CompanySsiDetailSorting +{ + /// + /// Ascending by bpnl + /// + BpnlAsc = 1, + + /// + /// Descending by bpnl + /// + BpnlDesc = 2, +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialDetailData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialDetailData.cs new file mode 100644 index 00000000..e04c59d8 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialDetailData.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record CredentialDetailData +( + Guid CredentialDetailId, + string Bpnl, + VerifiedCredentialTypeId CredentialType, + string? UseCase, + CompanySsiDetailStatusId ParticipantStatus, + DateTimeOffset? ExpiryDate, + IEnumerable Documents, + ExternalTypeDetailData? ExternalTypeDetail +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialExpiryData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialExpiryData.cs new file mode 100644 index 00000000..c541da4e --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/CredentialExpiryData.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record CredentialExpiryData( + Guid Id, + Guid RequesterId, + DateTimeOffset? ExpiryDate, + ExpiryCheckTypeId? ExpiryCheckTypeId, + string? DetailVersion, + string Bpnl, + CompanySsiDetailStatusId CompanySsiDetailStatusId, + VerifiedCredentialTypeId VerifiedCredentialTypeId, + CredentialScheduleData ScheduleData); + +public record CredentialScheduleData( + bool IsVcToDelete, + bool IsOneDayNotification, + bool IsTwoWeeksNotification, + bool IsOneMonthNotification, + bool IsVcToDecline +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.cs new file mode 100644 index 00000000..5f2202d4 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/DocumentData.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.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; + +public record DocumentData +( + Guid DocumentId, + string DocumentName, + DocumentTypeId argDocumentTypeId); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/EncryptionTransformationData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/EncryptionTransformationData.cs new file mode 100644 index 00000000..d343eba7 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/EncryptionTransformationData.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; + +public record EncryptionTransformationData( + byte[]? Secret, + byte[]? InitializationVector, + int? EncryptionMode +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/ExternalTypeDetailData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/ExternalTypeDetailData.cs new file mode 100644 index 00000000..6765c6b2 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/ExternalTypeDetailData.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record ExternalTypeDetailData( + Guid Id, + VerifiedCredentialExternalTypeId VerifiedCredentialExternalTypeId, + string? Version, + string? Template, + DateTimeOffset? ValidFrom, + DateTimeOffset? Expiry +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs new file mode 100644 index 00000000..2180ec38 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiApprovalData.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record SsiApprovalData( + CompanySsiDetailStatusId Status, + VerifiedCredentialTypeId Type, + Guid? ProcessId, + VerifiedCredentialTypeKindId? Kind, + string? Bpn, + DetailData? DetailData +); + +public record DetailData( + VerifiedCredentialExternalTypeId VerifiedCredentialExternalTypeId, + string? Template, + string? Version, + DateTimeOffset ExpiryDate +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/SsiCertificateTransferData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiCertificateTransferData.cs new file mode 100644 index 00000000..9b92cb09 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/SsiCertificateTransferData.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record SsiCertificateTransferData +( + VerifiedCredentialTypeId CredentialType, + IEnumerable Credentials +); + +public record SsiCertificateExternalTypeDetailTransferData +( + ExternalTypeDetailData ExternalDetailData, + IEnumerable SsiDetailData +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/TechnicalUserDetails.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/TechnicalUserDetails.cs new file mode 100644 index 00000000..9250b3bf --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/TechnicalUserDetails.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record TechnicalUserDetails( + [property: JsonPropertyName("walletUrl")] string WalletUrl, + [property: JsonPropertyName("clientId")] string ClientId, + [property: JsonPropertyName("clientSecret")] string ClientSecret +); + +public record HolderWalletData( + [property: JsonPropertyName("walletUrl")] string? WalletUrl, + [property: JsonPropertyName("clientId")] string? ClientId +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Models/UseCaseParticipationTransferData.cs b/src/database/SsiCredentialIssuer.DbAccess/Models/UseCaseParticipationTransferData.cs new file mode 100644 index 00000000..18f61106 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Models/UseCaseParticipationTransferData.cs @@ -0,0 +1,44 @@ +/******************************************************************************** + * 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.DBAccess.Models; + +public record UseCaseParticipationTransferData +( + string? UseCase, + string? Description, + VerifiedCredentialTypeId CredentialType, + IEnumerable VerifiedCredentials +); + +public record CompanySsiExternalTypeDetailTransferData +( + ExternalTypeDetailData ExternalDetailData, + IEnumerable SsiDetailData +); + +public record CompanySsiDetailTransferData +( + Guid CredentialId, + CompanySsiDetailStatusId ParticipationStatus, + DateTimeOffset? ExpiryDate, + IEnumerable Documents +); diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs new file mode 100644 index 00000000..0f230919 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CompanySsiDetailsRepository.cs @@ -0,0 +1,289 @@ +/******************************************************************************** + * 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; +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; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +public class CompanySsiDetailsRepository : ICompanySsiDetailsRepository +{ + private readonly IssuerDbContext _context; + + /// + /// Constructor. + /// + /// DB context. + public CompanySsiDetailsRepository(IssuerDbContext dbContext) + { + _context = dbContext; + } + + /// + public IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry) => + _context.VerifiedCredentialTypes + .Where(t => t.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId == VerifiedCredentialTypeKindId.FRAMEWORK) + .Select(t => new + { + t.VerifiedCredentialTypeAssignedUseCase!.UseCase, + TypeId = t.Id, + ExternalTypeDetails = t.VerifiedCredentialTypeAssignedExternalType!.VerifiedCredentialExternalType!.VerifiedCredentialExternalTypeDetailVersions + }) + .Select(x => new UseCaseParticipationTransferData( + x.UseCase!.Name, + x.UseCase.Shortname, + x.TypeId, + x.ExternalTypeDetails + .Select(e => + new CompanySsiExternalTypeDetailTransferData( + new ExternalTypeDetailData( + e.Id, + e.VerifiedCredentialExternalTypeId, + e.Version, + e.Template, + e.ValidFrom, + e.Expiry), + e.CompanySsiDetails + .Where(ssi => + ssi.Bpnl == bpnl && + ssi.VerifiedCredentialTypeId == x.TypeId && + ssi.CompanySsiDetailStatusId != CompanySsiDetailStatusId.INACTIVE && + ssi.VerifiedCredentialExternalTypeDetailVersionId == e.Id && + ssi.ExpiryDate > minExpiry) + .Select(ssi => + new CompanySsiDetailTransferData( + ssi.Id, + ssi.CompanySsiDetailStatusId, + ssi.ExpiryDate, + ssi.Documents.Select(d => new DocumentData( + d.Id, + d.DocumentName, + d.DocumentTypeId)))) + .Take(2) + )) + )) + .ToAsyncEnumerable(); + + /// + public IAsyncEnumerable GetSsiCertificates(string bpnl, DateTimeOffset minExpiry) => + _context.VerifiedCredentialTypes + .Where(types => types.VerifiedCredentialTypeAssignedKind != null && types.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId != VerifiedCredentialTypeKindId.FRAMEWORK) + .Select(t => new + { + TypeId = t.Id, + ExternalTypeDetails = t.VerifiedCredentialTypeAssignedExternalType!.VerifiedCredentialExternalType!.VerifiedCredentialExternalTypeDetailVersions + }) + .Select(x => new SsiCertificateTransferData( + x.TypeId, + x.ExternalTypeDetails + .Select(e => + new SsiCertificateExternalTypeDetailTransferData( + new ExternalTypeDetailData( + e.Id, + e.VerifiedCredentialExternalTypeId, + e.Version, + e.Template, + e.ValidFrom, + e.Expiry), + e.CompanySsiDetails + .Where(ssi => + ssi.Bpnl == bpnl && + ssi.VerifiedCredentialTypeId == x.TypeId && + ssi.CompanySsiDetailStatusId != CompanySsiDetailStatusId.INACTIVE && + ssi.ExpiryDate > minExpiry) + .Select(ssi => + new CompanySsiDetailTransferData( + ssi.Id, + ssi.CompanySsiDetailStatusId, + ssi.ExpiryDate, + ssi.Documents.Select(d => new DocumentData( + d.Id, + d.DocumentName, + d.DocumentTypeId)))) + .Take(2) + )) + )) + .ToAsyncEnumerable(); + + /// + public CompanySsiDetail CreateSsiDetails(string bpnl, VerifiedCredentialTypeId verifiedCredentialTypeId, CompanySsiDetailStatusId companySsiDetailStatusId, string issuerBpn, Guid userId, Action? setOptionalFields) + { + var detail = new CompanySsiDetail(Guid.NewGuid(), bpnl, verifiedCredentialTypeId, companySsiDetailStatusId, issuerBpn, userId, DateTimeOffset.UtcNow); + setOptionalFields?.Invoke(detail); + return _context.CompanySsiDetails.Add(detail).Entity; + } + + /// + public Task CheckSsiDetailsExistsForCompany(string bpnl, VerifiedCredentialTypeId verifiedCredentialTypeId, VerifiedCredentialTypeKindId kindId, Guid? verifiedCredentialExternalTypeUseCaseDetailId) => + _context.CompanySsiDetails + .AnyAsync(x => + x.Bpnl == bpnl && + x.VerifiedCredentialTypeId == verifiedCredentialTypeId && + x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId == kindId && + x.CompanySsiDetailStatusId != CompanySsiDetailStatusId.INACTIVE && + (verifiedCredentialExternalTypeUseCaseDetailId == null || x.VerifiedCredentialExternalTypeDetailVersionId == verifiedCredentialExternalTypeUseCaseDetailId)); + + /// + public Task<(bool Exists, string? Version, string? Template, IEnumerable UseCase, 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>( + true, + x.Version, + x.Template, + x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Select(y => y.VerifiedCredentialType!.VerifiedCredentialTypeAssignedUseCase!.UseCase!.Shortname), + x.Expiry)) + .SingleOrDefaultAsync(); + + /// + public Task<(bool Exists, IEnumerable DetailVersionIds)> CheckSsiCertificateType(VerifiedCredentialTypeId credentialTypeId) => + _context.VerifiedCredentialTypeAssignedKinds + .Where(x => + x.VerifiedCredentialTypeId == credentialTypeId && + x.VerifiedCredentialTypeKindId != VerifiedCredentialTypeKindId.FRAMEWORK) + .Select(x => new ValueTuple>( + true, + x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedExternalType!.VerifiedCredentialExternalType!.VerifiedCredentialExternalTypeDetailVersions.Select(v => v.Id) + )) + .SingleOrDefaultAsync(); + + /// + public IQueryable GetAllCredentialDetails(CompanySsiDetailStatusId? companySsiDetailStatusId, VerifiedCredentialTypeId? credentialTypeId) => + _context.CompanySsiDetails.AsNoTracking() + .Where(c => + (!companySsiDetailStatusId.HasValue || c.CompanySsiDetailStatusId == companySsiDetailStatusId.Value) && + (!credentialTypeId.HasValue || c.VerifiedCredentialTypeId == credentialTypeId)); + + /// + public Task<(bool exists, SsiApprovalData data)> GetSsiApprovalData(Guid credentialId) => + _context.CompanySsiDetails + .Where(x => x.Id == credentialId) + .Select(x => new ValueTuple( + true, + new SsiApprovalData( + x.CompanySsiDetailStatusId, + x.VerifiedCredentialTypeId, + x.ProcessId, + x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind == null ? null : x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId, + x.Bpnl, + x.VerifiedCredentialExternalTypeDetailVersion == null ? + null : + new DetailData( + x.VerifiedCredentialExternalTypeDetailVersion!.VerifiedCredentialExternalTypeId, + x.VerifiedCredentialExternalTypeDetailVersion.Template, + x.VerifiedCredentialExternalTypeDetailVersion.Version, + x.VerifiedCredentialExternalTypeDetailVersion.Expiry + ) + ) + )) + .SingleOrDefaultAsync(); + + /// + public Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, Guid? ProcessId, IEnumerable ProcessStepIds)> GetSsiRejectionData(Guid credentialId) => + _context.CompanySsiDetails + .Where(x => x.Id == credentialId) + .Select(x => new ValueTuple>( + true, + x.CompanySsiDetailStatusId, + x.VerifiedCredentialTypeId, + x.ProcessId, + x.Process!.ProcessSteps.Where(ps => ps.ProcessStepStatusId == ProcessStepStatusId.TODO).Select(p => p.Id) + )) + .SingleOrDefaultAsync(); + + /// + public void AttachAndModifyCompanySsiDetails(Guid id, Action? initialize, Action updateFields) + { + var entity = new CompanySsiDetail(id, null!, default, default, null!, Guid.Empty, DateTimeOffset.MinValue); + initialize?.Invoke(entity); + _context.Attach(entity); + updateFields.Invoke(entity); + } + + /// + public IAsyncEnumerable GetCertificateTypes(string bpnl) => + _context.VerifiedCredentialTypes + .Where(x => + x.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId != VerifiedCredentialTypeKindId.FRAMEWORK && + !x.CompanySsiDetails.Any(ssi => + ssi.Bpnl == bpnl && + (ssi.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING || ssi.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE))) + .Select(x => x.Id) + .ToAsyncEnumerable(); + + public IAsyncEnumerable GetExpiryData(DateTimeOffset now, DateTimeOffset inactiveVcsToDelete, DateTimeOffset expiredVcsToDelete) + { + var oneDay = now.AddDays(1); + var twoWeeks = now.AddDays(14); + var oneMonth = now.AddMonths(2); + + return _context.CompanySsiDetails + .Select(x => new + { + Details = x, + IsVcToDecline = x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING && x.VerifiedCredentialExternalTypeDetailVersion!.Expiry < now, + IsVcToDelete = x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE && x.DateCreated < inactiveVcsToDelete || (x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE || x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE) && x.ExpiryDate < expiredVcsToDelete, + IsOneDayNotification = x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && x.ExpiryDate <= oneDay && (x.ExpiryCheckTypeId == ExpiryCheckTypeId.TWO_WEEKS || x.ExpiryCheckTypeId == ExpiryCheckTypeId.ONE_MONTH || x.ExpiryCheckTypeId == null), + IsTwoWeeksNotification = x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && x.ExpiryDate > oneDay && x.ExpiryDate <= twoWeeks && (x.ExpiryCheckTypeId == ExpiryCheckTypeId.ONE_MONTH || x.ExpiryCheckTypeId == null), + IsOneMonthNotification = x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && x.ExpiryDate > twoWeeks && x.ExpiryDate <= oneMonth && x.ExpiryCheckTypeId == null + }) + .Where(x => x.IsVcToDecline || x.IsVcToDelete || x.IsOneDayNotification || x.IsTwoWeeksNotification || x.IsOneMonthNotification) + .Select(x => new CredentialExpiryData( + x.Details.Id, + x.Details.CreatorUserId, + x.Details.ExpiryDate, + x.Details.ExpiryCheckTypeId, + x.Details.VerifiedCredentialExternalTypeDetailVersion!.Version, + x.Details.Bpnl, + x.Details.CompanySsiDetailStatusId, + x.Details.VerifiedCredentialTypeId, + new CredentialScheduleData( + x.IsVcToDelete, + x.IsOneDayNotification, + x.IsTwoWeeksNotification, + x.IsOneMonthNotification, + x.IsVcToDecline + ))) + .ToAsyncEnumerable(); + } + + public void RemoveSsiDetail(Guid companySsiDetailId) => + _context.CompanySsiDetails.Remove(new CompanySsiDetail(companySsiDetailId, null!, default, default, null!, Guid.Empty, DateTimeOffset.MinValue)); + + public void CreateProcessData(Guid companySsiDetailId, JsonDocument schema, VerifiedCredentialTypeKindId credentialTypeKindId, Action? setOptionalFields) + { + var companySsiDetailData = new CompanySsiProcessData(companySsiDetailId, schema, credentialTypeKindId); + _context.CompanySsiProcessData.Add(companySsiDetailData); + setOptionalFields?.Invoke(companySsiDetailData); + } + + public void AttachAndModifyProcessData(Guid companySsiDetailId, Action? initialize, Action setOptionalFields) + { + var companySsiDetailData = new CompanySsiProcessData(companySsiDetailId, null!, default); + initialize?.Invoke(companySsiDetailData); + _context.CompanySsiProcessData.Attach(companySsiDetailData); + setOptionalFields(companySsiDetailData); + } +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs new file mode 100644 index 00000000..719103c5 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/CredentialRepository.cs @@ -0,0 +1,79 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +public class CredentialRepository : ICredentialRepository +{ + private readonly IssuerDbContext _dbContext; + + public CredentialRepository(IssuerDbContext dbContext) + { + _dbContext = dbContext; + } + + public Task GetWalletCredentialId(Guid credentialId) => + _dbContext.CompanySsiDetails.Where(x => x.Id == credentialId) + .Select(x => x.ExternalCredentialId) + .SingleOrDefaultAsync(); + + public Task<(HolderWalletData HolderWalletData, string? Credential, EncryptionTransformationData EncryptionInformation, string? CallbackUrl)> GetCredentialData(Guid credentialId) => + _dbContext.CompanySsiDetails + .Where(x => x.Id == credentialId) + .Select(x => new ValueTuple( + new HolderWalletData(x.CompanySsiProcessData!.HolderWalletUrl, x.CompanySsiProcessData.ClientId), + x.Credential, + new EncryptionTransformationData(x.CompanySsiProcessData!.ClientSecret, x.CompanySsiProcessData.InitializationVector, x.CompanySsiProcessData.EncryptionMode), + x.CompanySsiProcessData!.CallbackUrl)) + .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid CredentialId)> GetDataForProcessId(Guid processId) => + _dbContext.CompanySsiDetails + .Where(c => c.ProcessId == processId) + .Select(c => new ValueTuple(true, c.Id)) + .SingleOrDefaultAsync(); + + public Task<(VerifiedCredentialTypeKindId CredentialTypeKindId, JsonDocument Schema)> GetCredentialStorageInformationById(Guid credentialId) => + _dbContext.CompanySsiDetails + .Where(c => c.Id == credentialId) + .Select(c => new ValueTuple(c.CompanySsiProcessData!.CredentialTypeKindId, c.CompanySsiProcessData.Schema)) + .SingleOrDefaultAsync(); + + public Task<(Guid? ExternalCredentialId, VerifiedCredentialTypeKindId KindId, bool HasEncryptionInformation, string? CallbackUrl)> GetExternalCredentialAndKindId(Guid credentialId) => + _dbContext.CompanySsiDetails + .Where(c => c.Id == credentialId) + .Select(c => new ValueTuple( + c.ExternalCredentialId, + c.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId, + c.CompanySsiProcessData!.ClientSecret != null && c.CompanySsiProcessData.InitializationVector != null && c.CompanySsiProcessData.EncryptionMode != null, + c.CompanySsiProcessData!.CallbackUrl)) + .SingleOrDefaultAsync(); + + public Task<(string Bpn, string? CallbackUrl)> GetCallbackUrl(Guid credentialId) => + _dbContext.CompanySsiProcessData + .Where(x => x.CompanySsiDetailId == credentialId) + .Select(x => new ValueTuple(x.CompanySsiDetail!.Bpnl, x.CallbackUrl)) + .SingleOrDefaultAsync(); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs new file mode 100644 index 00000000..b6bf1b6d --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/DocumentRepository.cs @@ -0,0 +1,63 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +/// Implementation of accessing database with EF Core. +public class DocumentRepository : IDocumentRepository +{ + private readonly IssuerDbContext _dbContext; + + /// + /// Constructor. + /// + /// PortalDb context. + public DocumentRepository(IssuerDbContext dbContext) + { + this._dbContext = dbContext; + } + + /// + public Document CreateDocument(string documentName, byte[] documentContent, byte[] hash, MediaTypeId mediaTypeId, DocumentTypeId documentTypeId, Action? setupOptionalFields) + { + var document = new Document( + Guid.NewGuid(), + documentContent, + hash, + documentName, + mediaTypeId, + DateTimeOffset.UtcNow, + DocumentStatusId.ACTIVE, + documentTypeId); + + setupOptionalFields?.Invoke(document); + return _dbContext.Documents.Add(document).Entity; + } + + /// + public void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDetailId) + { + var document = new CompanySsiDetailAssignedDocument(documentId, companySsiDetailId); + _dbContext.CompanySsiDetailAssignedDocuments.Add(document); + } +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs new file mode 100644 index 00000000..225a51e0 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICompanySsiDetailsRepository.cs @@ -0,0 +1,98 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +public interface ICompanySsiDetailsRepository +{ + /// + /// Gets the company credential details for the given company id + /// + /// Bpnl of the company + /// The minimum datetime the expiry date should have + /// AsyncEnumerable of UseCaseParticipation + IAsyncEnumerable GetUseCaseParticipationForCompany(string bpnl, DateTimeOffset minExpiry); + + /// + /// Gets the company credential details for the given company id + /// + /// Bpnl of the company + /// The minimum datetime the expiry date should have + /// AsyncEnumerable of SsiCertificateData + IAsyncEnumerable GetSsiCertificates(string bpnl, DateTimeOffset minExpiry); + + /// + /// Creates the credential details + /// + /// Id of the company + /// Id of the credential types + /// id of detail status + /// bpn of the credential issuer + /// Id of the creator + /// sets the optional fields + /// The created entity + CompanySsiDetail CreateSsiDetails(string bpnl, VerifiedCredentialTypeId verifiedCredentialTypeId, CompanySsiDetailStatusId companySsiDetailStatusId, string issuerBpn, Guid userId, Action? setOptionalFields); + + /// + /// Checks whether the credential details are already exists for the company and the given version + /// + /// Bpnl of the company + /// Id of the verifiedCredentialType + /// Id of the credentialTypeKind + /// Id of the verifiedCredentialExternalType Detail Id + /// true if the details already exists, otherwise false + Task CheckSsiDetailsExistsForCompany(string bpnl, VerifiedCredentialTypeId verifiedCredentialTypeId, VerifiedCredentialTypeKindId kindId, Guid? verifiedCredentialExternalTypeUseCaseDetailId); + + /// + /// Checks whether the given externalTypeDetail exists and returns the CredentialTypeId + /// + /// 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); + + /// + /// Checks whether the given credentialTypeId is a Certificate + /// + /// Id of the credentialTypeId + /// true if the tpye is a certificate, otherwise false + Task<(bool Exists, IEnumerable DetailVersionIds)> CheckSsiCertificateType(VerifiedCredentialTypeId credentialTypeId); + + /// + /// Gets all credential details + /// + /// The status of the details + /// OPTIONAL: The type of the credential that should be returned + /// Returns data to create the pagination + IQueryable GetAllCredentialDetails(CompanySsiDetailStatusId? companySsiDetailStatusId, VerifiedCredentialTypeId? credentialTypeId); + + Task<(bool exists, SsiApprovalData data)> GetSsiApprovalData(Guid credentialId); + Task<(bool Exists, CompanySsiDetailStatusId Status, VerifiedCredentialTypeId Type, Guid? ProcessId, IEnumerable ProcessStepIds)> GetSsiRejectionData(Guid credentialId); + void AttachAndModifyCompanySsiDetails(Guid id, Action? initialize, Action updateFields); + IAsyncEnumerable GetCertificateTypes(string bpnl); + IAsyncEnumerable GetExpiryData(DateTimeOffset now, DateTimeOffset inactiveVcsToDelete, DateTimeOffset expiredVcsToDelete); + void RemoveSsiDetail(Guid companySsiDetailId); + void CreateProcessData(Guid companySsiDetailId, JsonDocument schema, VerifiedCredentialTypeKindId credentialTypeKindId, Action? setOptionalFields); + void AttachAndModifyProcessData(Guid companySsiDetailId, Action? initialize, Action setOptionalFields); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs new file mode 100644 index 00000000..3902ad2e --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ICredentialRepository.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +public interface ICredentialRepository +{ + Task GetWalletCredentialId(Guid credentialId); + Task<(HolderWalletData HolderWalletData, string? Credential, EncryptionTransformationData EncryptionInformation, string? CallbackUrl)> GetCredentialData(Guid credentialId); + Task<(bool Exists, Guid CredentialId)> GetDataForProcessId(Guid processId); + 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); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs new file mode 100644 index 00000000..5b5c105b --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IDocumentRepository.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +/// +/// Repository for writing documents on persistence layer. +/// +public interface IDocumentRepository +{ + /// + /// Creates a document in the persistence layer. + /// + /// The documents name + /// The document itself + /// Hash of the document + /// The documents mediaType + /// the document type id + /// Action to setup the additional fields + /// Returns the created document + Document CreateDocument(string documentName, byte[] documentContent, byte[] hash, MediaTypeId mediaTypeId, DocumentTypeId documentTypeId, Action? setupOptionalFields); + + void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDetailId); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/IProcessStepRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IProcessStepRepository.cs new file mode 100644 index 00000000..3e1419f9 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/IProcessStepRepository.cs @@ -0,0 +1,37 @@ +/******************************************************************************** + * 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.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +/// +/// Repository for accessing and creating processSteps on persistence layer. +/// +public interface IProcessStepRepository +{ + Process CreateProcess(ProcessTypeId processTypeId); + ProcessStep CreateProcessStep(ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid processId); + IEnumerable CreateProcessStepRange(IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> processStepTypeStatus); + void AttachAndModifyProcessStep(Guid processStepId, Action? initialize, Action modify); + void AttachAndModifyProcessSteps(IEnumerable<(Guid ProcessStepId, Action? Initialize, Action Modify)> processStepIdsInitializeModifyData); + IAsyncEnumerable GetActiveProcesses(IEnumerable processTypeIds, IEnumerable processStepTypeIds, DateTimeOffset lockExpiryDate); + IAsyncEnumerable<(Guid ProcessStepId, ProcessStepTypeId ProcessStepTypeId)> GetProcessStepData(Guid processId); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/Repositories/ProcessStepRepository.cs b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ProcessStepRepository.cs new file mode 100644 index 00000000..177eddf3 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/Repositories/ProcessStepRepository.cs @@ -0,0 +1,100 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using ProcessTypeId = Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums.ProcessTypeId; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; + +public class ProcessStepRepository : IProcessStepRepository +{ + private readonly IssuerDbContext _context; + + /// + /// Constructor + /// + /// PortalDb context. + public ProcessStepRepository(IssuerDbContext portalDbContext) + { + _context = portalDbContext; + } + + public Process CreateProcess(ProcessTypeId processTypeId) => + _context.Add(new Process(Guid.NewGuid(), processTypeId, Guid.NewGuid())).Entity; + + public ProcessStep CreateProcessStep(ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid processId) => + _context.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, processId, DateTimeOffset.UtcNow)).Entity; + + public IEnumerable CreateProcessStepRange(IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> processStepTypeStatus) + { + var processSteps = processStepTypeStatus.Select(x => new ProcessStep(Guid.NewGuid(), x.ProcessStepTypeId, x.ProcessStepStatusId, x.ProcessId, DateTimeOffset.UtcNow)).ToList(); + _context.AddRange(processSteps); + return processSteps; + } + + public void AttachAndModifyProcessStep(Guid processStepId, Action? initialize, Action modify) + { + var step = new ProcessStep(processStepId, default, default, Guid.Empty, default); + initialize?.Invoke(step); + _context.Attach(step); + step.DateLastChanged = DateTimeOffset.UtcNow; + modify(step); + } + + public void AttachAndModifyProcessSteps(IEnumerable<(Guid ProcessStepId, Action? Initialize, Action Modify)> processStepIdsInitializeModifyData) + { + var stepModifyData = processStepIdsInitializeModifyData.Select(data => + { + var step = new ProcessStep(data.ProcessStepId, default, default, Guid.Empty, default); + data.Initialize?.Invoke(step); + return (Step: step, data.Modify); + }).ToList(); + _context.AttachRange(stepModifyData.Select(data => data.Step)); + stepModifyData.ForEach(data => + { + data.Step.DateLastChanged = DateTimeOffset.UtcNow; + data.Modify(data.Step); + }); + } + + public IAsyncEnumerable GetActiveProcesses(IEnumerable processTypeIds, IEnumerable processStepTypeIds, DateTimeOffset lockExpiryDate) => + _context.Processes + .AsNoTracking() + .Where(process => + processTypeIds.Contains(process.ProcessTypeId) && + process.ProcessSteps.Any(step => processStepTypeIds.Contains(step.ProcessStepTypeId) && step.ProcessStepStatusId == ProcessStepStatusId.TODO) && + (process.LockExpiryDate == null || process.LockExpiryDate < lockExpiryDate)) + .AsAsyncEnumerable(); + + public IAsyncEnumerable<(Guid ProcessStepId, ProcessStepTypeId ProcessStepTypeId)> GetProcessStepData(Guid processId) => + _context.ProcessSteps + .AsNoTracking() + .Where(step => + step.ProcessId == processId && + step.ProcessStepStatusId == ProcessStepStatusId.TODO) + .OrderBy(step => step.ProcessStepTypeId) + .Select(step => + new ValueTuple( + step.Id, + step.ProcessStepTypeId)) + .AsAsyncEnumerable(); +} diff --git a/src/database/SsiCredentialIssuer.DbAccess/SsiCredentialIssuer.DbAccess.csproj b/src/database/SsiCredentialIssuer.DbAccess/SsiCredentialIssuer.DbAccess.csproj new file mode 100644 index 00000000..6cb5fa65 --- /dev/null +++ b/src/database/SsiCredentialIssuer.DbAccess/SsiCredentialIssuer.DbAccess.csproj @@ -0,0 +1,42 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess + Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess + net7.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditCompanySsiDetail20240228.cs b/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditCompanySsiDetail20240228.cs new file mode 100644 index 00000000..be5dd3fb --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditCompanySsiDetail20240228.cs @@ -0,0 +1,58 @@ +/******************************************************************************** + * 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.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities; + +public class AuditCompanySsiDetail20240228 : IAuditEntityV1 +{ + /// + [Key] + public Guid AuditV1Id { get; set; } + + public Guid Id { get; set; } + public string Bpnl { get; set; } = null!; + public string IssuerBpn { get; set; } = null!; + public VerifiedCredentialTypeId VerifiedCredentialTypeId { get; set; } + public CompanySsiDetailStatusId CompanySsiDetailStatusId { get; set; } + public DateTimeOffset DateCreated { get; private set; } + public Guid CreatorUserId { get; set; } + public DateTimeOffset? ExpiryDate { get; set; } + public Guid? VerifiedCredentialExternalTypeDetailVersionId { get; set; } + + public ExpiryCheckTypeId? ExpiryCheckTypeId { get; set; } + public Guid? ProcessId { get; set; } + public Guid? ExternalCredentialId { get; set; } + public string? Credential { get; set; } + public DateTimeOffset? DateLastChanged { get; set; } + public Guid? LastEditorId { get; set; } + + /// + public Guid? AuditV1LastEditorId { get; set; } + + /// + public AuditOperationId AuditV1OperationId { get; set; } + + /// + public DateTimeOffset AuditV1DateLastChanged { get; set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditDocument20240305.cs b/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditDocument20240305.cs new file mode 100644 index 00000000..d7a623fd --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/AuditEntities/AuditDocument20240305.cs @@ -0,0 +1,61 @@ +/******************************************************************************** + * 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.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities; + +public class AuditDocument20240305 : IAuditEntityV1 +{ + /// + [Key] + public Guid AuditV1Id { get; set; } + + public Guid Id { get; private set; } + + public DateTimeOffset? DateCreated { get; private set; } + + public byte[]? DocumentHash { get; set; } + + public byte[]? DocumentContent { get; set; } + + public string? DocumentName { get; set; } + + public MediaTypeId? MediaTypeId { get; set; } + + public DocumentTypeId? DocumentTypeId { get; set; } + + public DocumentStatusId? DocumentStatusId { get; set; } + + public Guid? CompanyUserId { get; set; } + public DateTimeOffset? DateLastChanged { get; set; } + public Guid? LastEditorId { get; private set; } + + /// + public DateTimeOffset AuditV1DateLastChanged { get; set; } + + /// + public Guid? AuditV1LastEditorId { get; set; } + + /// + public AuditOperationId AuditV1OperationId { get; set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditEntityV1Attribute.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditEntityV1Attribute.cs new file mode 100644 index 00000000..b9d84ea4 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditEntityV1Attribute.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; + +/// +/// Attribute to Provide the needed methods to setup an audit trigger +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +[AttributeUsage(AttributeTargets.Class)] +public class AuditEntityV1Attribute : Attribute +{ + public AuditEntityV1Attribute(Type auditEntityType) + { + if (!typeof(IAuditEntityV1).IsAssignableFrom(auditEntityType)) + { + throw new ArgumentException($"Entity must derive from {nameof(IAuditEntityV1)}", nameof(auditEntityType)); + } + + AuditEntityType = auditEntityType; + } + + public virtual Type AuditEntityType { get; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditInsertEditorV1Attribute.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditInsertEditorV1Attribute.cs new file mode 100644 index 00000000..4ccebc8b --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/AuditInsertEditorV1Attribute.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; + +/// +/// Attribute to mark the creators id in the base class. +/// The usage is optional. If not set +/// is being used to determine the creators id. +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +[AttributeUsage(AttributeTargets.Property)] +public class AuditInsertEditorV1Attribute : Attribute +{ +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastChangedV1Attribute.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastChangedV1Attribute.cs new file mode 100644 index 00000000..8028d45e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastChangedV1Attribute.cs @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; + +/// +/// Attribute to mark the last editor id in the base class +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +[AttributeUsage(AttributeTargets.Property)] +public class LastChangedV1Attribute : Attribute +{ +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastEditorV1Attribute.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastEditorV1Attribute.cs new file mode 100644 index 00000000..6af18970 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Attributes/LastEditorV1Attribute.cs @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; + +/// +/// Attribute to mark the last editor id in the base class +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +[AttributeUsage(AttributeTargets.Property)] +public class LastEditorV1Attribute : Attribute +{ +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/DependencyInjection/AuditingDependencyInjection.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/DependencyInjection/AuditingDependencyInjection.cs new file mode 100644 index 00000000..bd641eba --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/DependencyInjection/AuditingDependencyInjection.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.DependencyInjection; + +public static class AuditingDependencyInjection +{ + public static IServiceCollection AddDbAuditing(this IServiceCollection services) + { + return services + .AddTransient() + .AddTransient(); + } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditOperationId.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditOperationId.cs new file mode 100644 index 00000000..975bb728 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditOperationId.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; + +/// +/// Possible operations for the audit table +/// +public enum AuditOperationId +{ + /// + /// The entity has been inserted + /// + INSERT = 1, + + /// + /// The entity has been updated + /// + UPDATE = 2, + + /// + /// The entity has been deleted + /// + DELETE = 3, +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditPropertyV1Names.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditPropertyV1Names.cs new file mode 100644 index 00000000..ea04951b --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Enums/AuditPropertyV1Names.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; + +public enum AuditPropertyV1Names +{ + AuditV1Id, + AuditV1OperationId, + AuditV1DateLastChanged, + AuditV1LastEditorId +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/AuditExtensions.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/AuditExtensions.cs new file mode 100644 index 00000000..e881df0c --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/AuditExtensions.cs @@ -0,0 +1,86 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; +using System.Collections.Immutable; +using System.Reflection; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Extensions; + +public static class AuditExtensions +{ + public record AuditPropertyInformation + ( + Type AuditEntityType, + IEnumerable SourceProperties, + IEnumerable AuditProperties, + IEnumerable TargetProperties + ); + + public static AuditOperationId ToAuditOperation(this EntityState state) => + state switch + { + EntityState.Added => AuditOperationId.INSERT, + EntityState.Deleted => AuditOperationId.DELETE, + EntityState.Modified => AuditOperationId.UPDATE, + _ => throw new ConflictException($"Entries with state {state} should not be audited") + }; + + public static AuditPropertyInformation? GetAuditPropertyInformation(this Type auditableEntityType) + { + var auditEntityAttribute = + (AuditEntityV1Attribute?)Attribute.GetCustomAttribute(auditableEntityType, typeof(AuditEntityV1Attribute)); + if (auditEntityAttribute == null) + { + return null; + } + + var auditEntityType = auditEntityAttribute.AuditEntityType; + if (!typeof(IAuditEntityV1).IsAssignableFrom(auditEntityType)) + { + throw new ConflictException($"{auditEntityType} must inherit from {nameof(IAuditEntityV1)}"); + } + + var sourceProperties = (typeof(IBaseEntity).IsAssignableFrom(auditableEntityType) + ? typeof(IBaseEntity).GetProperties() + : Enumerable.Empty()) + .Concat(auditableEntityType + .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(p => !(p.GetGetMethod()?.IsVirtual ?? false))) + .ToImmutableList(); + var auditProperties = typeof(IAuditEntityV1).GetProperties(); + var targetProperties = auditEntityType.GetProperties().ExceptBy(auditProperties.Select(x => x.Name), p => p.Name).ToImmutableList(); + + targetProperties + .ExceptBy(Enumerable.Repeat(nameof(IBaseEntity.Id), 1), p => p.Name) + .Where(x => x.PropertyType == typeof(Nullable<>)) + .IfAny(notNullableProperties => + throw new ConfigurationException($"Properties {string.Join(",", notNullableProperties.Select(x => x.Name))} of type {auditEntityType.Name} are not nullable")); + + return new AuditPropertyInformation( + auditEntityType, + sourceProperties, + auditProperties, + targetProperties); + } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/EntityTypeBuilderV1Extension.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/EntityTypeBuilderV1Extension.cs new file mode 100644 index 00000000..8d3270b8 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Extensions/EntityTypeBuilderV1Extension.cs @@ -0,0 +1,129 @@ +/******************************************************************************** + * 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 Laraue.EfCoreTriggers.Common.Extensions; +using Laraue.EfCoreTriggers.Common.TriggerBuilders.TableRefs; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; +using System.ComponentModel.DataAnnotations; +using System.Linq.Expressions; +using System.Reflection; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Extensions; + +public static class EntityTypeBuilderV1Extension +{ + public static EntityTypeBuilder HasAuditV1Triggers(this EntityTypeBuilder builder) where TEntity : class, IAuditableV1 where TAuditEntity : class, IAuditEntityV1 + { + var (auditEntityType, sourceProperties, auditProperties, targetProperties) = typeof(TEntity).GetAuditPropertyInformation() ?? throw new ConfigurationException($"{typeof(TEntity)} must be annotated with {nameof(AuditEntityV1Attribute)}"); + if (typeof(TAuditEntity) != auditEntityType) + { + throw new ConfigurationException($"{typeof(TEntity).Name} is annotated with {nameof(AuditEntityV1Attribute)} referring to a different audit entity type {auditEntityType.Name} then {typeof(TAuditEntity).Name}"); + } + + sourceProperties.IntersectBy(auditProperties.Select(x => x.Name), p => p.Name).IfAny( + illegalProperties => throw new ConfigurationException($"{typeof(TEntity).Name} is must not declare any of the following properties: {string.Join(", ", illegalProperties.Select(x => x.Name))}")); + + sourceProperties.ExceptBy(targetProperties.Select(x => x.Name), p => p.Name).IfAny( + missingProperties => throw new ArgumentException($"{typeof(TAuditEntity).Name} is missing the following properties: {string.Join(", ", missingProperties.Select(x => x.Name))}")); + + if (!Array.Exists( + typeof(TAuditEntity).GetProperties(), + p => p.Name == AuditPropertyV1Names.AuditV1Id.ToString() && p.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute)))) + { + throw new ConfigurationException($"{typeof(TAuditEntity).Name}.{AuditPropertyV1Names.AuditV1Id} must be marked as primary key by attribute {typeof(KeyAttribute).Name}"); + } + + var insertEditorProperty = sourceProperties.SingleOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(AuditInsertEditorV1Attribute))); + var lastEditorProperty = sourceProperties.SingleOrDefault(p => p.CustomAttributes.Any(a => a.AttributeType == typeof(LastEditorV1Attribute))); + + return builder + .AfterInsert(trigger => + trigger.Action(action => + action.Insert(CreateNewAuditEntityExpression(sourceProperties, insertEditorProperty ?? lastEditorProperty)))) + .AfterUpdate(trigger => + trigger.Action(action => + action.Insert(CreateUpdateAuditEntityExpression(sourceProperties, lastEditorProperty)))); + } + + private static Expression, TAuditEntity>> CreateNewAuditEntityExpression(IEnumerable sourceProperties, PropertyInfo? lastEditorProperty) where TEntity : class + { + var entity = Expression.Parameter(typeof(NewTableRef), "entity"); + + var newPropertyInfo = typeof(NewTableRef).GetProperty("New"); + if (newPropertyInfo == null) + { + throw new UnexpectedConditionException($"{nameof(NewTableRef)} must have property New"); + } + + var propertyExpression = Expression.Property(entity, newPropertyInfo); + return Expression.Lambda, TAuditEntity>>( + CreateAuditEntityExpression(sourceProperties, AuditOperationId.INSERT, propertyExpression, lastEditorProperty), + entity); + } + + private static Expression, TAuditEntity>> CreateUpdateAuditEntityExpression(IEnumerable sourceProperties, PropertyInfo? lastEditorProperty) where TEntity : class + { + var entity = Expression.Parameter(typeof(OldAndNewTableRefs), "entity"); + + var newPropertyInfo = typeof(OldAndNewTableRefs).GetProperty("New"); + if (newPropertyInfo == null) + { + throw new UnexpectedConditionException($"{nameof(OldAndNewTableRefs)} must have property New"); + } + + var propertyExpression = Expression.Property(entity, newPropertyInfo); + return Expression.Lambda, TAuditEntity>>( + CreateAuditEntityExpression(sourceProperties, AuditOperationId.UPDATE, propertyExpression, lastEditorProperty), + entity); + } + + private static MemberInitExpression CreateAuditEntityExpression(IEnumerable sourceProperties, AuditOperationId auditOperationId, Expression entity, PropertyInfo? lastEditorProperty) + { + var memberBindings = sourceProperties.Select(p => + CreateMemberAssignment(typeof(TAuditEntity).GetMember(p.Name)[0], Expression.Property(entity, p))) + .Append(CreateMemberAssignment(typeof(TAuditEntity).GetMember(AuditPropertyV1Names.AuditV1Id.ToString())[0], Expression.New(typeof(Guid)))) + .Append(CreateMemberAssignment(typeof(TAuditEntity).GetMember(AuditPropertyV1Names.AuditV1OperationId.ToString())[0], Expression.Constant(auditOperationId))) + .Append(CreateMemberAssignment(typeof(TAuditEntity).GetMember(AuditPropertyV1Names.AuditV1DateLastChanged.ToString())[0], Expression.New(typeof(DateTimeOffset)))); + + if (lastEditorProperty != null) + { + memberBindings = memberBindings.Append(CreateMemberAssignment(typeof(TAuditEntity).GetMember(AuditPropertyV1Names.AuditV1LastEditorId.ToString())[0], Expression.Property(entity, lastEditorProperty))); + } + + return Expression.MemberInit( + Expression.New(typeof(TAuditEntity)), + memberBindings); + } + + private static MemberAssignment CreateMemberAssignment(MemberInfo member, Expression expression) + { + try + { + return Expression.Bind(member, expression); + } + catch (Exception e) + { + throw new ArgumentException($"{member.DeclaringType?.Name}.{member.Name} is not assignable from {expression}, {e.Message}", e); + } + } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/AuditHandlerV1.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/AuditHandlerV1.cs new file mode 100644 index 00000000..ce651a6e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/AuditHandlerV1.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 Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Extensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Identity; +using System.Collections.Immutable; +using System.Reflection; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; + +public class AuditHandlerV1 : IAuditHandler +{ + private readonly IIdentityIdService _identityService; + private readonly IDateTimeProvider _dateTimeProvider; + + public AuditHandlerV1(IIdentityIdService identityService, IDateTimeProvider dateTimeProvider) + { + _identityService = identityService; + _dateTimeProvider = dateTimeProvider; + } + + public void HandleAuditForChangedEntries(IEnumerable changedEntries, DbContext context) + { + var now = _dateTimeProvider.OffsetNow; + foreach (var groupedEntries in changedEntries + .GroupBy(entry => entry.Metadata.ClrType)) + { + var lastEditorNames = groupedEntries.Key.GetProperties() + .Where(x => Attribute.IsDefined(x, typeof(LastEditorV1Attribute))) + .Select(x => x.Name) + .ToImmutableHashSet(); + var lastChangedNames = groupedEntries.Key.GetProperties() + .Where(x => Attribute.IsDefined(x, typeof(LastChangedV1Attribute))) + .Select(x => x.Name) + .ToImmutableHashSet(); + + foreach (var properties in groupedEntries.Where(entry => entry.State != EntityState.Deleted).Select(entry => entry.Properties)) + { + foreach (var prop in properties.IntersectBy( + lastEditorNames, + property => property.Metadata.Name)) + { + prop.CurrentValue = _identityService.IdentityId; + } + + foreach (var prop in properties.IntersectBy( + lastChangedNames, + property => property.Metadata.Name)) + { + prop.CurrentValue = now; + } + } + + var auditPropertyInformation = groupedEntries.Key.GetAuditPropertyInformation(); + if (auditPropertyInformation == null) + continue; + var (auditEntityType, sourceProperties, _, targetProperties) = auditPropertyInformation; + + foreach (var entry in groupedEntries.Where(entry => entry.State == EntityState.Deleted)) + { + AddAuditEntry(entry, entry.Metadata.ClrType, context, auditEntityType, sourceProperties, targetProperties); + } + } + } + + private void AddAuditEntry(EntityEntry entityEntry, Type entityType, DbContext context, Type auditEntityType, IEnumerable sourceProperties, IEnumerable targetProperties) + { + if (Activator.CreateInstance(auditEntityType) is not IAuditEntityV1 newAuditEntity) + throw new UnexpectedConditionException($"AuditEntityV1Attribute can only be used on types implementing IAuditEntityV1 but Type {entityType} isn't"); + + var propertyValues = entityEntry.CurrentValues; + foreach (var joined in targetProperties.Join( + sourceProperties, + t => t.Name, + s => s.Name, + (t, s) => (Target: t, Value: propertyValues?[s.Name]))) + { + joined.Target.SetValue(newAuditEntity, joined.Value); + } + + newAuditEntity.AuditV1Id = Guid.NewGuid(); + newAuditEntity.AuditV1OperationId = entityEntry.State.ToAuditOperation(); + newAuditEntity.AuditV1DateLastChanged = _dateTimeProvider.OffsetNow; + newAuditEntity.AuditV1LastEditorId = _identityService.IdentityId; + + context.Add(newAuditEntity); + } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/IAuditHandler.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/IAuditHandler.cs new file mode 100644 index 00000000..02c25572 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Handler/IAuditHandler.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 Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; + +public interface IAuditHandler +{ + void HandleAuditForChangedEntries(IEnumerable changedEntries, DbContext context); +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditEntityV1.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditEntityV1.cs new file mode 100644 index 00000000..18471c8e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditEntityV1.cs @@ -0,0 +1,52 @@ +/******************************************************************************** + * 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.Auditing.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; + +/// +/// Marker interface to define that the entity is an audit entity +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +public interface IAuditEntityV1 +{ + /// + /// Id of the audited entity + /// + Guid AuditV1Id { get; set; } + + /// + /// Date Time of the last change of the entity + /// + DateTimeOffset AuditV1DateLastChanged { get; set; } + + /// + /// Reference to the that changed the entity + /// + Guid? AuditV1LastEditorId { get; set; } + + /// + /// Id of the audit operation + /// + AuditOperationId AuditV1OperationId { get; set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditableV1.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditableV1.cs new file mode 100644 index 00000000..99f65976 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/IAuditableV1.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; + +/// +/// Marker interface to define that a entity is auditable +/// +/// +/// The implementation of this Attribute must not be changed. +/// When changes are needed create a V2 of it. +/// +public interface IAuditableV1 +{ +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/IBaseEntity.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/IBaseEntity.cs new file mode 100644 index 00000000..5ee0e0c3 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/IBaseEntity.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; + +/// +/// Marker interface +/// +public interface IBaseEntity +{ + Guid Id { get; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Auditing/Identity/IIdentityIdService.cs b/src/database/SsiCredentialIssuer.Entities/Auditing/Identity/IIdentityIdService.cs new file mode 100644 index 00000000..26544541 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Auditing/Identity/IIdentityIdService.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Identity; + +public interface IIdentityIdService +{ + Guid IdentityId { get; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetail.cs b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetail.cs new file mode 100644 index 00000000..102e6776 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetail.cs @@ -0,0 +1,78 @@ +/******************************************************************************** + * 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.AuditEntities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +[AuditEntityV1(typeof(AuditCompanySsiDetail20240228))] +public class CompanySsiDetail : IAuditableV1, IBaseEntity +{ + private CompanySsiDetail() + { + Bpnl = null!; + IssuerBpn = null!; + Documents = new HashSet(); + } + + public CompanySsiDetail(Guid id, string bpnl, VerifiedCredentialTypeId verifiedCredentialTypeId, CompanySsiDetailStatusId companySsiDetailStatusId, string issuerBpn, Guid creatorUserId, DateTimeOffset dateCreated) + : this() + { + Id = id; + Bpnl = bpnl; + VerifiedCredentialTypeId = verifiedCredentialTypeId; + CompanySsiDetailStatusId = companySsiDetailStatusId; + IssuerBpn = issuerBpn; + CreatorUserId = creatorUserId; + DateCreated = dateCreated; + } + + public Guid Id { get; set; } + public string Bpnl { get; set; } + public string IssuerBpn { get; set; } + public VerifiedCredentialTypeId VerifiedCredentialTypeId { get; set; } + public CompanySsiDetailStatusId CompanySsiDetailStatusId { get; set; } + public DateTimeOffset DateCreated { get; set; } + public Guid CreatorUserId { get; set; } + public DateTimeOffset? ExpiryDate { get; set; } + public Guid? VerifiedCredentialExternalTypeDetailVersionId { get; set; } + + public ExpiryCheckTypeId? ExpiryCheckTypeId { get; set; } + public Guid? ProcessId { get; set; } + public Guid? ExternalCredentialId { get; set; } + public string? Credential { get; set; } + + [LastChangedV1] + public DateTimeOffset? DateLastChanged { get; set; } + + [LastEditorV1] + public Guid? LastEditorId { get; private set; } + + // Navigation Properties + public virtual VerifiedCredentialType? VerifiedCredentialType { get; set; } + public virtual ExpiryCheckType? ExpiryCheckType { get; set; } + public virtual CompanySsiDetailStatus? CompanySsiDetailStatus { get; set; } + public virtual Process? Process { get; set; } + public virtual VerifiedCredentialExternalTypeDetailVersion? VerifiedCredentialExternalTypeDetailVersion { get; set; } + public virtual CompanySsiProcessData? CompanySsiProcessData { get; set; } + public virtual ICollection Documents { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailAssignedDocument.cs b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailAssignedDocument.cs new file mode 100644 index 00000000..79f88bdf --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailAssignedDocument.cs @@ -0,0 +1,16 @@ +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class CompanySsiDetailAssignedDocument +{ + public CompanySsiDetailAssignedDocument(Guid documentId, Guid companySsiDetailId) + { + DocumentId = documentId; + CompanySsiDetailId = companySsiDetailId; + } + + public Guid DocumentId { get; set; } + public Guid CompanySsiDetailId { get; set; } + + public virtual Document? Document { get; set; } + public virtual CompanySsiDetail? CompanySsiDetail { get; set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailStatus.cs b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailStatus.cs new file mode 100644 index 00000000..7c4be658 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiDetailStatus.cs @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class CompanySsiDetailStatus +{ + private CompanySsiDetailStatus() + { + Label = null!; + CompanySsiDetails = new HashSet(); + } + + public CompanySsiDetailStatus(CompanySsiDetailStatusId companySsiDetailStatusId) + : this() + { + Id = companySsiDetailStatusId; + Label = companySsiDetailStatusId.ToString(); + } + + public CompanySsiDetailStatusId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiProcessData.cs b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiProcessData.cs new file mode 100644 index 00000000..1e62601e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/CompanySsiProcessData.cs @@ -0,0 +1,51 @@ +/******************************************************************************** + * 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; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class CompanySsiProcessData +{ + private CompanySsiProcessData() + { + Schema = null!; + } + + public CompanySsiProcessData(Guid companySsiDetailId, JsonDocument schema, VerifiedCredentialTypeKindId credentialTypeKindId) + : this() + { + CompanySsiDetailId = companySsiDetailId; + Schema = schema; + CredentialTypeKindId = credentialTypeKindId; + } + + public Guid CompanySsiDetailId { get; set; } + public JsonDocument Schema { get; set; } + public VerifiedCredentialTypeKindId CredentialTypeKindId { get; set; } + public string? ClientId { get; set; } + public byte[]? ClientSecret { get; set; } + public byte[]? InitializationVector { get; set; } + public int? EncryptionMode { get; set; } + public string? HolderWalletUrl { get; set; } + public string? CallbackUrl { get; set; } + public virtual CompanySsiDetail? CompanySsiDetail { get; private set; } + public virtual VerifiedCredentialTypeKind? CredentialTypeKind { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/Document.cs b/src/database/SsiCredentialIssuer.Entities/Entities/Document.cs new file mode 100644 index 00000000..ac70ee87 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/Document.cs @@ -0,0 +1,83 @@ +/******************************************************************************** + * 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.AuditEntities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +[AuditEntityV1(typeof(AuditDocument20240305))] +public class Document : IAuditableV1, IBaseEntity +{ + private Document() + { + DocumentHash = null!; + DocumentName = null!; + DocumentContent = null!; + CompanySsiDetails = new HashSet(); + } + + public Document(Guid id, byte[] documentContent, byte[] documentHash, string documentName, MediaTypeId mediaTypeId, DateTimeOffset dateCreated, DocumentStatusId documentStatusId, DocumentTypeId documentTypeId) + : this() + { + Id = id; + DocumentContent = documentContent; + DocumentHash = documentHash; + DocumentName = documentName; + DateCreated = dateCreated; + DocumentStatusId = documentStatusId; + DocumentTypeId = documentTypeId; + MediaTypeId = mediaTypeId; + } + + public Guid Id { get; private set; } + + public DateTimeOffset DateCreated { get; private set; } + + public byte[] DocumentHash { get; set; } + + public byte[] DocumentContent { get; set; } + + [MaxLength(255)] + public string DocumentName { get; set; } + + public MediaTypeId MediaTypeId { get; set; } + + public DocumentTypeId DocumentTypeId { get; set; } + + public DocumentStatusId DocumentStatusId { get; set; } + + public Guid? CompanyUserId { get; set; } + + [LastChangedV1] + public DateTimeOffset? DateLastChanged { get; set; } + + [LastEditorV1] + public Guid? LastEditorId { get; private set; } + + // Navigation properties + public virtual DocumentType? DocumentType { get; set; } + public virtual MediaType? MediaType { get; set; } + public virtual DocumentStatus? DocumentStatus { get; set; } + + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/DocumentStatus.cs b/src/database/SsiCredentialIssuer.Entities/Entities/DocumentStatus.cs new file mode 100644 index 00000000..425bb472 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/DocumentStatus.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class DocumentStatus +{ + private DocumentStatus() + { + Label = null!; + Documents = new HashSet(); + } + + public DocumentStatus(DocumentStatusId documentStatusId) : this() + { + Id = documentStatusId; + Label = documentStatusId.ToString(); + } + + public DocumentStatusId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection Documents { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/DocumentType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/DocumentType.cs new file mode 100644 index 00000000..602c03c9 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/DocumentType.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class DocumentType +{ + private DocumentType() + { + Label = null!; + Documents = new HashSet(); + } + + public DocumentType(DocumentTypeId documentTypeId) : this() + { + Id = documentTypeId; + Label = documentTypeId.ToString(); + } + + public DocumentTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection Documents { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/ExpiryCheckType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/ExpiryCheckType.cs new file mode 100644 index 00000000..3eddca76 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/ExpiryCheckType.cs @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class ExpiryCheckType +{ + private ExpiryCheckType() + { + Label = null!; + CompanySsiDetails = new HashSet(); + } + + public ExpiryCheckType(ExpiryCheckTypeId expiryCheckId) + : this() + { + Id = expiryCheckId; + Label = expiryCheckId.ToString(); + } + + public ExpiryCheckTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/MediaType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/MediaType.cs new file mode 100644 index 00000000..274f3881 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/MediaType.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class MediaType +{ + private MediaType() + { + Label = null!; + Documents = new HashSet(); + } + + public MediaType(MediaTypeId mediaTypeId) : this() + { + Id = mediaTypeId; + Label = mediaTypeId.ToString(); + } + + public MediaTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection Documents { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/Process.cs b/src/database/SsiCredentialIssuer.Entities/Entities/Process.cs new file mode 100644 index 00000000..41448123 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/Process.cs @@ -0,0 +1,55 @@ +/******************************************************************************** + * 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.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class Process : IBaseEntity, ILockableEntity +{ + private Process() + { + ProcessSteps = new HashSet(); + CompanySsiDetails = new HashSet(); + } + + public Process(Guid id, ProcessTypeId processTypeId, Guid version) : this() + { + Id = id; + ProcessTypeId = processTypeId; + Version = version; + } + + public Guid Id { get; private set; } + + public ProcessTypeId ProcessTypeId { get; set; } + + public DateTimeOffset? LockExpiryDate { get; set; } + + [ConcurrencyCheck] + public Guid Version { get; set; } + + // Navigation properties + public virtual ProcessType? ProcessType { get; set; } + public virtual ICollection ProcessSteps { get; private set; } + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStep.cs b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStep.cs new file mode 100644 index 00000000..13da78d4 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStep.cs @@ -0,0 +1,56 @@ +/******************************************************************************** + * 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.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Attributes; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class ProcessStep : IBaseEntity +{ + public ProcessStep(Guid id, ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid processId, DateTimeOffset dateCreated) + { + Id = id; + ProcessStepTypeId = processStepTypeId; + ProcessStepStatusId = processStepStatusId; + ProcessId = processId; + DateCreated = dateCreated; + } + + public Guid Id { get; private set; } + + public ProcessStepTypeId ProcessStepTypeId { get; private set; } + + public ProcessStepStatusId ProcessStepStatusId { get; set; } + + public Guid ProcessId { get; private set; } + + public DateTimeOffset DateCreated { get; private set; } + + [LastChangedV1] + public DateTimeOffset? DateLastChanged { get; set; } + + public string? Message { get; set; } + + // Navigation properties + public virtual ProcessStepType? ProcessStepType { get; private set; } + public virtual ProcessStepStatus? ProcessStepStatus { get; set; } + public virtual Process? Process { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepStatus.cs b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepStatus.cs new file mode 100644 index 00000000..a62480b7 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepStatus.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class ProcessStepStatus +{ + private ProcessStepStatus() + { + this.Label = null!; + this.ProcessSteps = new HashSet(); + } + + public ProcessStepStatus(ProcessStepStatusId processStepStatusId) : this() + { + Id = processStepStatusId; + Label = processStepStatusId.ToString(); + } + + public ProcessStepStatusId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection ProcessSteps { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepType.cs new file mode 100644 index 00000000..d175f667 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessStepType.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class ProcessStepType +{ + private ProcessStepType() + { + this.Label = null!; + this.ProcessSteps = new HashSet(); + } + + public ProcessStepType(ProcessStepTypeId processStepTypeId) : this() + { + Id = processStepTypeId; + Label = processStepTypeId.ToString(); + } + + public ProcessStepTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection ProcessSteps { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/ProcessType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessType.cs new file mode 100644 index 00000000..87c8860a --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/ProcessType.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class ProcessType +{ + private ProcessType() + { + this.Label = null!; + this.Processes = new HashSet(); + } + + public ProcessType(ProcessTypeId processTypeId) : this() + { + Id = processTypeId; + Label = processTypeId.ToString(); + } + + public ProcessTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection Processes { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/UseCase.cs b/src/database/SsiCredentialIssuer.Entities/Entities/UseCase.cs new file mode 100644 index 00000000..69be03e8 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/UseCase.cs @@ -0,0 +1,50 @@ +/******************************************************************************** + * 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.Auditing; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class UseCase : IBaseEntity +{ + private UseCase() + { + Name = null!; + Shortname = null!; + } + + public UseCase(Guid id, string name, string shortname) : this() + { + Id = id; + Name = name; + Shortname = shortname; + } + + public Guid Id { get; private set; } + + [MaxLength(255)] + public string Name { get; set; } + + [MaxLength(255)] + public string Shortname { get; set; } + + // Navigation properties + public virtual VerifiedCredentialTypeAssignedUseCase? VerifiedCredentialAssignedUseCase { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalType.cs new file mode 100644 index 00000000..512124d7 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalType.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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.Entities.Entities; + +public class VerifiedCredentialExternalType +{ + public VerifiedCredentialExternalType(VerifiedCredentialExternalTypeId id) + { + Id = id; + Label = id.ToString(); + + VerifiedCredentialTypeAssignedExternalTypes = new HashSet(); + VerifiedCredentialExternalTypeDetailVersions = new HashSet(); + } + + public VerifiedCredentialExternalTypeId Id { get; private set; } + + public string Label { get; private set; } + + // Navigation properties + public virtual ICollection VerifiedCredentialTypeAssignedExternalTypes { get; private set; } + + public virtual ICollection VerifiedCredentialExternalTypeDetailVersions { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalTypeDetailVersion.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalTypeDetailVersion.cs new file mode 100644 index 00000000..f4a8f9b6 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialExternalTypeDetailVersion.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class VerifiedCredentialExternalTypeDetailVersion : IBaseEntity +{ + public VerifiedCredentialExternalTypeDetailVersion() + { + CompanySsiDetails = new HashSet(); + } + + public Guid Id { get; set; } + public VerifiedCredentialExternalTypeId VerifiedCredentialExternalTypeId { get; set; } + public string? Version { get; set; } + public string? Template { get; set; } + public DateTimeOffset ValidFrom { get; set; } + public DateTimeOffset Expiry { get; set; } + + public virtual VerifiedCredentialExternalType? VerifiedCredentialExternalType { get; private set; } + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialType.cs new file mode 100644 index 00000000..e72944fd --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialType.cs @@ -0,0 +1,52 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class VerifiedCredentialType +{ + private VerifiedCredentialType() + { + Label = null!; + CompanySsiDetails = new HashSet(); + } + + public VerifiedCredentialType(VerifiedCredentialTypeId verifiedCredentialTypeId) + : this() + { + Id = verifiedCredentialTypeId; + Label = verifiedCredentialTypeId.ToString(); + } + + public VerifiedCredentialTypeId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation properties + public virtual VerifiedCredentialTypeAssignedExternalType? VerifiedCredentialTypeAssignedExternalType { get; private set; } + public virtual VerifiedCredentialTypeAssignedKind? VerifiedCredentialTypeAssignedKind { get; set; } + + public virtual VerifiedCredentialTypeAssignedUseCase? VerifiedCredentialTypeAssignedUseCase { get; set; } + + public virtual ICollection CompanySsiDetails { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedExternalType.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedExternalType.cs new file mode 100644 index 00000000..2ae5f28b --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedExternalType.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Entities.Entities; + +public class VerifiedCredentialTypeAssignedExternalType +{ + public VerifiedCredentialTypeAssignedExternalType(VerifiedCredentialTypeId verifiedCredentialTypeId, VerifiedCredentialExternalTypeId verifiedCredentialExternalTypeId) + { + VerifiedCredentialTypeId = verifiedCredentialTypeId; + VerifiedCredentialExternalTypeId = verifiedCredentialExternalTypeId; + } + + /// + /// Id of the credential type. + /// + public VerifiedCredentialTypeId VerifiedCredentialTypeId { get; private set; } + + /// + /// Id of the credential type kind. + /// + public VerifiedCredentialExternalTypeId VerifiedCredentialExternalTypeId { get; private set; } + + // Navigation properties + public virtual VerifiedCredentialType? VerifiedCredentialType { get; private set; } + + public virtual VerifiedCredentialExternalType? VerifiedCredentialExternalType { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedKind.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedKind.cs new file mode 100644 index 00000000..b1a174d5 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedKind.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Entities.Entities; + +public class VerifiedCredentialTypeAssignedKind +{ + public VerifiedCredentialTypeAssignedKind(VerifiedCredentialTypeId verifiedCredentialTypeId, VerifiedCredentialTypeKindId verifiedCredentialTypeKindId) + { + VerifiedCredentialTypeId = verifiedCredentialTypeId; + VerifiedCredentialTypeKindId = verifiedCredentialTypeKindId; + } + + /// + /// Id of the credential type. + /// + public VerifiedCredentialTypeId VerifiedCredentialTypeId { get; private set; } + + /// + /// Id of the credential type kind. + /// + public VerifiedCredentialTypeKindId VerifiedCredentialTypeKindId { get; private set; } + + // Navigation properties + public virtual VerifiedCredentialType? VerifiedCredentialType { get; private set; } + + public virtual VerifiedCredentialTypeKind? VerifiedCredentialTypeKind { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedUseCase.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedUseCase.cs new file mode 100644 index 00000000..8924c076 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeAssignedUseCase.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * 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.Entities.Entities; + +public class VerifiedCredentialTypeAssignedUseCase +{ + public VerifiedCredentialTypeAssignedUseCase(VerifiedCredentialTypeId verifiedCredentialTypeId, Guid useCaseId) + { + VerifiedCredentialTypeId = verifiedCredentialTypeId; + UseCaseId = useCaseId; + } + + public VerifiedCredentialTypeId VerifiedCredentialTypeId { get; set; } + public Guid UseCaseId { get; set; } + + // Navigation Properties + public virtual VerifiedCredentialType? VerifiedCredentialType { get; set; } + public virtual UseCase? UseCase { get; set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeKind.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeKind.cs new file mode 100644 index 00000000..f5c5fbf9 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifiedCredentialTypeKind.cs @@ -0,0 +1,49 @@ +/******************************************************************************** + * 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; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public class VerifiedCredentialTypeKind +{ + private VerifiedCredentialTypeKind() + { + Label = null!; + VerifiedCredentialTypeAssignedKinds = new HashSet(); + CompanySsiProcessData = new HashSet(); + } + + public VerifiedCredentialTypeKind(VerifiedCredentialTypeKindId verifiedCredentialTypeKindId) + : this() + { + Id = verifiedCredentialTypeKindId; + Label = verifiedCredentialTypeKindId.ToString(); + } + + public VerifiedCredentialTypeKindId Id { get; private set; } + + [MaxLength(255)] + public string Label { get; private set; } + + // Navigation Properties + public virtual ICollection VerifiedCredentialTypeAssignedKinds { get; private set; } + public virtual ICollection CompanySsiProcessData { get; private set; } +} diff --git a/src/database/SsiCredentialIssuer.Entities/Entities/VerifyProcessData.cs b/src/database/SsiCredentialIssuer.Entities/Entities/VerifyProcessData.cs new file mode 100644 index 00000000..20004488 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Entities/VerifyProcessData.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +public record VerifyProcessData( + Process? Process, + IEnumerable? ProcessSteps +); diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/CompanySsiDetailStatusId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/CompanySsiDetailStatusId.cs new file mode 100644 index 00000000..9cc90ea6 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/CompanySsiDetailStatusId.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum CompanySsiDetailStatusId +{ + PENDING = 1, + ACTIVE = 2, + REVOKED = 3, + INACTIVE = 4 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/DocumentStatusId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/DocumentStatusId.cs new file mode 100644 index 00000000..96d3d43a --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/DocumentStatusId.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +/// +/// Possible status for documents +/// +public enum DocumentStatusId +{ + /// + /// The document is active for changes + /// + ACTIVE = 2, + + /// + /// The document was deleted by the user + /// + INACTIVE = 3, +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/DocumentTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/DocumentTypeId.cs new file mode 100644 index 00000000..d9502810 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/DocumentTypeId.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum DocumentTypeId +{ + PRESENTATION = 1, + CREDENTIAL = 2, + VERIFIED_CREDENTIAL = 3 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ExpiryCheckTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ExpiryCheckTypeId.cs new file mode 100644 index 00000000..3280b9c9 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ExpiryCheckTypeId.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum ExpiryCheckTypeId +{ + ONE_MONTH = 1, + TWO_WEEKS = 2, + ONE_DAY = 3 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/MediaTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/MediaTypeId.cs new file mode 100644 index 00000000..f322bdf6 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/MediaTypeId.cs @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum MediaTypeId +{ + JPEG = 1, + GIF = 2, + PNG = 3, + SVG = 4, + TIFF = 5, + PDF = 6, + JSON = 7, + PEM = 8, + CA_CERT = 9, + PKX_CER = 10, + OCTET = 11 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepStatusId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepStatusId.cs new file mode 100644 index 00000000..9450d24b --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepStatusId.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum ProcessStepStatusId +{ + TODO = 1, + DONE = 2, + SKIPPED = 3, + FAILED = 4, + DUPLICATE = 5 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.cs new file mode 100644 index 00000000..a44fed17 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessStepTypeId.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum ProcessStepTypeId +{ + // Issuer Process + CREATE_CREDENTIAL = 1, + SIGN_CREDENTIAL = 2, + SAVE_CREDENTIAL_DOCUMENT = 3, + CREATE_CREDENTIAL_FOR_HOLDER = 4, + TRIGGER_CALLBACK = 5, +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs new file mode 100644 index 00000000..a07c1bca --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/ProcessTypeId.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum ProcessTypeId +{ + CREATE_CREDENTIAL = 1, +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs new file mode 100644 index 00000000..fbb6178c --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialExternalTypeId.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Runtime.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum VerifiedCredentialExternalTypeId +{ + [EnumMember(Value = "TraceabilityCredential")] + TRACEABILITY_CREDENTIAL = 1, + + [EnumMember(Value = "PcfCredential")] + PCF_CREDENTIAL = 2, + + [EnumMember(Value = "BehaviorTwinCredential")] + BEHAVIOR_TWIN_CREDENTIAL = 3, + + [EnumMember(Value = "vehicleDismantle")] + VEHICLE_DISMANTLE = 4, + + [EnumMember(Value = "SustainabilityCredential")] + SUSTAINABILITY_CREDENTIAL = 5, + + [EnumMember(Value = "QualityCredential")] + QUALITY_CREDENTIAL = 6, + + [EnumMember(Value = "BusinessPartnerCredential")] + BUSINESS_PARTNER_NUMBER = 7 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs new file mode 100644 index 00000000..725f9d22 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeId.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Runtime.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum VerifiedCredentialTypeId +{ + [EnumMember(Value = "Traceability Framework")] + TRACEABILITY_FRAMEWORK = 1, + + [EnumMember(Value = "PCF Framework")] + PCF_FRAMEWORK = 2, + + [EnumMember(Value = "Behavior Twin Framework")] + BEHAVIOR_TWIN_FRAMEWORK = 3, + + [EnumMember(Value = "Dismantler Certificate")] + DISMANTLER_CERTIFICATE = 4, + + [EnumMember(Value = "Sustainability Framework")] + SUSTAINABILITY_FRAMEWORK = 5, + + [EnumMember(Value = "frameworkAgreement.quality")] + FRAMEWORK_AGREEMENT_QUALITY = 6, + + [EnumMember(Value = "BusinessPartnerCredential")] + BUSINESS_PARTNER_NUMBER = 7 +} diff --git a/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeKindId.cs b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeKindId.cs new file mode 100644 index 00000000..23581709 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/Enums/VerifiedCredentialTypeKindId.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +public enum VerifiedCredentialTypeKindId +{ + FRAMEWORK = 1, + MEMBERSHIP = 2, + BPN = 3 +} diff --git a/src/database/SsiCredentialIssuer.Entities/IssuerDbContext.cs b/src/database/SsiCredentialIssuer.Entities/IssuerDbContext.cs new file mode 100644 index 00000000..65274bdc --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/IssuerDbContext.cs @@ -0,0 +1,318 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Extensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; + +public class IssuerDbContext : DbContext +{ + private readonly IAuditHandler _auditHandler; + + protected IssuerDbContext() + { + throw new InvalidOperationException("IdentityService should never be null"); + } + + public IssuerDbContext(DbContextOptions options, IAuditHandler auditHandler) + : base(options) + { + _auditHandler = auditHandler; + } + + public virtual DbSet AuditCompanySsiDetail20240228 { get; set; } = default!; + public virtual DbSet AuditDocument20240305 { get; set; } = default!; + public virtual DbSet CompanySsiDetails { get; set; } = default!; + public virtual DbSet CompanySsiDetailAssignedDocuments { get; set; } = default!; + public virtual DbSet CompanySsiDetailStatuses { get; set; } = default!; + public virtual DbSet CompanySsiProcessData { get; set; } = default!; + public virtual DbSet Documents { get; set; } = default!; + public virtual DbSet DocumentStatus { get; set; } = default!; + public virtual DbSet DocumentTypes { get; set; } = default!; + public virtual DbSet ExpiryCheckTypes { get; set; } = default!; + public virtual DbSet MediaTypes { get; set; } = default!; + public virtual DbSet Processes { get; set; } = default!; + public virtual DbSet ProcessSteps { get; set; } = default!; + public virtual DbSet ProcessStepStatuses { get; set; } = default!; + public virtual DbSet ProcessStepTypes { get; set; } = default!; + public virtual DbSet ProcessTypes { get; set; } = default!; + public virtual DbSet UseCases { get; set; } = default!; + public virtual DbSet VerifiedCredentialExternalTypes { get; set; } = default!; + public virtual DbSet VerifiedCredentialExternalTypeDetailVersions { get; set; } = default!; + public virtual DbSet VerifiedCredentialTypes { get; set; } = default!; + public virtual DbSet VerifiedCredentialTypeAssignedExternalTypes { get; set; } = default!; + public virtual DbSet VerifiedCredentialTypeAssignedKinds { get; set; } = default!; + public virtual DbSet VerifiedCredentialTypeAssignedUseCases { get; set; } = default!; + public virtual DbSet VerifiedCredentialTypeKinds { get; set; } = default!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSnakeCaseNamingConvention(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.HasAnnotation("Relational:Collation", "en_US.utf8"); + modelBuilder.HasDefaultSchema("issuer"); + + modelBuilder.Entity(entity => + { + entity.HasOne(c => c.Process) + .WithMany(c => c.CompanySsiDetails) + .HasForeignKey(t => t.ProcessId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(c => c.VerifiedCredentialType) + .WithMany(c => c.CompanySsiDetails) + .HasForeignKey(t => t.VerifiedCredentialTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(c => c.CompanySsiDetailStatus) + .WithMany(c => c.CompanySsiDetails) + .HasForeignKey(t => t.CompanySsiDetailStatusId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(c => c.VerifiedCredentialExternalTypeDetailVersion) + .WithMany(c => c.CompanySsiDetails) + .HasForeignKey(t => t.VerifiedCredentialExternalTypeDetailVersionId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasMany(t => t.Documents) + .WithMany(o => o.CompanySsiDetails) + .UsingEntity( + j => j + .HasOne(d => d.Document!) + .WithMany() + .HasForeignKey(d => d.DocumentId) + .OnDelete(DeleteBehavior.ClientSetNull), + j => j + .HasOne(d => d.CompanySsiDetail!) + .WithMany() + .HasForeignKey(d => d.CompanySsiDetailId) + .OnDelete(DeleteBehavior.ClientSetNull), + j => + { + j.HasKey(e => new { e.DocumentId, e.CompanySsiDetailId }); + }); + + entity.HasAuditV1Triggers(); + }); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(CompanySsiDetailStatusId)) + .Cast() + .Select(e => new CompanySsiDetailStatus(e)) + ); + + modelBuilder.Entity(e => + { + e.HasKey(x => x.CompanySsiDetailId); + + e.HasOne(x => x.CompanySsiDetail) + .WithOne(x => x.CompanySsiProcessData) + .HasForeignKey(x => x.CompanySsiDetailId); + + e.HasOne(x => x.CredentialTypeKind) + .WithMany(x => x.CompanySsiProcessData) + .HasForeignKey(x => x.CredentialTypeKindId); + }); + + modelBuilder.Entity(entity => + { + entity.HasAuditV1Triggers(); + }); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(DocumentStatusId)) + .Cast() + .Select(e => new DocumentStatus(e)) + ); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(DocumentTypeId)) + .Cast() + .Select(e => new DocumentType(e)) + ); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(ExpiryCheckTypeId)) + .Cast() + .Select(e => new ExpiryCheckType(e)) + ); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(MediaTypeId)) + .Cast() + .Select(e => new MediaType(e)) + ); + + modelBuilder.Entity() + .HasOne(d => d.ProcessType) + .WithMany(p => p!.Processes) + .HasForeignKey(d => d.ProcessTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + + modelBuilder.Entity() + .HasOne(d => d.Process) + .WithMany(p => p!.ProcessSteps) + .HasForeignKey(d => d.ProcessId) + .OnDelete(DeleteBehavior.ClientSetNull); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(ProcessTypeId)) + .Cast() + .Select(e => new ProcessType(e)) + ); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(ProcessStepStatusId)) + .Cast() + .Select(e => new ProcessStepStatus(e)) + ); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(ProcessStepTypeId)) + .Cast() + .Select(e => new ProcessStepType(e)) + ); + + modelBuilder.Entity(); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(VerifiedCredentialExternalTypeId)) + .Cast() + .Select(e => new VerifiedCredentialExternalType(e)) + ); + + modelBuilder.Entity(entity => + { + entity.HasOne(d => d.VerifiedCredentialExternalType) + .WithMany(x => x.VerifiedCredentialExternalTypeDetailVersions) + .HasForeignKey(d => d.VerifiedCredentialExternalTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasIndex(e => new { e.VerifiedCredentialExternalTypeId, e.Version }) + .IsUnique(); + }); + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(VerifiedCredentialTypeId)) + .Cast() + .Select(e => new VerifiedCredentialType(e)) + ); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.VerifiedCredentialTypeId, e.VerifiedCredentialExternalTypeId }); + + entity.HasOne(d => d.VerifiedCredentialType) + .WithOne(x => x.VerifiedCredentialTypeAssignedExternalType) + .HasForeignKey(d => d.VerifiedCredentialTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(d => d.VerifiedCredentialExternalType) + .WithMany(x => x.VerifiedCredentialTypeAssignedExternalTypes) + .HasForeignKey(d => d.VerifiedCredentialExternalTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.VerifiedCredentialTypeId, e.VerifiedCredentialTypeKindId }); + + entity.HasOne(d => d.VerifiedCredentialTypeKind) + .WithMany(x => x.VerifiedCredentialTypeAssignedKinds) + .HasForeignKey(d => d.VerifiedCredentialTypeKindId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(d => d.VerifiedCredentialType) + .WithOne(x => x.VerifiedCredentialTypeAssignedKind) + .HasForeignKey(d => d.VerifiedCredentialTypeId) + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasIndex(x => x.VerifiedCredentialTypeId) + .IsUnique(false); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => new { x.VerifiedCredentialTypeId, x.UseCaseId }); + + entity.HasOne(c => c.VerifiedCredentialType) + .WithOne(c => c.VerifiedCredentialTypeAssignedUseCase) + .HasForeignKey(c => c.VerifiedCredentialTypeId); + + entity.HasOne(c => c.UseCase) + .WithOne(c => c.VerifiedCredentialAssignedUseCase) + .HasForeignKey(c => c.UseCaseId); + }); + + modelBuilder.Entity() + .HasData( + Enum.GetValues(typeof(VerifiedCredentialTypeKindId)) + .Cast() + .Select(e => new VerifiedCredentialTypeKind(e)) + ); + } + + /// + public override Task SaveChangesAsync(CancellationToken cancellationToken = new()) + { + EnhanceChangedEntries(); + return base.SaveChangesAsync(cancellationToken); + } + + /// + public override int SaveChanges(bool acceptAllChangesOnSuccess) + { + EnhanceChangedEntries(); + return base.SaveChanges(acceptAllChangesOnSuccess); + } + + public override int SaveChanges() + { + EnhanceChangedEntries(); + return base.SaveChanges(); + } + + private void EnhanceChangedEntries() + { + _auditHandler.HandleAuditForChangedEntries( + ChangeTracker.Entries().Where(entry => + entry.State != EntityState.Unchanged && entry.State != EntityState.Detached && + entry.Entity is IAuditableV1).ToImmutableList(), + ChangeTracker.Context); + } +} diff --git a/src/database/SsiCredentialIssuer.Entities/SsiCredentialIssuer.Entities.csproj b/src/database/SsiCredentialIssuer.Entities/SsiCredentialIssuer.Entities.csproj new file mode 100644 index 00000000..6d397738 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Entities/SsiCredentialIssuer.Entities.csproj @@ -0,0 +1,46 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Entities + Org.Eclipse.TractusX.SsiCredentialIssuer.Entities + net7.0 + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + **/IssuerDbContext.cs + + + diff --git a/src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.Designer.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.Designer.cs new file mode 100644 index 00000000..2b3c68d7 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.Designer.cs @@ -0,0 +1,1432 @@ +/******************************************************************************** +// * 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("20240321135530_1.0.0-rc.1")] + partial class _100rc1 + { + /// + 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" + }); + }); + + 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" + }); + }); + + 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 = "SUSTAINABILITY_CREDENTIAL" + }, + new + { + Id = 6, + Label = "QUALITY_CREDENTIAL" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }); + }); + + 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 = "SUSTAINABILITY_FRAMEWORK" + }, + new + { + Id = 6, + Label = "FRAMEWORK_AGREEMENT_QUALITY" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }); + }); + + 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/20240321135530_1.0.0-rc.1.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.cs new file mode 100644 index 00000000..2a110962 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/20240321135530_1.0.0-rc.1.cs @@ -0,0 +1,952 @@ +/******************************************************************************** + * 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; +using System; +using System.Text.Json; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Migrations +{ + /// + public partial class _100rc1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "issuer"); + + migrationBuilder.CreateTable( + name: "audit_company_ssi_detail20240228", + schema: "issuer", + columns: table => new + { + audit_v1id = table.Column(type: "uuid", nullable: false), + id = table.Column(type: "uuid", nullable: false), + bpnl = table.Column(type: "text", nullable: false), + issuer_bpn = table.Column(type: "text", nullable: false), + verified_credential_type_id = table.Column(type: "integer", nullable: false), + company_ssi_detail_status_id = table.Column(type: "integer", nullable: false), + date_created = table.Column(type: "timestamp with time zone", nullable: false), + creator_user_id = table.Column(type: "uuid", nullable: false), + expiry_date = table.Column(type: "timestamp with time zone", nullable: true), + verified_credential_external_type_detail_version_id = table.Column(type: "uuid", nullable: true), + expiry_check_type_id = table.Column(type: "integer", nullable: true), + process_id = table.Column(type: "uuid", nullable: true), + external_credential_id = table.Column(type: "uuid", nullable: true), + credential = table.Column(type: "text", nullable: true), + date_last_changed = table.Column(type: "timestamp with time zone", nullable: true), + last_editor_id = table.Column(type: "uuid", nullable: true), + audit_v1last_editor_id = table.Column(type: "uuid", nullable: true), + audit_v1operation_id = table.Column(type: "integer", nullable: false), + audit_v1date_last_changed = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_audit_company_ssi_detail20240228", x => x.audit_v1id); + }); + + migrationBuilder.CreateTable( + name: "audit_document20240305", + schema: "issuer", + columns: table => new + { + audit_v1id = table.Column(type: "uuid", nullable: false), + id = table.Column(type: "uuid", nullable: false), + date_created = table.Column(type: "timestamp with time zone", nullable: true), + document_hash = table.Column(type: "bytea", nullable: true), + document_content = table.Column(type: "bytea", nullable: true), + document_name = table.Column(type: "text", nullable: true), + media_type_id = table.Column(type: "integer", nullable: true), + document_type_id = table.Column(type: "integer", nullable: true), + document_status_id = table.Column(type: "integer", nullable: true), + company_user_id = table.Column(type: "uuid", nullable: true), + date_last_changed = table.Column(type: "timestamp with time zone", nullable: true), + last_editor_id = table.Column(type: "uuid", nullable: true), + audit_v1date_last_changed = table.Column(type: "timestamp with time zone", nullable: false), + audit_v1last_editor_id = table.Column(type: "uuid", nullable: true), + audit_v1operation_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_audit_document20240305", x => x.audit_v1id); + }); + + migrationBuilder.CreateTable( + name: "company_ssi_detail_statuses", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_company_ssi_detail_statuses", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "document_status", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_document_status", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "document_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_document_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "expiry_check_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_expiry_check_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "media_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_media_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "process_step_statuses", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_process_step_statuses", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "process_step_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_process_step_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "process_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_process_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "use_cases", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + shortname = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_use_cases", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_external_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_external_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_type_kinds", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_type_kinds", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_types", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "integer", nullable: false), + label = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_types", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "documents", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + date_created = table.Column(type: "timestamp with time zone", nullable: false), + document_hash = table.Column(type: "bytea", nullable: false), + document_content = table.Column(type: "bytea", nullable: false), + document_name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + media_type_id = table.Column(type: "integer", nullable: false), + document_type_id = table.Column(type: "integer", nullable: false), + document_status_id = table.Column(type: "integer", nullable: false), + company_user_id = table.Column(type: "uuid", nullable: true), + date_last_changed = table.Column(type: "timestamp with time zone", nullable: true), + last_editor_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_documents", x => x.id); + table.ForeignKey( + name: "fk_documents_document_status_document_status_id", + column: x => x.document_status_id, + principalSchema: "issuer", + principalTable: "document_status", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_documents_document_types_document_type_id", + column: x => x.document_type_id, + principalSchema: "issuer", + principalTable: "document_types", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_documents_media_types_media_type_id", + column: x => x.media_type_id, + principalSchema: "issuer", + principalTable: "media_types", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "processes", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + process_type_id = table.Column(type: "integer", nullable: false), + lock_expiry_date = table.Column(type: "timestamp with time zone", nullable: true), + version = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_processes", x => x.id); + table.ForeignKey( + name: "fk_processes_process_types_process_type_id", + column: x => x.process_type_id, + principalSchema: "issuer", + principalTable: "process_types", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_external_type_detail_versions", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + verified_credential_external_type_id = table.Column(type: "integer", nullable: false), + version = table.Column(type: "text", nullable: true), + template = table.Column(type: "text", nullable: true), + valid_from = table.Column(type: "timestamp with time zone", nullable: false), + expiry = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_external_type_detail_versions", x => x.id); + table.ForeignKey( + name: "fk_verified_credential_external_type_detail_versions_verified_", + column: x => x.verified_credential_external_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_external_types", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_type_assigned_external_types", + schema: "issuer", + columns: table => new + { + verified_credential_type_id = table.Column(type: "integer", nullable: false), + verified_credential_external_type_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_type_assigned_external_types", x => new { x.verified_credential_type_id, x.verified_credential_external_type_id }); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_external_types_verified_c", + column: x => x.verified_credential_external_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_external_types", + principalColumn: "id"); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_external_types_verified_c1", + column: x => x.verified_credential_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_types", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_type_assigned_kinds", + schema: "issuer", + columns: table => new + { + verified_credential_type_id = table.Column(type: "integer", nullable: false), + verified_credential_type_kind_id = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_type_assigned_kinds", x => new { x.verified_credential_type_id, x.verified_credential_type_kind_id }); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_kinds_verified_credential", + column: x => x.verified_credential_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_types", + principalColumn: "id"); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_kinds_verified_credential1", + column: x => x.verified_credential_type_kind_id, + principalSchema: "issuer", + principalTable: "verified_credential_type_kinds", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "verified_credential_type_assigned_use_cases", + schema: "issuer", + columns: table => new + { + verified_credential_type_id = table.Column(type: "integer", nullable: false), + use_case_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_verified_credential_type_assigned_use_cases", x => new { x.verified_credential_type_id, x.use_case_id }); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_use_cases_use_cases_use_c", + column: x => x.use_case_id, + principalSchema: "issuer", + principalTable: "use_cases", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_verified_credential_type_assigned_use_cases_verified_creden", + column: x => x.verified_credential_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_types", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "process_steps", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + process_step_type_id = table.Column(type: "integer", nullable: false), + process_step_status_id = table.Column(type: "integer", nullable: false), + process_id = table.Column(type: "uuid", nullable: false), + date_created = table.Column(type: "timestamp with time zone", nullable: false), + date_last_changed = table.Column(type: "timestamp with time zone", nullable: true), + message = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_process_steps", x => x.id); + table.ForeignKey( + name: "fk_process_steps_process_step_statuses_process_step_status_id", + column: x => x.process_step_status_id, + principalSchema: "issuer", + principalTable: "process_step_statuses", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_process_steps_process_step_types_process_step_type_id", + column: x => x.process_step_type_id, + principalSchema: "issuer", + principalTable: "process_step_types", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_process_steps_processes_process_id", + column: x => x.process_id, + principalSchema: "issuer", + principalTable: "processes", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "company_ssi_details", + schema: "issuer", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + bpnl = table.Column(type: "text", nullable: false), + issuer_bpn = table.Column(type: "text", nullable: false), + verified_credential_type_id = table.Column(type: "integer", nullable: false), + company_ssi_detail_status_id = table.Column(type: "integer", nullable: false), + date_created = table.Column(type: "timestamp with time zone", nullable: false), + creator_user_id = table.Column(type: "uuid", nullable: false), + expiry_date = table.Column(type: "timestamp with time zone", nullable: true), + verified_credential_external_type_detail_version_id = table.Column(type: "uuid", nullable: true), + expiry_check_type_id = table.Column(type: "integer", nullable: true), + process_id = table.Column(type: "uuid", nullable: true), + external_credential_id = table.Column(type: "uuid", nullable: true), + credential = table.Column(type: "text", nullable: true), + date_last_changed = table.Column(type: "timestamp with time zone", nullable: true), + last_editor_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_company_ssi_details", x => x.id); + table.ForeignKey( + name: "fk_company_ssi_details_company_ssi_detail_statuses_company_ssi", + column: x => x.company_ssi_detail_status_id, + principalSchema: "issuer", + principalTable: "company_ssi_detail_statuses", + principalColumn: "id"); + table.ForeignKey( + name: "fk_company_ssi_details_expiry_check_types_expiry_check_type_id", + column: x => x.expiry_check_type_id, + principalSchema: "issuer", + principalTable: "expiry_check_types", + principalColumn: "id"); + table.ForeignKey( + name: "fk_company_ssi_details_processes_process_id", + column: x => x.process_id, + principalSchema: "issuer", + principalTable: "processes", + principalColumn: "id"); + table.ForeignKey( + name: "fk_company_ssi_details_verified_credential_external_type_detai", + column: x => x.verified_credential_external_type_detail_version_id, + principalSchema: "issuer", + principalTable: "verified_credential_external_type_detail_versions", + principalColumn: "id"); + table.ForeignKey( + name: "fk_company_ssi_details_verified_credential_types_verified_cred", + column: x => x.verified_credential_type_id, + principalSchema: "issuer", + principalTable: "verified_credential_types", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "company_ssi_detail_assigned_documents", + schema: "issuer", + columns: table => new + { + document_id = table.Column(type: "uuid", nullable: false), + company_ssi_detail_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_company_ssi_detail_assigned_documents", x => new { x.document_id, x.company_ssi_detail_id }); + table.ForeignKey( + name: "fk_company_ssi_detail_assigned_documents_company_ssi_details_c", + column: x => x.company_ssi_detail_id, + principalSchema: "issuer", + principalTable: "company_ssi_details", + principalColumn: "id"); + table.ForeignKey( + name: "fk_company_ssi_detail_assigned_documents_documents_document_id", + column: x => x.document_id, + principalSchema: "issuer", + principalTable: "documents", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "company_ssi_process_data", + schema: "issuer", + columns: table => new + { + company_ssi_detail_id = table.Column(type: "uuid", nullable: false), + schema = table.Column(type: "jsonb", nullable: false), + credential_type_kind_id = table.Column(type: "integer", nullable: false), + client_id = table.Column(type: "text", nullable: true), + client_secret = table.Column(type: "bytea", nullable: true), + initialization_vector = table.Column(type: "bytea", nullable: true), + encryption_mode = table.Column(type: "integer", nullable: true), + holder_wallet_url = table.Column(type: "text", nullable: true), + callback_url = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_company_ssi_process_data", x => x.company_ssi_detail_id); + table.ForeignKey( + name: "fk_company_ssi_process_data_company_ssi_details_company_ssi_de", + column: x => x.company_ssi_detail_id, + principalSchema: "issuer", + principalTable: "company_ssi_details", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_company_ssi_process_data_verified_credential_type_kinds_cre", + column: x => x.credential_type_kind_id, + principalSchema: "issuer", + principalTable: "verified_credential_type_kinds", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "company_ssi_detail_statuses", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "PENDING" }, + { 2, "ACTIVE" }, + { 3, "REVOKED" }, + { 4, "INACTIVE" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "document_status", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 2, "ACTIVE" }, + { 3, "INACTIVE" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "document_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "PRESENTATION" }, + { 2, "CREDENTIAL" }, + { 3, "VERIFIED_CREDENTIAL" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "expiry_check_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "ONE_MONTH" }, + { 2, "TWO_WEEKS" }, + { 3, "ONE_DAY" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "media_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "JPEG" }, + { 2, "GIF" }, + { 3, "PNG" }, + { 4, "SVG" }, + { 5, "TIFF" }, + { 6, "PDF" }, + { 7, "JSON" }, + { 8, "PEM" }, + { 9, "CA_CERT" }, + { 10, "PKX_CER" }, + { 11, "OCTET" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "process_step_statuses", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "TODO" }, + { 2, "DONE" }, + { 3, "SKIPPED" }, + { 4, "FAILED" }, + { 5, "DUPLICATE" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "process_step_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "CREATE_CREDENTIAL" }, + { 2, "SIGN_CREDENTIAL" }, + { 3, "SAVE_CREDENTIAL_DOCUMENT" }, + { 4, "CREATE_CREDENTIAL_FOR_HOLDER" }, + { 5, "TRIGGER_CALLBACK" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "process_types", + columns: new[] { "id", "label" }, + values: new object[] { 1, "CREATE_CREDENTIAL" }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "verified_credential_external_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "TRACEABILITY_CREDENTIAL" }, + { 2, "PCF_CREDENTIAL" }, + { 3, "BEHAVIOR_TWIN_CREDENTIAL" }, + { 4, "VEHICLE_DISMANTLE" }, + { 5, "SUSTAINABILITY_CREDENTIAL" }, + { 6, "QUALITY_CREDENTIAL" }, + { 7, "BUSINESS_PARTNER_NUMBER" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "verified_credential_type_kinds", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "FRAMEWORK" }, + { 2, "MEMBERSHIP" }, + { 3, "BPN" } + }); + + migrationBuilder.InsertData( + schema: "issuer", + table: "verified_credential_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 1, "TRACEABILITY_FRAMEWORK" }, + { 2, "PCF_FRAMEWORK" }, + { 3, "BEHAVIOR_TWIN_FRAMEWORK" }, + { 4, "DISMANTLER_CERTIFICATE" }, + { 5, "SUSTAINABILITY_FRAMEWORK" }, + { 6, "FRAMEWORK_AGREEMENT_QUALITY" }, + { 7, "BUSINESS_PARTNER_NUMBER" } + }); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_detail_assigned_documents_company_ssi_detail_id", + schema: "issuer", + table: "company_ssi_detail_assigned_documents", + column: "company_ssi_detail_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_details_company_ssi_detail_status_id", + schema: "issuer", + table: "company_ssi_details", + column: "company_ssi_detail_status_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_details_expiry_check_type_id", + schema: "issuer", + table: "company_ssi_details", + column: "expiry_check_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_details_process_id", + schema: "issuer", + table: "company_ssi_details", + column: "process_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_details_verified_credential_external_type_detai", + schema: "issuer", + table: "company_ssi_details", + column: "verified_credential_external_type_detail_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_details_verified_credential_type_id", + schema: "issuer", + table: "company_ssi_details", + column: "verified_credential_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_company_ssi_process_data_credential_type_kind_id", + schema: "issuer", + table: "company_ssi_process_data", + column: "credential_type_kind_id"); + + migrationBuilder.CreateIndex( + name: "ix_documents_document_status_id", + schema: "issuer", + table: "documents", + column: "document_status_id"); + + migrationBuilder.CreateIndex( + name: "ix_documents_document_type_id", + schema: "issuer", + table: "documents", + column: "document_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_documents_media_type_id", + schema: "issuer", + table: "documents", + column: "media_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_process_steps_process_id", + schema: "issuer", + table: "process_steps", + column: "process_id"); + + migrationBuilder.CreateIndex( + name: "ix_process_steps_process_step_status_id", + schema: "issuer", + table: "process_steps", + column: "process_step_status_id"); + + migrationBuilder.CreateIndex( + name: "ix_process_steps_process_step_type_id", + schema: "issuer", + table: "process_steps", + column: "process_step_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_processes_process_type_id", + schema: "issuer", + table: "processes", + column: "process_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_external_type_detail_versions_verified_", + schema: "issuer", + table: "verified_credential_external_type_detail_versions", + columns: new[] { "verified_credential_external_type_id", "version" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_external_types_verified_c", + schema: "issuer", + table: "verified_credential_type_assigned_external_types", + column: "verified_credential_external_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_external_types_verified_c1", + schema: "issuer", + table: "verified_credential_type_assigned_external_types", + column: "verified_credential_type_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_kinds_verified_credential", + schema: "issuer", + table: "verified_credential_type_assigned_kinds", + column: "verified_credential_type_id"); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_kinds_verified_credential1", + schema: "issuer", + table: "verified_credential_type_assigned_kinds", + column: "verified_credential_type_kind_id"); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_use_cases_use_case_id", + schema: "issuer", + table: "verified_credential_type_assigned_use_cases", + column: "use_case_id", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_verified_credential_type_assigned_use_cases_verified_creden", + schema: "issuer", + table: "verified_credential_type_assigned_use_cases", + column: "verified_credential_type_id", + unique: true); + + migrationBuilder.Sql("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\"();"); + + migrationBuilder.Sql("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\"();"); + + migrationBuilder.Sql("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\"();"); + + migrationBuilder.Sql("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\"();"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DROP FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_INSERT_COMPANYSSIDETAIL\"() CASCADE;"); + + migrationBuilder.Sql("DROP FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_COMPANYSSIDETAIL\"() CASCADE;"); + + migrationBuilder.Sql("DROP FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_INSERT_DOCUMENT\"() CASCADE;"); + + migrationBuilder.Sql("DROP FUNCTION \"issuer\".\"LC_TRIGGER_AFTER_UPDATE_DOCUMENT\"() CASCADE;"); + + migrationBuilder.DropTable( + name: "audit_company_ssi_detail20240228", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "audit_document20240305", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "company_ssi_detail_assigned_documents", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "company_ssi_process_data", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "process_steps", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_type_assigned_external_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_type_assigned_kinds", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_type_assigned_use_cases", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "documents", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "company_ssi_details", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "process_step_statuses", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "process_step_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_type_kinds", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "use_cases", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "document_status", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "document_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "media_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "company_ssi_detail_statuses", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "expiry_check_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "processes", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_external_type_detail_versions", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "process_types", + schema: "issuer"); + + migrationBuilder.DropTable( + name: "verified_credential_external_types", + schema: "issuer"); + } + } +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs b/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs new file mode 100644 index 00000000..bf3c8653 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Migrations/IssuerDbContextModelSnapshot.cs @@ -0,0 +1,1425 @@ +/******************************************************************************** +// * 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; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Migrations +{ + [DbContext(typeof(IssuerDbContext))] + partial class IssuerDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(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" + }); + }); + + 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" + }); + }); + + 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 = "SUSTAINABILITY_CREDENTIAL" + }, + new + { + Id = 6, + Label = "QUALITY_CREDENTIAL" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }); + }); + + 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 = "SUSTAINABILITY_FRAMEWORK" + }, + new + { + Id = 6, + Label = "FRAMEWORK_AGREEMENT_QUALITY" + }, + new + { + Id = 7, + Label = "BUSINESS_PARTNER_NUMBER" + }); + }); + + 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/Program.cs b/src/database/SsiCredentialIssuer.Migrations/Program.cs new file mode 100644 index 00000000..05eca5fa --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Program.cs @@ -0,0 +1,64 @@ +/******************************************************************************** + * 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 Laraue.EfCoreTriggers.PostgreSql.Extensions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library.DependencyInjection; +using Serilog; +using System.Reflection; + +LoggingExtensions.EnsureInitialized(); +Log.Information("Starting process"); +try +{ + var host = Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services + .AddProcessIdentity(hostContext.Configuration.GetSection("ProcessIdentity")) + .AddDbAuditing() + .AddDbContext(o => + o.UseNpgsql(hostContext.Configuration.GetConnectionString("IssuerDb"), + x => x.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_issuer")) + .UsePostgreSqlTriggers()) + .AddDatabaseInitializer(hostContext.Configuration.GetSection("Seeding")); + }) + .AddLogging() + .Build(); + + await host.Services.InitializeDatabasesAsync(); // We don't actually run anything here. The magic happens in InitializeDatabasesAsync +} +catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) +{ + Log.Fatal("Unhandled exception {Exception}", ex); + throw; +} +finally +{ + Log.Information("Process Shutting down..."); + Log.CloseAndFlush(); +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Properties/launchSettings.json b/src/database/SsiCredentialIssuer.Migrations/Properties/launchSettings.json new file mode 100644 index 00000000..cb41575e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "SsiCredentialIssuer.Migrations": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchInsertSeeder.cs b/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchInsertSeeder.cs new file mode 100644 index 00000000..0c59abf8 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchInsertSeeder.cs @@ -0,0 +1,108 @@ +/******************************************************************************** + * 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.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Seeder; + +/// +/// Seeder to seed the base entities (those with an id as primary key) +/// +public class BatchInsertSeeder : ICustomSeeder +{ + private readonly IssuerDbContext _context; + private readonly ILogger _logger; + private readonly SeederSettings _settings; + + /// + /// Constructor + /// + /// The database context + /// The logger + /// The options + public BatchInsertSeeder(IssuerDbContext context, ILogger logger, IOptions options) + { + _context = context; + _logger = logger; + _settings = options.Value; + } + + /// + public int Order => 1; + + /// + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_settings.DataPaths.Any()) + { + _logger.LogInformation("There a no data paths configured, therefore the {SeederName} will be skipped", nameof(BatchUpdateSeeder)); + return; + } + + _logger.LogInformation("Start BaseEntityBatch Seeder"); + await SeedBaseEntity(cancellationToken); + await SeedTable("company_ssi_process_datas", x => x.CompanySsiDetailId, cancellationToken).ConfigureAwait(false); + await SeedTable("verified_credential_type_assigned_kinds", x => new { x.VerifiedCredentialTypeId, x.VerifiedCredentialTypeKindId }, cancellationToken).ConfigureAwait(false); + await SeedTable("verified_credential_type_assigned_use_cases", x => new { x.VerifiedCredentialTypeId, x.UseCaseId }, cancellationToken).ConfigureAwait(false); + await SeedTable("verified_credential_type_assigned_external_types", x => new { x.VerifiedCredentialTypeId, x.VerifiedCredentialExternalTypeId }, cancellationToken).ConfigureAwait(false); + + await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Finished BaseEntityBatch Seeder"); + } + + private async Task SeedBaseEntity(CancellationToken cancellationToken) + { + await SeedTableForBaseEntity("documents", cancellationToken).ConfigureAwait(false); + await SeedTableForBaseEntity("use_cases", cancellationToken).ConfigureAwait(false); + await SeedTableForBaseEntity("process_steps", cancellationToken).ConfigureAwait(false); + await SeedTableForBaseEntity("processes", cancellationToken).ConfigureAwait(false); + await SeedTableForBaseEntity("verified_credential_external_type_detail_versions", cancellationToken).ConfigureAwait(false); + await SeedTableForBaseEntity("company_ssi_details", cancellationToken).ConfigureAwait(false); + } + + private async Task SeedTableForBaseEntity(string fileName, CancellationToken cancellationToken) where T : class, IBaseEntity + { + await SeedTable(fileName, x => x.Id, cancellationToken).ConfigureAwait(false); + } + + private async Task SeedTable(string fileName, Func keySelector, CancellationToken cancellationToken) where T : class + { + _logger.LogInformation("Start seeding {Filename}", fileName); + var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); + var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); + _logger.LogInformation("Found {ElementCount} data", data.Count); + if (data.Any()) + { + var typeName = typeof(T).Name; + _logger.LogInformation("Started to Seed {TableName}", typeName); + data = data.GroupJoin(_context.Set(), keySelector, keySelector, (d, dbEntry) => new { d, dbEntry }) + .SelectMany(t => t.dbEntry.DefaultIfEmpty(), (t, x) => new { t, x }) + .Where(t => t.x == null) + .Select(t => t.t.d).ToList(); + _logger.LogInformation("Seeding {DataCount} {TableName}", data.Count, typeName); + await _context.Set().AddRangeAsync(data, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Seeded {TableName}", typeName); + } + } +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchUpdateSeeder.cs b/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchUpdateSeeder.cs new file mode 100644 index 00000000..3fcc80a4 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/BatchUpdateSeeder.cs @@ -0,0 +1,103 @@ +/******************************************************************************** + * 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.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Seeder; + +/// +/// Seeder to seed the base entities (those with an id as primary key) +/// +public class BatchUpdateSeeder : ICustomSeeder +{ + private readonly IssuerDbContext _context; + private readonly ILogger _logger; + private readonly SeederSettings _settings; + + /// + /// Constructor + /// + /// The database context + /// The logger + /// The options + public BatchUpdateSeeder(IssuerDbContext context, ILogger logger, IOptions options) + { + _context = context; + _logger = logger; + _settings = options.Value; + } + + /// + public int Order => 2; + + /// + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_settings.DataPaths.Any()) + { + _logger.LogInformation("There a no data paths configured, therefore the {SeederName} will be skipped", nameof(BatchUpdateSeeder)); + return; + } + + _logger.LogInformation("Start BaseEntityBatch Seeder"); + + await SeedTable("verified_credential_external_type_detail_versions", + x => x.Id, + x => x.dataEntity.Template != x.dbEntity.Template || x.dataEntity.Expiry != x.dbEntity.Expiry || x.dataEntity.ValidFrom != x.dbEntity.ValidFrom, + (dbEntry, entry) => + { + dbEntry.Template = entry.Template; + dbEntry.Expiry = entry.Expiry; + dbEntry.ValidFrom = entry.ValidFrom; + }, cancellationToken).ConfigureAwait(false); + + await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Finished BaseEntityBatch Seeder"); + } + + private async Task SeedTable(string fileName, Func keySelector, Func<(T dataEntity, T dbEntity), bool> whereClause, Action updateEntries, CancellationToken cancellationToken) where T : class + { + _logger.LogInformation("Start seeding {Filename}", fileName); + var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); + var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); + _logger.LogInformation("Found {ElementCount} data", data.Count); + if (data.Any()) + { + var typeName = typeof(T).Name; + var entriesForUpdate = data + .Join(_context.Set(), keySelector, keySelector, (dataEntry, dbEntry) => (DataEntry: dataEntry, DbEntry: dbEntry)) + .Where(whereClause.Invoke) + .ToList(); + if (entriesForUpdate.Any()) + { + _logger.LogInformation("Started to Update {EntryCount} entries of {TableName}", entriesForUpdate.Count, typeName); + foreach (var entry in entriesForUpdate) + { + updateEntries.Invoke(entry.DbEntry, entry.DataEntry); + } + + _logger.LogInformation("Updated {TableName}", typeName); + } + } + } +} diff --git a/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json new file mode 100644 index 00000000..6c33df1d --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/use_cases.json @@ -0,0 +1,57 @@ +[ + { + "id": "06b243a4-ba51-4bf3-bc40-5d79a2231b86", + "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", + "shortname": "RTC" + }, + { + "id": "06b243a4-ba51-4bf3-bc40-5d79a2231b90", + "name": "Modular Production", + "shortname": "MP" + }, + { + "id": "1aacde78-35ec-4df3-ba1e-f988cddcbbd8", + "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", + "shortname": "DCM" + }, + { + "id": "6909ccc7-37c8-4088-99ab-790f20702460", + "name": "Business Partner Management", + "shortname": "BPDM" + }, + { + "id": "c065a349-f649-47f8-94d5-1a504a855419", + "name": "Quality Management", + "shortname": "QM" + }, + { + "id": "b3948771-3372-4568-9e0e-acca4e674098", + "name": "Behavior Twin", + "shortname": "BT" + } +] \ 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 new file mode 100644 index 00000000..58bb092c --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.consortia.json @@ -0,0 +1,42 @@ +[ + { + "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, + "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, + "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", + "template": null, + "valid_from": "2024-01-01 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" + }, + { + "id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", + "verified_credential_external_type_id": 4, + "version": null, + "template": null, + "valid_from": "2024-01-01 00:00:00.000000 +00:00", + "expiry": "2999-12-31 23:59:59.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 new file mode 100644 index 00000000..ee5ccd40 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_external_type_detail_versions.json @@ -0,0 +1,26 @@ +[ + { + "id": "1268a76a-ca19-4dd8-b932-01f24071d560", + "verified_credential_external_type_id": 1, + "version": "1.0.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" + }, + { + "id": "1268a76a-ca19-4dd8-b932-01f24071d561", + "verified_credential_external_type_id": 2, + "version": "1.0.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" + }, + { + "id": "37aa6259-b452-4d50-b09e-827929dcfa15", + "verified_credential_external_type_id": 6, + "version": "1.0.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" + } +] 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 new file mode 100644 index 00000000..c07dc64e --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_external_types.json @@ -0,0 +1,26 @@ +[ + { + "verified_credential_external_type_id": 1, + "verified_credential_type_id": 1 + }, + { + "verified_credential_external_type_id": 2, + "verified_credential_type_id": 2 + }, + { + "verified_credential_external_type_id": 3, + "verified_credential_type_id": 3 + }, + { + "verified_credential_external_type_id": 4, + "verified_credential_type_id": 4 + }, + { + "verified_credential_external_type_id": 5, + "verified_credential_type_id": 5 + }, + { + "verified_credential_external_type_id": 6, + "verified_credential_type_id": 6 + } +] 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 new file mode 100644 index 00000000..365789bd --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_kinds.json @@ -0,0 +1,30 @@ +[ + { + "verified_credential_type_id": 1, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 2, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 3, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 4, + "verified_credential_type_kind_id": 2 + }, + { + "verified_credential_type_id": 5, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 6, + "verified_credential_type_kind_id": 1 + }, + { + "verified_credential_type_id": 7, + "verified_credential_type_kind_id": 3 + } +] 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 new file mode 100644 index 00000000..92d3a49f --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/Seeder/Data/verified_credential_type_assigned_use_cases.json @@ -0,0 +1,22 @@ +[ + { + "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" + }, + { + "verified_credential_type_id": 5, + "use_case_id": "1aacde78-35ec-4df3-ba1e-f988cddcbbd8" + }, + { + "verified_credential_type_id": 6, + "use_case_id": "c065a349-f649-47f8-94d5-1a504a855419" + } +] diff --git a/src/database/SsiCredentialIssuer.Migrations/SsiCredentialIssuer.Migrations.csproj b/src/database/SsiCredentialIssuer.Migrations/SsiCredentialIssuer.Migrations.csproj new file mode 100644 index 00000000..7741e998 --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/SsiCredentialIssuer.Migrations.csproj @@ -0,0 +1,82 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations + Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations + net7.0 + enable + enable + 31645f70-6335-4833-b3b4-4826ca778084 + Linux + ..\..\.. + True + Exe + + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/database/SsiCredentialIssuer.Migrations/appsettings.json b/src/database/SsiCredentialIssuer.Migrations/appsettings.json new file mode 100644 index 00000000..8a8bb05b --- /dev/null +++ b/src/database/SsiCredentialIssuer.Migrations/appsettings.json @@ -0,0 +1,39 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Org.Eclipse.TractusX.SsiCredentialIssuer": "Information", + "Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations": "Warning" + } + }, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithProcessId", + "WithThreadId" + ], + "Properties": { + "Application": "Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations" + } + }, + "ConnectionStrings": { + "IssuerDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" + }, + "Seeding": { + "DataPaths": [ + "Seeder/Data" + ], + "TestDataEnvironments": [] + }, + "ProcessIdentity": { + "IdentityId": "" + } +} diff --git a/src/externalservices/Callback.Service/Callback.Service.csproj b/src/externalservices/Callback.Service/Callback.Service.csproj new file mode 100644 index 00000000..3fbe75c7 --- /dev/null +++ b/src/externalservices/Callback.Service/Callback.Service.csproj @@ -0,0 +1,35 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service + Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service + net7.0 + enable + enable + + + + + + + + diff --git a/src/externalservices/Callback.Service/DependencyInjection/CallbackSettings.cs b/src/externalservices/Callback.Service/DependencyInjection/CallbackSettings.cs new file mode 100644 index 00000000..6857e041 --- /dev/null +++ b/src/externalservices/Callback.Service/DependencyInjection/CallbackSettings.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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.Token; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.DependencyInjection; + +public class CallbackSettings : KeyVaultAuthSettings +{ +} diff --git a/src/externalservices/Callback.Service/DependencyInjection/ServiceCollectionExtensions.cs b/src/externalservices/Callback.Service/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..9e9dff45 --- /dev/null +++ b/src/externalservices/Callback.Service/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddCallbackService(this IServiceCollection services, IConfigurationSection section) + { + services.AddOptions() + .Bind(section) + .ValidateOnStart(); + + services.AddTransient>(); + + return services + .AddScoped() + .AddCustomHttpClientWithAuthentication(null); + } +} diff --git a/src/externalservices/Callback.Service/Models/IssuerResponseData.cs b/src/externalservices/Callback.Service/Models/IssuerResponseData.cs new file mode 100644 index 00000000..7f537c31 --- /dev/null +++ b/src/externalservices/Callback.Service/Models/IssuerResponseData.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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.Callback.Service.Models; + +public record IssuerResponseData( + [property: JsonPropertyName("bpn")] string Bpn, + [property: JsonPropertyName("status")] IssuerResponseStatus Status, + [property: JsonPropertyName("message")] string? Message); + +public enum IssuerResponseStatus +{ + SUCCESSFUL = 1, + UNSUCCESSFUL = 2 +} diff --git a/src/externalservices/Callback.Service/Services/CallbackService.cs b/src/externalservices/Callback.Service/Services/CallbackService.cs new file mode 100644 index 00000000..719f3609 --- /dev/null +++ b/src/externalservices/Callback.Service/Services/CallbackService.cs @@ -0,0 +1,55 @@ +/******************************************************************************** + * 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.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Models; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; + +public class CallbackService : ICallbackService +{ + private static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(allowIntegerValues: false) } + }; + + private readonly ITokenService _tokenService; + private readonly CallbackSettings _settings; + + public CallbackService(ITokenService tokenService, IOptions options) + { + _tokenService = tokenService; + _settings = options.Value; + } + + public async Task TriggerCallback(string callbackUrl, IssuerResponseData responseData, CancellationToken cancellationToken) + { + var client = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + await client.PostAsJsonAsync($"{callbackUrl}", responseData, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("callback", HttpAsyncResponseMessageExtension.RecoverOptions.REQUEST_EXCEPTION) + .ConfigureAwait(false); + } +} diff --git a/src/externalservices/Callback.Service/Services/ICallbackService.cs b/src/externalservices/Callback.Service/Services/ICallbackService.cs new file mode 100644 index 00000000..7a04d886 --- /dev/null +++ b/src/externalservices/Callback.Service/Services/ICallbackService.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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.Callback.Service.Models; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; + +public interface ICallbackService +{ + Task TriggerCallback(string callbackUrl, IssuerResponseData responseData, CancellationToken cancellationToken); +} diff --git a/src/externalservices/Portal.Service/DependencyInjection/PortalSettings.cs b/src/externalservices/Portal.Service/DependencyInjection/PortalSettings.cs new file mode 100644 index 00000000..e9e9e456 --- /dev/null +++ b/src/externalservices/Portal.Service/DependencyInjection/PortalSettings.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.Portal.Backend.Framework.Token; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; + +public class PortalSettings : KeyVaultAuthSettings +{ + [Required(AllowEmptyStrings = false)] + public string BaseAddress { get; set; } = null!; +} diff --git a/src/externalservices/Portal.Service/DependencyInjection/ServiceCollectionExtensions.cs b/src/externalservices/Portal.Service/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..8bfe6921 --- /dev/null +++ b/src/externalservices/Portal.Service/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,44 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddPortalService(this IServiceCollection services, IConfigurationSection section) + { + services.AddOptions() + .Bind(section) + .ValidateOnStart(); + + services.AddTransient>(); + + var sp = services.BuildServiceProvider(); + var settings = sp.GetRequiredService>(); + return services + .AddScoped() + .AddCustomHttpClientWithAuthentication(settings.Value.BaseAddress); + } +} diff --git a/src/externalservices/Portal.Service/Models/MailData.cs b/src/externalservices/Portal.Service/Models/MailData.cs new file mode 100644 index 00000000..b3834531 --- /dev/null +++ b/src/externalservices/Portal.Service/Models/MailData.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 System.Text.Json.Serialization; + +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 +); diff --git a/src/externalservices/Portal.Service/Models/NotificationRequest.cs b/src/externalservices/Portal.Service/Models/NotificationRequest.cs new file mode 100644 index 00000000..e07f38c3 --- /dev/null +++ b/src/externalservices/Portal.Service/Models/NotificationRequest.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 System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; + +public record NotificationRequest( + [property: JsonPropertyName("requester")] Guid Requester, + [property: JsonPropertyName("content")] string Content, + [property: JsonPropertyName("notificationTypeId")] NotificationTypeId NotificationTypeId +); diff --git a/src/externalservices/Portal.Service/Models/NotificationTypeId.cs b/src/externalservices/Portal.Service/Models/NotificationTypeId.cs new file mode 100644 index 00000000..e6d3409d --- /dev/null +++ b/src/externalservices/Portal.Service/Models/NotificationTypeId.cs @@ -0,0 +1,156 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; + +/// +/// Possible types of a notification +/// +public enum NotificationTypeId +{ + /// + /// Notification is just an information for the user + /// + INFO = 1, + + /// + /// Notification requires the user to take some kind of action + /// + ACTION = 2, + + /// + /// Welcome message + /// + WELCOME = 3, + + /// + /// Welcome use case explination + /// + WELCOME_USE_CASES = 4, + + /// + /// Welcome - link to service provider marketplace + /// + WELCOME_SERVICE_PROVIDER = 5, + + /// + /// Welcome - link to register connector + /// + WELCOME_CONNECTOR_REGISTRATION = 6, + + /// + /// Welcome - link to apps + /// + WELCOME_APP_MARKETPLACE = 7, + + /// + /// New App Subscription was requested + /// + APP_SUBSCRIPTION_REQUEST = 8, + + /// + /// New App Subscription was activated + /// + APP_SUBSCRIPTION_ACTIVATION = 9, + + /// + /// Connector was registered + /// + CONNECTOR_REGISTERED = 10, + + /// + /// App Release was requested + /// + APP_RELEASE_REQUEST = 11, + + /// + /// Technical user was created + /// + TECHNICAL_USER_CREATION = 12, + + /// + /// Service request + /// + SERVICE_REQUEST = 13, + + /// + /// Activation of a service + /// + SERVICE_ACTIVATION = 14, + + /// + /// Role Added for Active App + /// + APP_ROLE_ADDED = 15, + + /// + /// Approve App to change status from IN_REVIEW to ACTIVE + /// + APP_RELEASE_APPROVAL = 16, + + /// + /// Service Release was requested + /// + SERVICE_RELEASE_REQUEST = 17, + + /// + /// Approve Service to change status from IN_REVIEW to ACTIVE + /// + SERVICE_RELEASE_APPROVAL = 18, + + /// + /// Notification when a app is rejected + /// + APP_RELEASE_REJECTION = 19, + + /// + /// Notification when a service is rejected + /// + SERVICE_RELEASE_REJECTION = 20, + + /// + /// Notification when the user roles are updated + /// + ROLE_UPDATE_CORE_OFFER = 21, + + /// + /// Notification when the user roles are updated for an offer + /// + ROLE_UPDATE_APP_OFFER = 22, + + /// + /// Notification when the url of a subscription is changed + /// + SUBSCRIPTION_URL_UPDATE = 23, + + /// + /// Notification when a credential got approved + /// + CREDENTIAL_APPROVAL = 24, + + /// + /// Notification when a credential got rejected + /// + CREDENTIAL_REJECTED = 25, + + /// + /// Notification when a credential got rejected + /// + CREDENTIAL_EXPIRY = 26 +} diff --git a/src/externalservices/Portal.Service/Portal.Service.csproj b/src/externalservices/Portal.Service/Portal.Service.csproj new file mode 100644 index 00000000..894c46d8 --- /dev/null +++ b/src/externalservices/Portal.Service/Portal.Service.csproj @@ -0,0 +1,35 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service + Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service + net7.0 + enable + enable + + + + + + + + diff --git a/src/externalservices/Portal.Service/Services/IPortalService.cs b/src/externalservices/Portal.Service/Services/IPortalService.cs new file mode 100644 index 00000000..c09725cd --- /dev/null +++ b/src/externalservices/Portal.Service/Services/IPortalService.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.Portal.Service.Models; + +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); +} diff --git a/src/externalservices/Portal.Service/Services/PortalService.cs b/src/externalservices/Portal.Service/Services/PortalService.cs new file mode 100644 index 00000000..fb63a2a7 --- /dev/null +++ b/src/externalservices/Portal.Service/Services/PortalService.cs @@ -0,0 +1,60 @@ +/******************************************************************************** + * 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.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; + +public class PortalService : IPortalService +{ + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + private readonly ITokenService _tokenService; + private readonly PortalSettings _settings; + + public PortalService(ITokenService tokenService, IOptions options) + { + _tokenService = tokenService; + _settings = options.Value; + } + + public async Task AddNotification(string content, Guid requester, NotificationTypeId notificationTypeId, CancellationToken cancellationToken) + { + var client = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + var data = new NotificationRequest(requester, content, notificationTypeId); + await client.PostAsJsonAsync("api/notifications/management", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("notification", HttpAsyncResponseMessageExtension.RecoverOptions.REQUEST_EXCEPTION) + .ConfigureAwait(false); + } + + public async Task TriggerMail(string template, Guid requester, IDictionary mailParameters, CancellationToken cancellationToken) + { + var client = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + var data = new MailData(requester, template, mailParameters); + await client.PostAsJsonAsync("api/administration/mail", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("mail", HttpAsyncResponseMessageExtension.RecoverOptions.REQUEST_EXCEPTION) + .ConfigureAwait(false); + } +} diff --git a/src/externalservices/Wallet.Service/BusinessLogic/IWalletBusinessLogic.cs b/src/externalservices/Wallet.Service/BusinessLogic/IWalletBusinessLogic.cs new file mode 100644 index 00000000..e04edc06 --- /dev/null +++ b/src/externalservices/Wallet.Service/BusinessLogic/IWalletBusinessLogic.cs @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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; +using System.Text.Json; +using EncryptionInformation = Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models.EncryptionInformation; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.BusinessLogic; + +public interface IWalletBusinessLogic +{ + Task CreateCredential(Guid companySsiDetailId, JsonDocument schema, CancellationToken cancellationToken); + Task SignCredential(Guid companySsiDetailId, Guid credentialId, CancellationToken cancellationToken); + Task CreateCredentialForHolder(Guid companySsiDetailId, string holderWalletUrl, string clientId, EncryptionInformation encryptionInformation, string credential, CancellationToken cancellationToken); + Task GetCredential(Guid credentialId, Guid externalCredentialId, VerifiedCredentialTypeKindId kindId, CancellationToken cancellationToken); +} diff --git a/src/externalservices/Wallet.Service/BusinessLogic/WalletBusinessLogic.cs b/src/externalservices/Wallet.Service/BusinessLogic/WalletBusinessLogic.cs new file mode 100644 index 00000000..4695c193 --- /dev/null +++ b/src/externalservices/Wallet.Service/BusinessLogic/WalletBusinessLogic.cs @@ -0,0 +1,122 @@ +/******************************************************************************** + * 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 Json.Schema; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; +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.Wallet.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Reflection; +using System.Security.Cryptography; +using System.Text.Json; +using EncryptionInformation = Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models.EncryptionInformation; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.BusinessLogic; + +public class WalletBusinessLogic : IWalletBusinessLogic +{ + private readonly IWalletService _walletService; + private readonly IIssuerRepositories _repositories; + private readonly WalletSettings _settings; + + public WalletBusinessLogic(IWalletService walletService, IIssuerRepositories repositories, IOptions options) + { + _walletService = walletService; + _repositories = repositories; + _settings = options.Value; + } + + public async Task CreateCredential(Guid companySsiDetailId, JsonDocument schema, CancellationToken cancellationToken) + { + var credentialId = await _walletService.CreateCredential(schema, cancellationToken).ConfigureAwait(false); + _repositories.GetInstance().AttachAndModifyCompanySsiDetails(companySsiDetailId, c => c.ExternalCredentialId = null, c => c.ExternalCredentialId = credentialId); + } + + public async Task SignCredential(Guid companySsiDetailId, Guid credentialId, CancellationToken cancellationToken) + { + var credential = await _walletService.SignCredential(credentialId, cancellationToken).ConfigureAwait(false); + _repositories.GetInstance().AttachAndModifyCompanySsiDetails(companySsiDetailId, c => c.Credential = null, c => c.Credential = credential); + } + + public async Task GetCredential(Guid credentialId, Guid externalCredentialId, VerifiedCredentialTypeKindId kindId, CancellationToken cancellationToken) + { + var credential = await _walletService.GetCredential(externalCredentialId, cancellationToken).ConfigureAwait(false); + await ValidateSchema(kindId, credential, cancellationToken).ConfigureAwait(false); + + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }); + credential.WriteTo(writer); + await writer.FlushAsync(cancellationToken).ConfigureAwait(false); + var documentContent = stream.ToArray(); + var hash = SHA512.HashData(documentContent); + var documentRepository = _repositories.GetInstance(); + var docId = documentRepository.CreateDocument("signed-credential.json", documentContent, hash, MediaTypeId.JSON, DocumentTypeId.VERIFIED_CREDENTIAL, null).Id; + documentRepository.AssignDocumentToCompanySsiDetails(docId, credentialId); + } + + private static async Task ValidateSchema(VerifiedCredentialTypeKindId kindId, JsonDocument content, CancellationToken cancellationToken) + { + var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (location == null) + { + throw new UnexpectedConditionException("Assembly location must be set"); + } + + var path = Path.Combine(location, "Schemas", $"{kindId}Credential.schema.json"); + var schemaJson = await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(false); + + var schema = JsonSchema.FromText(schemaJson); + SchemaRegistry.Global.Register(schema); + var results = schema.Evaluate(content); + if (!results.IsValid) + { + throw new ServiceException($"Invalid schema for type {kindId}"); + } + } + + public async Task CreateCredentialForHolder(Guid companySsiDetailId, string holderWalletUrl, string clientId, EncryptionInformation encryptionInformation, string credential, CancellationToken cancellationToken) + { + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == encryptionInformation.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {encryptionInformation.EncryptionMode} is not configured"); + var secret = CryptoHelper.Decrypt(encryptionInformation.Secret, encryptionInformation.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + + await _walletService + .CreateCredentialForHolder(holderWalletUrl, clientId, secret, credential, cancellationToken) + .ConfigureAwait(false); + + _repositories.GetInstance().AttachAndModifyProcessData(companySsiDetailId, + c => + { + c.ClientId = clientId; + c.ClientSecret = encryptionInformation.Secret; + c.InitializationVector = encryptionInformation.InitializationVector; + c.EncryptionMode = encryptionInformation.EncryptionMode; + }, + c => + { + c.ClientId = null; + c.ClientSecret = null; + c.InitializationVector = null; + c.EncryptionMode = null; + }); + } +} diff --git a/src/externalservices/Wallet.Service/DependencyInjection/ServiceCollectionExtensions.cs b/src/externalservices/Wallet.Service/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..d8e011c0 --- /dev/null +++ b/src/externalservices/Wallet.Service/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.BusinessLogic; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddWalletService(this IServiceCollection services, IConfiguration config) + { + services.AddOptions() + .Bind(config.GetSection("Wallet")) + .ValidateOnStart(); + + services.AddTransient>(); + + var sp = services.BuildServiceProvider(); + var settings = sp.GetRequiredService>(); + return services + .AddScoped() + .AddScoped() + .AddScoped() + .AddCustomHttpClientWithAuthentication(settings.Value.BaseAddress); + } +} diff --git a/src/externalservices/Wallet.Service/DependencyInjection/WalletSettings.cs b/src/externalservices/Wallet.Service/DependencyInjection/WalletSettings.cs new file mode 100644 index 00000000..a802bd45 --- /dev/null +++ b/src/externalservices/Wallet.Service/DependencyInjection/WalletSettings.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.Models.Configuration; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; + +public class WalletSettings : BasicAuthSettings +{ + [Required(AllowEmptyStrings = false)] + public string BaseAddress { get; set; } = null!; + + [Required] + public IEnumerable EncryptionConfigs { get; set; } = null!; + + [Required] + public int EncrptionConfigIndex { get; set; } +} diff --git a/src/externalservices/Wallet.Service/Models/CreateCredentialResponse.cs b/src/externalservices/Wallet.Service/Models/CreateCredentialResponse.cs new file mode 100644 index 00000000..74938f56 --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/CreateCredentialResponse.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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; +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; + +public record CreateCredentialRequest( + [property: JsonPropertyName("application")] string Application, + [property: JsonPropertyName("payload")] CredentialPayload Payload +); + +public record CredentialPayload( + [property: JsonPropertyName("issue")] JsonDocument Issue +); + +public record CreateCredentialResponse( + [property: JsonPropertyName("id")] Guid Id +); diff --git a/src/externalservices/Wallet.Service/Models/CredentialData.cs b/src/externalservices/Wallet.Service/Models/CredentialData.cs new file mode 100644 index 00000000..817c2d9b --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/CredentialData.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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 CredentialData( + [property: JsonPropertyName("id")] Guid Id, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("description")] string Description, + [property: JsonPropertyName("verifiableCredential")] string VerifiableCredential, + [property: JsonPropertyName("application")] string Application, + [property: JsonPropertyName("signing_key_id")] string SigningKeyId +); diff --git a/src/externalservices/Wallet.Service/Models/DeriveCredentialData.cs b/src/externalservices/Wallet.Service/Models/DeriveCredentialData.cs new file mode 100644 index 00000000..5cd9aa53 --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/DeriveCredentialData.cs @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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 DeriveCredentialData( + [property: JsonPropertyName("application")] string Application, + [property: JsonPropertyName("payload")] DeriveCredentialPayload Payload +); + +public record DeriveCredentialPayload( + [property: JsonPropertyName("derive")] DeriveCredential Dervie +); + +public record DeriveCredential( + [property: JsonPropertyName("verifiableCredential")] string Credential +); diff --git a/src/externalservices/Wallet.Service/Models/EncryptionInformation.cs b/src/externalservices/Wallet.Service/Models/EncryptionInformation.cs new file mode 100644 index 00000000..4565d204 --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/EncryptionInformation.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; + +public record EncryptionInformation( + byte[] Secret, + byte[] InitializationVector, + int EncryptionMode +); diff --git a/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs b/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs new file mode 100644 index 00000000..19a6902c --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/GetCredentialResponse.cs @@ -0,0 +1,10 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +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 +); diff --git a/src/externalservices/Wallet.Service/Models/SignCredentialRequest.cs b/src/externalservices/Wallet.Service/Models/SignCredentialRequest.cs new file mode 100644 index 00000000..f3dc0442 --- /dev/null +++ b/src/externalservices/Wallet.Service/Models/SignCredentialRequest.cs @@ -0,0 +1,39 @@ +/******************************************************************************** + * 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 SignCredentialRequest( + [property: JsonPropertyName("payload")] SignPayload Payload +); + +public record SignPayload( + [property: JsonPropertyName("sign")] SignUpdate Sign +); + +public record SignUpdate( + [property: JsonPropertyName("proofMechanism")] string ProofMechanism, + [property: JsonPropertyName("proofType")] string ProofType +); + +public record SignCredentialResponse( + [property: JsonPropertyName("jwt")] string Jwt +); diff --git a/src/externalservices/Wallet.Service/Schemas/BPNCredential.schema.json b/src/externalservices/Wallet.Service/Schemas/BPNCredential.schema.json new file mode 100644 index 00000000..45918c2a --- /dev/null +++ b/src/externalservices/Wallet.Service/Schemas/BPNCredential.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://eclipse-tractusx.github.io/BpnCredential.schema.json", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "@context": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/catenax/credentials/v1.0.0" + ] + }, + "type": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "VerifiableCredential", + "BpnCredential" + ] + } + }, + "issuanceDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "expirationDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "issuer": { + "type": "string" + }, + "credentialSubject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "holderIdentifier": { + "type": "string" + }, + "bpn": { + "type": "string" + } + }, + "required": ["id", "holderIdentifier", "bpn"], + "additionalProperties": false + } + }, + "required": ["id", "@context", "type", "issuanceDate", "expirationDate", "issuer", "credentialSubject"], + "additionalProperties": false +} diff --git a/src/externalservices/Wallet.Service/Schemas/FRAMEWORKCredential.schema.json b/src/externalservices/Wallet.Service/Schemas/FRAMEWORKCredential.schema.json new file mode 100644 index 00000000..182a1ea3 --- /dev/null +++ b/src/externalservices/Wallet.Service/Schemas/FRAMEWORKCredential.schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://eclipse-tractusx.github.io/MembershipCredential.schema.json", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "@context": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/catenax/credentials/v1.0.0" + ] + }, + "type": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuanceDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "expirationDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "issuer": { + "type": "string" + }, + "credentialSubject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "holderIdentifier": { + "type": "string" + }, + "group": { + "type": "string", + "const": "UseCaseFramework" + }, + "useCase": { + "type": "string" + }, + "contractTemplate": { + "type": "string" + }, + "contractVersion": { + "type": "string" + } + }, + "required": ["id", "holderIdentifier", "group", "useCase", "contractTemplate", "contractVersion"], + "additionalProperties": false + } + }, + "required": ["id", "@context", "type", "issuanceDate", "expirationDate", "issuer", "credentialSubject"], + "additionalProperties": false +} diff --git a/src/externalservices/Wallet.Service/Schemas/MEMBERSHIPCredential.schema.json b/src/externalservices/Wallet.Service/Schemas/MEMBERSHIPCredential.schema.json new file mode 100644 index 00000000..1b796039 --- /dev/null +++ b/src/externalservices/Wallet.Service/Schemas/MEMBERSHIPCredential.schema.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://eclipse-tractusx.github.io/MembershipCredential.schema.json", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "@context": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/catenax/credentials/v1.0.0" + ] + }, + "type": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "VerifiableCredential", + "MembershipCredential" + ] + } + }, + "issuanceDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "expirationDate": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|\\+\\d{2}:\\d{2})?$" + }, + "issuer": { + "type": "string" + }, + "credentialSubject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "holderIdentifier": { + "type": "string" + }, + "memberOf": { + "type": "string" + } + }, + "required": ["id", "holderIdentifier", "memberOf"], + "additionalProperties": false + } + }, + "required": ["id", "@context", "type", "issuanceDate", "expirationDate", "issuer", "credentialSubject"], + "additionalProperties": false +} diff --git a/src/externalservices/Wallet.Service/Services/BasicAuthSettings.cs b/src/externalservices/Wallet.Service/Services/BasicAuthSettings.cs new file mode 100644 index 00000000..3589d2c0 --- /dev/null +++ b/src/externalservices/Wallet.Service/Services/BasicAuthSettings.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +public class BasicAuthSettings +{ + [Required(AllowEmptyStrings = false)] + public string ClientId { get; set; } = null!; + + [Required(AllowEmptyStrings = false)] + public string ClientSecret { get; set; } = null!; + + [Required(AllowEmptyStrings = false)] + public string TokenAddress { get; set; } = null!; +} + +public record GetBasicTokenSettings(string HttpClientName, string ClientId, string ClientSecret, string TokenAddress); diff --git a/src/externalservices/Wallet.Service/Services/BasicAuthTokenService.cs b/src/externalservices/Wallet.Service/Services/BasicAuthTokenService.cs new file mode 100644 index 00000000..fae20579 --- /dev/null +++ b/src/externalservices/Wallet.Service/Services/BasicAuthTokenService.cs @@ -0,0 +1,72 @@ +/******************************************************************************** + * 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.HttpClientExtensions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +public class BasicAuthTokenService : IBasicAuthTokenService +{ + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + private readonly IHttpClientFactory _httpClientFactory; + + public BasicAuthTokenService(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + + public async Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken) + { + var tokenParameters = new GetBasicTokenSettings( + $"{typeof(T).Name}Auth", + settings.ClientId, + settings.ClientSecret, + settings.TokenAddress); + + var token = await this.GetBasicTokenAsync(tokenParameters, cancellationToken).ConfigureAwait(false); + + var httpClient = _httpClientFactory.CreateClient(typeof(T).Name); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + return httpClient; + } + + private async Task GetBasicTokenAsync(GetBasicTokenSettings settings, CancellationToken cancellationToken) + { + var formParameters = new Dictionary + { + { "grant_type", "client_credentials" } + }; + using var content = new FormUrlEncodedContent(formParameters); + var authClient = _httpClientFactory.CreateClient(settings.HttpClientName); + var authenticationString = $"{settings.ClientId}:{settings.ClientSecret}"; + var base64String = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(authenticationString)); + + authClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64String); + + var response = await authClient.PostAsync(settings.TokenAddress, content, cancellationToken) + .CatchingIntoServiceExceptionFor("token-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); + + var responseObject = await response.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + return responseObject?.AccessToken; + } +} diff --git a/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs b/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs new file mode 100644 index 00000000..e379cb34 --- /dev/null +++ b/src/externalservices/Wallet.Service/Services/IBasicAuthTokenService.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +public interface IBasicAuthTokenService +{ + Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken); +} diff --git a/src/externalservices/Wallet.Service/Services/IWalletService.cs b/src/externalservices/Wallet.Service/Services/IWalletService.cs new file mode 100644 index 00000000..27365f47 --- /dev/null +++ b/src/externalservices/Wallet.Service/Services/IWalletService.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; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +public interface IWalletService +{ + Task CreateCredential(JsonDocument payload, CancellationToken cancellationToken); + Task SignCredential(Guid credentialId, CancellationToken cancellationToken); + Task CreateCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, string credential, CancellationToken cancellationToken); + Task GetCredential(Guid externalCredentialId, CancellationToken cancellationToken); +} diff --git a/src/externalservices/Wallet.Service/Services/WalletService.cs b/src/externalservices/Wallet.Service/Services/WalletService.cs new file mode 100644 index 00000000..e5fb59ff --- /dev/null +++ b/src/externalservices/Wallet.Service/Services/WalletService.cs @@ -0,0 +1,116 @@ +/******************************************************************************** + * 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.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; + +public class WalletService : IWalletService +{ + private const string NoIdErrorMessage = "Response must contain a valid id"; + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + private readonly IBasicAuthTokenService _basicAuthTokenService; + private readonly WalletSettings _settings; + + public WalletService(IBasicAuthTokenService basicAuthTokenService, IOptions options) + { + _basicAuthTokenService = basicAuthTokenService; + _settings = options.Value; + } + + public async Task CreateCredential(JsonDocument payload, CancellationToken cancellationToken) + { + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(_settings, cancellationToken); + var data = new CreateCredentialRequest("catena-x-portal", new CredentialPayload(payload)); + var result = await client.PostAsJsonAsync("api/v2.0.0/credentials", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("create-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + var response = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + if (response == null) + { + throw new ConflictException(NoIdErrorMessage); + } + + return response.Id; + } + + public async Task SignCredential(Guid credentialId, CancellationToken cancellationToken) + { + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(_settings, cancellationToken); + var data = new SignCredentialRequest(new SignPayload(new SignUpdate("external", "jwt"))); + var result = await client.PatchAsJsonAsync($"/api/v2.0.0/credentials/{credentialId}", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("sign-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + var response = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + if (response is null) + { + throw new ServiceException(NoIdErrorMessage, true); + } + + return response.Jwt; + } + + public async Task GetCredential(Guid externalCredentialId, CancellationToken cancellationToken) + { + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(_settings, cancellationToken); + var result = await client.GetAsync($"/api/v2.0.0/credentials/{externalCredentialId}", cancellationToken) + .CatchingIntoServiceExceptionFor("get-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + var response = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + if (response is null) + { + throw new ServiceException(NoIdErrorMessage, true); + } + + return response.Credential; + } + + public async Task CreateCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, string credential, CancellationToken cancellationToken) + { + var authSettings = new BasicAuthSettings + { + ClientId = clientId, + ClientSecret = clientSecret, + TokenAddress = $"{holderWalletUrl}/oauth/token" + }; + var client = await _basicAuthTokenService.GetBasicAuthorizedClient(authSettings, cancellationToken); + var data = new DeriveCredentialData("catena-x-portal", new DeriveCredentialPayload(new DeriveCredential(credential))); + var result = await client.PostAsJsonAsync("/api/v2.0.0/credentials", data, Options, cancellationToken) + .CatchingIntoServiceExceptionFor("create-holder-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false))) + .ConfigureAwait(false); + var response = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + if (response is null) + { + throw new ServiceException(NoIdErrorMessage, true); + } + + return response.Id; + } +} diff --git a/src/externalservices/Wallet.Service/Wallet.Service.csproj b/src/externalservices/Wallet.Service/Wallet.Service.csproj new file mode 100644 index 00000000..c37e3a95 --- /dev/null +++ b/src/externalservices/Wallet.Service/Wallet.Service.csproj @@ -0,0 +1,67 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service + Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service + net7.0 + enable + enable + + + + + + + + + + + + + + true + Always + PreserveNewest + + + true + Always + PreserveNewest + + + true + Always + PreserveNewest + + + + Always + + + + Always + + + + Always + + + diff --git a/src/issuer/SsiCredentialIssuer.Service/Authentication/CustomClaimTypes.cs b/src/issuer/SsiCredentialIssuer.Service/Authentication/CustomClaimTypes.cs new file mode 100644 index 00000000..688d6877 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Authentication/CustomClaimTypes.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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Authentication; + +public static class CustomClaimTypes +{ + public const string Sub = "sub"; + public const string ClientId = "clientId"; + public const string PreferredUserName = "preferred_username"; + public const string ResourceAccess = "resource_access"; +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Authentication/KeycloakClaimsTransformation.cs b/src/issuer/SsiCredentialIssuer.Service/Authentication/KeycloakClaimsTransformation.cs new file mode 100644 index 00000000..321a1c7b --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Authentication/KeycloakClaimsTransformation.cs @@ -0,0 +1,73 @@ +/******************************************************************************** + * 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.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using System.Json; +using System.Security.Claims; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Authentication +{ + public class KeycloakClaimsTransformation : IClaimsTransformation + { + private readonly JwtBearerOptions _options; + + public KeycloakClaimsTransformation(IOptions options) + { + _options = options.Value; + } + + public Task TransformAsync(ClaimsPrincipal principal) + { + var claimsIdentity = new ClaimsIdentity(); + if (AddRoles(principal, claimsIdentity)) + { + principal.AddIdentity(claimsIdentity); + } + + return Task.FromResult(principal); + } + + private bool AddRoles(ClaimsPrincipal principal, ClaimsIdentity claimsIdentity) => + principal.Claims + .Where(claim => + claim.Type == CustomClaimTypes.ResourceAccess && + claim.ValueType == "JSON") + .SelectMany(claim => + JsonValue.Parse(claim.Value) is JsonObject jsonObject && + jsonObject.TryGetValue( + _options.TokenValidationParameters.ValidAudience, + out var audience) && + audience is JsonObject client && + client.TryGetValue("roles", out var jsonRoles) && + jsonRoles is JsonArray roles + ? roles.Where(x => x.JsonType == JsonType.String) + .Select(role => new Claim(ClaimTypes.Role, role)) + : Enumerable.Empty()) + .IfAny(claims => + { + foreach (var claim in claims) + { + claimsIdentity.AddClaim(claim); + } + }); + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs new file mode 100644 index 00000000..1141396c --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IIssuerBusinessLogic.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +public interface IIssuerBusinessLogic +{ + Task> GetUseCaseParticipationAsync(); + + Task> GetSsiCertificatesAsync(); + + Task> GetCredentials(int page, int size, CompanySsiDetailStatusId? companySsiDetailStatusId, VerifiedCredentialTypeId? credentialTypeId, CompanySsiDetailSorting? sorting); + + Task ApproveCredential(Guid credentialId, CancellationToken cancellationToken); + + Task RejectCredential(Guid credentialId, CancellationToken cancellationToken); + + IAsyncEnumerable GetCertificateTypes(); + Task CreateBpnCredential(CreateBpnCredentialRequest requestData, CancellationToken cancellationToken); + Task CreateMembershipCredential(CreateMembershipCredentialRequest requestData, CancellationToken cancellationToken); + Task CreateFrameworkCredential(CreateFrameworkCredentialRequest requestData, CancellationToken cancellationToken); +} diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs new file mode 100644 index 00000000..8dd4b588 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerBusinessLogic.cs @@ -0,0 +1,516 @@ +/******************************************************************************** + * 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; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; +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.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; +using System.Globalization; +using System.Security.Cryptography; +using System.Text.Json; +using System.Text.RegularExpressions; +using ErrorParameter = Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.ErrorParameter; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +public class IssuerBusinessLogic : IIssuerBusinessLogic +{ + private const string StatusList = "StatusList2021"; + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + private static readonly IEnumerable Context = new[] { "https://www.w3.org/2018/credentials/v1", "https://w3id.org/catenax/credentials/v1.0.0" }; + private static readonly Regex UrlPathInvalidCharsRegex = new("""[""<>#%{}|\\^~\[\]`]+""", RegexOptions.Compiled, TimeSpan.FromSeconds(1)); + + private readonly IIssuerRepositories _repositories; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly IHttpClientFactory _clientFactory; + private readonly IPortalService _portalService; + private readonly IssuerSettings _settings; + private readonly IIdentityData _identity; + + /// + /// Constructor + /// + /// + /// + /// + /// + /// + /// + public IssuerBusinessLogic( + IIssuerRepositories repositories, + IIdentityService identityService, + IDateTimeProvider dateTimeProvider, + IHttpClientFactory clientFactory, + IPortalService portalService, + IOptions options) + { + _repositories = repositories; + _identity = identityService.IdentityData; + _dateTimeProvider = dateTimeProvider; + _clientFactory = clientFactory; + _portalService = portalService; + _settings = options.Value; + } + + /// + public async Task> GetUseCaseParticipationAsync() => + await _repositories + .GetInstance() + .GetUseCaseParticipationForCompany(_identity.Bpnl, _dateTimeProvider.OffsetNow) + .Select(x => new UseCaseParticipationData( + x.UseCase, + x.Description, + x.CredentialType, + x.VerifiedCredentials + .Select(y => + new CompanySsiExternalTypeDetailData( + y.ExternalDetailData, + y.SsiDetailData.CatchingInto( + data => data + .Select(d => new CompanySsiDetailData( + d.CredentialId, + d.ParticipationStatus, + d.ExpiryDate, + d.Documents)) + .SingleOrDefault(), + (InvalidOperationException _) => throw ConflictException.Create(CredentialErrors.MULTIPLE_SSI_DETAIL)))) + .ToList())) + .ToListAsync() + .ConfigureAwait(false); + + /// + public async Task> GetSsiCertificatesAsync() => + await _repositories + .GetInstance() + .GetSsiCertificates(_identity.Bpnl, _dateTimeProvider.OffsetNow) + .Select(x => new CertificateParticipationData( + x.CredentialType, + x.Credentials + .Select(y => + new CompanySsiExternalTypeDetailData( + y.ExternalDetailData, + y.SsiDetailData.CatchingInto( + data => data + .Select(d => new CompanySsiDetailData( + d.CredentialId, + d.ParticipationStatus, + d.ExpiryDate, + d.Documents)) + .SingleOrDefault(), + (InvalidOperationException _) => throw ConflictException.Create(CredentialErrors.MULTIPLE_SSI_DETAIL)))) + .ToList())) + .ToListAsync() + .ConfigureAwait(false); + + /// + public Task> GetCredentials(int page, int size, CompanySsiDetailStatusId? companySsiDetailStatusId, VerifiedCredentialTypeId? credentialTypeId, CompanySsiDetailSorting? sorting) + { + var query = _repositories + .GetInstance() + .GetAllCredentialDetails(companySsiDetailStatusId, credentialTypeId); + var sortedQuery = sorting switch + { + CompanySsiDetailSorting.BpnlAsc or null => query.OrderBy(c => c.Bpnl), + CompanySsiDetailSorting.BpnlDesc => query.OrderByDescending(c => c.Bpnl), + _ => query + }; + + return Pagination.CreateResponseAsync(page, size, _settings.MaxPageSize, (skip, take) => + new Pagination.AsyncSource + ( + query.CountAsync(), + sortedQuery + .Skip(skip) + .Take(take) + .Select(c => new CredentialDetailData( + c.Id, + c.Bpnl, + c.VerifiedCredentialTypeId, + c.VerifiedCredentialType!.VerifiedCredentialTypeAssignedUseCase!.UseCase!.Name, + c.CompanySsiDetailStatusId, + c.ExpiryDate, + c.Documents.Select(d => new DocumentData(d.Id, d.DocumentName, d.DocumentTypeId)), + c.VerifiedCredentialExternalTypeDetailVersion == null + ? null + : new ExternalTypeDetailData( + c.VerifiedCredentialExternalTypeDetailVersion.Id, + c.VerifiedCredentialExternalTypeDetailVersion.VerifiedCredentialExternalTypeId, + c.VerifiedCredentialExternalTypeDetailVersion.Version, + c.VerifiedCredentialExternalTypeDetailVersion.Template, + c.VerifiedCredentialExternalTypeDetailVersion.ValidFrom, + c.VerifiedCredentialExternalTypeDetailVersion.Expiry)) + ).AsAsyncEnumerable() + )); + } + + /// + public async Task ApproveCredential(Guid credentialId, CancellationToken cancellationToken) + { + var companySsiRepository = _repositories.GetInstance(); + var (exists, data) = await companySsiRepository.GetSsiApprovalData(credentialId).ConfigureAwait(false); + ValidateApprovalData(credentialId, exists, data); + + var processId = CreateProcess(); + + var expiry = GetExpiryDate(data.DetailData?.ExpiryDate); + companySsiRepository.AttachAndModifyCompanySsiDetails(credentialId, c => + { + c.CompanySsiDetailStatusId = data.Status; + c.ExpiryDate = DateTimeOffset.MinValue; + c.ProcessId = null; + }, + c => + { + c.CompanySsiDetailStatusId = CompanySsiDetailStatusId.ACTIVE; + c.DateLastChanged = _dateTimeProvider.OffsetNow; + c.ExpiryDate = expiry; + 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 + { + { "requestName", typeValue }, + { "credentialType", typeValue }, + { "expiryDate", expiry.ToString("o", CultureInfo.InvariantCulture) } + }; + await _portalService.TriggerMail("CredentialApproval", _identity.IdentityId, mailParameters, cancellationToken).ConfigureAwait(false); + await _repositories.SaveAsync().ConfigureAwait(false); + } + + private Guid CreateProcess() + { + var processStepRepository = _repositories.GetInstance(); + var processId = processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL).Id; + processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_CREDENTIAL, ProcessStepStatusId.TODO, processId); + return processId; + } + + private static void ValidateApprovalData(Guid credentialId, bool exists, SsiApprovalData data) + { + if (!exists) + { + throw NotFoundException.Create(CredentialErrors.SSI_DETAILS_NOT_FOUND, new ErrorParameter[] { new("credentialId", credentialId.ToString()) }); + } + + if (data.Status != CompanySsiDetailStatusId.PENDING) + { + throw ConflictException.Create(CredentialErrors.CREDENTIAL_NOT_PENDING, new ErrorParameter[] { new("credentialId", credentialId.ToString()), new("status", CompanySsiDetailStatusId.PENDING.ToString()) }); + } + + if (string.IsNullOrWhiteSpace(data.Bpn)) + { + throw UnexpectedConditionException.Create(CredentialErrors.BPN_NOT_SET); + } + + if (data.DetailData == null && data.Kind == VerifiedCredentialTypeKindId.FRAMEWORK) + { + throw ConflictException.Create(CredentialErrors.EXTERNAL_TYPE_DETAIL_ID_NOT_SET); + } + + if (data.Kind != VerifiedCredentialTypeKindId.FRAMEWORK && data.Kind != VerifiedCredentialTypeKindId.MEMBERSHIP && data.Kind != VerifiedCredentialTypeKindId.BPN) + { + throw ConflictException.Create(CredentialErrors.KIND_NOT_SUPPORTED, new ErrorParameter[] { new("kind", data.Kind != null ? data.Kind.Value.ToString() : "empty kind") }); + } + + if (data.Kind == VerifiedCredentialTypeKindId.FRAMEWORK && string.IsNullOrWhiteSpace(data.DetailData!.Version)) + { + throw ConflictException.Create(CredentialErrors.EMPTY_VERSION); + } + + if (data.ProcessId is not null) + { + throw UnexpectedConditionException.Create(CredentialErrors.ALREADY_LINKED_PROCESS); + } + } + + private DateTimeOffset GetExpiryDate(DateTimeOffset? expiryDate) + { + var now = _dateTimeProvider.OffsetNow; + var future = now.AddMonths(12); + var expiry = expiryDate ?? future; + + if (expiry < now) + { + throw ConflictException.Create(CredentialErrors.EXPIRY_DATE_IN_PAST); + } + + return expiry > future ? future : expiry; + } + + /// + public async Task RejectCredential(Guid credentialId, CancellationToken cancellationToken) + { + var companySsiRepository = _repositories.GetInstance(); + var (exists, status, type, processId, processStepIds) = await companySsiRepository.GetSsiRejectionData(credentialId).ConfigureAwait(false); + if (!exists) + { + throw NotFoundException.Create(CredentialErrors.SSI_DETAILS_NOT_FOUND, new ErrorParameter[] { new("credentialId", credentialId.ToString()) }); + } + + if (status != CompanySsiDetailStatusId.PENDING) + { + throw ConflictException.Create(CredentialErrors.CREDENTIAL_NOT_PENDING, new ErrorParameter[] { new("credentialId", credentialId.ToString()), new("status", CompanySsiDetailStatusId.PENDING.ToString()) }); + } + + var typeValue = type.GetEnumValue() ?? throw UnexpectedConditionException.Create(CredentialErrors.CREDENTIAL_TYPE_NOT_FOUND, new ErrorParameter[] { new("verifiedCredentialType", type.ToString()) }); + 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 + { + { "requestName", typeValue }, + { "reason", "Declined by the Operator" } + }; + + await _portalService.TriggerMail("CredentialRejected", _identity.IdentityId, mailParameters, cancellationToken).ConfigureAwait(false); + + companySsiRepository.AttachAndModifyCompanySsiDetails(credentialId, c => + { + c.CompanySsiDetailStatusId = status; + }, + c => + { + c.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + c.DateLastChanged = _dateTimeProvider.OffsetNow; + }); + + if (processId is not null) + { + _repositories.GetInstance().AttachAndModifyProcessSteps( + processStepIds.Select(p => new ValueTuple?, Action>( + p, + ps => ps.ProcessStepStatusId = ProcessStepStatusId.TODO, + ps => ps.ProcessStepStatusId = ProcessStepStatusId.SKIPPED + ))); + } + + await _repositories.SaveAsync().ConfigureAwait(false); + } + + /// + public IAsyncEnumerable GetCertificateTypes() => + _repositories.GetInstance().GetCertificateTypes(_identity.Bpnl); + + public async Task CreateBpnCredential(CreateBpnCredentialRequest requestData, CancellationToken cancellationToken) + { + var companyCredentialDetailsRepository = _repositories.GetInstance(); + var holderDid = await GetHolderInformation(requestData.Holder, cancellationToken).ConfigureAwait(false); + var schemaData = new BpnCredential( + Guid.NewGuid(), + Context, + new[] { "VerifiableCredential", "BpnCredential" }, + "BpnCredential", + "Bpn Credential", + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddMonths(12), + _settings.IssuerDid, + new BpnCredentialSubject( + holderDid, + requestData.BusinessPartnerNumber, + requestData.BusinessPartnerNumber + ), + new CredentialStatus( + _settings.StatusListUrl, + StatusList) + ); + var schema = JsonSerializer.Serialize(schemaData, Options); + return await HandleCredentialProcessCreation(requestData.BusinessPartnerNumber, VerifiedCredentialTypeKindId.BPN, VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, schema, requestData.TechnicalUserDetails, null, requestData.CallbackUrl, companyCredentialDetailsRepository); + } + + public async Task CreateMembershipCredential(CreateMembershipCredentialRequest requestData, CancellationToken cancellationToken) + { + var companyCredentialDetailsRepository = _repositories.GetInstance(); + + var holderDid = await GetHolderInformation(requestData.Holder, cancellationToken).ConfigureAwait(false); + var schemaData = new MembershipCredential( + Guid.NewGuid(), + Context, + new[] { "VerifiableCredential", "MembershipCredential" }, + "MembershipCredential", + "Membership Credential", + DateTimeOffset.UtcNow, + DateTimeOffset.UtcNow.AddMonths(12), + _settings.IssuerDid, + new MembershipCredentialSubject( + holderDid, + requestData.HolderBpn, + requestData.MemberOf + ), + new CredentialStatus( + _settings.StatusListUrl, + StatusList) + ); + var schema = JsonSerializer.Serialize(schemaData, Options); + return await HandleCredentialProcessCreation(requestData.HolderBpn, VerifiedCredentialTypeKindId.MEMBERSHIP, VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, schema, requestData.TechnicalUserDetails, null, requestData.CallbackUrl, companyCredentialDetailsRepository); + } + + public async Task CreateFrameworkCredential(CreateFrameworkCredentialRequest requestData, CancellationToken cancellationToken) + { + var companyCredentialDetailsRepository = _repositories.GetInstance(); + var result = await companyCredentialDetailsRepository.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(requestData.UseCaseFrameworkVersionId, requestData.UseCaseFrameworkId).ConfigureAwait(false); + if (!result.Exists) + { + throw ControllerArgumentException.Create(CredentialErrors.EXTERNAL_TYPE_DETAIL_NOT_FOUND, new ErrorParameter[] { new("verifiedCredentialExternalTypeDetailId", requestData.UseCaseFrameworkId.ToString()) }); + } + + if (result.Expiry < _dateTimeProvider.OffsetNow) + { + throw ControllerArgumentException.Create(CredentialErrors.EXPIRY_DATE_IN_PAST); + } + + if (string.IsNullOrWhiteSpace(result.Version)) + { + throw ControllerArgumentException.Create(CredentialErrors.EMPTY_VERSION); + } + + if (string.IsNullOrWhiteSpace(result.Template)) + { + throw ControllerArgumentException.Create(CredentialErrors.EMPTY_TEMPLATE); + } + + if (result.UseCase.Count() != 1) + { + throw ControllerArgumentException.Create(CredentialErrors.MULTIPLE_USE_CASES); + } + + var useCase = result.UseCase.Single(); + 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}", + DateTimeOffset.UtcNow, + result.Expiry, + _settings.IssuerDid, + new FrameworkCredentialSubject( + holderDid, + requestData.HolderBpn, + "UseCaseFramework", + useCase, + result.Template!, + result.Version! + ), + new CredentialStatus( + _settings.StatusListUrl, + StatusList) + ); + var schema = JsonSerializer.Serialize(schemaData, Options); + return await HandleCredentialProcessCreation(_identity.Bpnl, VerifiedCredentialTypeKindId.FRAMEWORK, requestData.UseCaseFrameworkId, schema, requestData.TechnicalUserDetails, requestData.UseCaseFrameworkVersionId, requestData.CallbackUrl, companyCredentialDetailsRepository); + } + + private async Task GetHolderInformation(string didDocumentLocation, CancellationToken cancellationToken) + { + if (!Uri.TryCreate(didDocumentLocation, UriKind.Absolute, out var uri) || uri.Scheme != "https" || !string.IsNullOrEmpty(uri.Query) || !string.IsNullOrEmpty(uri.Fragment) || UrlPathInvalidCharsRegex.IsMatch(uri.AbsolutePath)) + { + throw ControllerArgumentException.Create(CredentialErrors.INVALID_DID_LOCATION, null, nameof(didDocumentLocation)); + } + + var client = _clientFactory.CreateClient("didDocumentDownload"); + var result = await client.GetAsync(uri, cancellationToken) + .CatchingIntoServiceExceptionFor("get-did-document").ConfigureAwait(false); + var did = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(false); + if (did == null) + { + throw ConflictException.Create(CredentialErrors.DID_NOT_SET); + } + + return did.Id; + } + + private async Task HandleCredentialProcessCreation( + string bpnl, + VerifiedCredentialTypeKindId kindId, + VerifiedCredentialTypeId typeId, + string schema, + TechnicalUserDetails? technicalUserDetails, + Guid? detailVersionId, + string? callbackUrl, + ICompanySsiDetailsRepository companyCredentialDetailsRepository) + { + var documentContent = System.Text.Encoding.UTF8.GetBytes(schema); + var hash = SHA512.HashData(documentContent); + var documentRepository = _repositories.GetInstance(); + var docId = documentRepository.CreateDocument("schema.json", documentContent, + hash, MediaTypeId.JSON, DocumentTypeId.PRESENTATION, x => + { + x.CompanyUserId = _identity.IdentityId; + x.DocumentStatusId = DocumentStatusId.ACTIVE; + }).Id; + + Guid? processId = null; + var status = CompanySsiDetailStatusId.PENDING; + if (kindId != VerifiedCredentialTypeKindId.FRAMEWORK) + { + processId = CreateProcess(); + status = CompanySsiDetailStatusId.ACTIVE; + } + + var ssiDetailId = companyCredentialDetailsRepository.CreateSsiDetails( + bpnl, + typeId, + status, + _settings.IssuerBpn, + _identity.IdentityId, + c => + { + c.VerifiedCredentialExternalTypeDetailVersionId = detailVersionId; + c.ProcessId = processId; + }).Id; + documentRepository.AssignDocumentToCompanySsiDetails(docId, ssiDetailId); + + companyCredentialDetailsRepository.CreateProcessData(ssiDetailId, JsonDocument.Parse(schema), kindId, + c => + { + if (technicalUserDetails == null) + { + return; + } + + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == _settings.EncrptionConfigIndex) ?? throw new ConfigurationException($"EncryptionModeIndex {_settings.EncrptionConfigIndex} is not configured"); + var (secret, initializationVector) = CryptoHelper.Encrypt(technicalUserDetails.ClientSecret, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + + c.ClientId = technicalUserDetails.ClientId; + c.ClientSecret = secret; + c.InitializationVector = initializationVector; + c.HolderWalletUrl = technicalUserDetails.WalletUrl; + c.CallbackUrl = callbackUrl; + }); + + await _repositories.SaveAsync().ConfigureAwait(false); + return ssiDetailId; + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerSettings.cs b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerSettings.cs new file mode 100644 index 00000000..fda6d5c9 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/BusinessLogic/IssuerSettings.cs @@ -0,0 +1,65 @@ +/******************************************************************************** + * 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.Models.Configuration; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +public class IssuerSettings +{ + /// + /// The Did of the issuer + /// + [Required(AllowEmptyStrings = false)] + public string IssuerDid { get; set; } = null!; + + /// + /// The maximum amount of elements for a page + /// + public int MaxPageSize { get; set; } + + [Required] + public IEnumerable EncryptionConfigs { get; set; } = null!; + + [Required] + public int EncrptionConfigIndex { get; set; } + + [Required(AllowEmptyStrings = false)] + public string StatusListUrl { get; set; } = null!; + + [Required(AllowEmptyStrings = false)] + public string IssuerBpn { get; set; } = null!; +} + +public static class CompanyDataSettingsExtensions +{ + public static IServiceCollection ConfigureCredentialSettings( + this IServiceCollection services, + IConfigurationSection section) + { + services.AddOptions() + .Bind(section) + .ValidateDistinctValues(section) + .ValidateEnumEnumeration(section) + .ValidateOnStart(); + return services; + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs b/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs new file mode 100644 index 00000000..62fd8f06 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Controllers/IssuerController.cs @@ -0,0 +1,174 @@ +/******************************************************************************** + * 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.Portal.Backend.Framework.ErrorHandling.Web; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +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; +using Constants = Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models.Constants; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Controllers; + +/// +/// Creates a new instance of +/// +public static class IssuerController +{ + private const string RequestSsiRole = "request_ssicredential"; + private const string DecisionSsiRole = "decision_ssicredential"; + + public static RouteGroupBuilder MapIssuerApi(this RouteGroupBuilder group) + { + var issuer = group.MapGroup("/issuer"); + + issuer.MapGet("useCaseParticipation", (IIssuerBusinessLogic logic) => logic.GetUseCaseParticipationAsync()) + .WithSwaggerDescription("Gets all use case frameworks and the participation status of the acting company", + "Example: GET: api/issuer/useCaseParticipation") + .RequireAuthorization(r => + { + r.RequireRole("view_use_case_participation"); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(IEnumerable), Constants.JsonContentType) + .Produces(StatusCodes.Status409Conflict, typeof(ErrorResponse), Constants.JsonContentType); + + issuer.MapGet("certificates", (IIssuerBusinessLogic logic) => logic.GetSsiCertificatesAsync()) + .WithSwaggerDescription("Gets all company certificate requests and their status", + "Example: GET: api/issuer/certificates") + .RequireAuthorization(r => + { + r.RequireRole("view_certificates"); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(IEnumerable), Constants.JsonContentType); + + issuer.MapGet("certificateTypes", (IIssuerBusinessLogic logic) => logic.GetCertificateTypes()) + .WithSwaggerDescription("Gets the certificate types for which the company can apply for", + "Example: GET: api/issuer/certificateTypes") + .RequireAuthorization(r => + { + r.RequireRole(RequestSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(IEnumerable), Constants.JsonContentType); + + issuer.MapGet(string.Empty, (IIssuerBusinessLogic logic, [FromQuery] int? page, + [FromQuery] int? size, + [FromQuery] CompanySsiDetailStatusId? companySsiDetailStatusId, + [FromQuery] VerifiedCredentialTypeId? credentialTypeId, + [FromQuery] CompanySsiDetailSorting? sorting) => logic.GetCredentials(page ?? 0, size ?? 15, + companySsiDetailStatusId, credentialTypeId, sorting)) + .WithSwaggerDescription("Gets all outstanding, existing and inactive credentials", + "Example: GET: /api/issuer", + "The page to get", + "Amount of entries", + "OPTIONAL: Filter for the status", + "OPTIONAL: The type of the credential that should be returned", + "OPTIONAL: Search string for the company name", + "Defines the sorting of the list") + .RequireAuthorization(r => r.RequireRole(DecisionSsiRole)) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(IEnumerable), Constants.JsonContentType); + + issuer.MapPost("bpn", ([FromBody] CreateBpnCredentialRequest requestData, CancellationToken cancellationToken, IIssuerBusinessLogic logic) => logic.CreateBpnCredential(requestData, cancellationToken)) + .WithSwaggerDescription("Creates a bpn credential for the given data", + "POST: api/issuer/bpn", + "The request data containing information over the credential that should be created") + .RequireAuthorization(r => + { + r.RequireRole(RequestSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(Guid), contentType: Constants.JsonContentType); + + issuer.MapPost("membership", ([FromBody] CreateMembershipCredentialRequest requestData, CancellationToken cancellationToken, IIssuerBusinessLogic logic) => logic.CreateMembershipCredential(requestData, cancellationToken)) + .WithSwaggerDescription("Creates a membership credential for the given data", + "POST: api/issuer/membership", + "The request data containing information over the credential that should be created") + .RequireAuthorization(r => + { + r.RequireRole(RequestSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(Guid), contentType: Constants.JsonContentType); + + issuer.MapPost("framework", ([FromBody] CreateFrameworkCredentialRequest requestData, CancellationToken cancellationToken, IIssuerBusinessLogic logic) => logic.CreateFrameworkCredential(requestData, cancellationToken)) + .WithSwaggerDescription("Creates a framework credential for the given data", + "POST: api/issuer/framework", + "The request data containing information over the credential that should be created") + .RequireAuthorization(r => + { + r.RequireRole(RequestSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status200OK, typeof(Guid), contentType: Constants.JsonContentType); + + issuer.MapPut("{credentialId}/approval", async ([FromRoute] Guid credentialId, CancellationToken cancellationToken, IIssuerBusinessLogic logic) => + { + await logic.ApproveCredential(credentialId, cancellationToken).ConfigureAwait(false); + return Results.NoContent(); + }) + .WithSwaggerDescription("Approves the given credential and triggers the verified credential creation", + "PUT: api/issuer/{credentialId}/approval", + "Id of the entry that should be approved", + "Cancellation Token") + .RequireAuthorization(r => + { + r.RequireRole(DecisionSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status204NoContent, contentType: Constants.JsonContentType) + .Produces(StatusCodes.Status404NotFound, typeof(ErrorResponse), Constants.JsonContentType) + .Produces(StatusCodes.Status409Conflict, typeof(ErrorResponse), Constants.JsonContentType); + + issuer.MapPut("{credentialId}/reject", async ([FromRoute] Guid credentialId, CancellationToken cancellationToken, IIssuerBusinessLogic logic) => + { + await logic.RejectCredential(credentialId, cancellationToken).ConfigureAwait(false); + return Results.NoContent(); + }) + .WithSwaggerDescription("Rejects the given credential", + "PUT: api/issuer/{credentialId}/reject", + "Id of the entry that should be rejected") + .RequireAuthorization(r => + { + r.RequireRole(DecisionSsiRole); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidBpn)); + r.AddRequirements(new MandatoryIdentityClaimRequirement(PolicyTypeId.ValidIdentity)); + }) + .WithDefaultResponses() + .Produces(StatusCodes.Status204NoContent, contentType: Constants.JsonContentType) + .Produces(StatusCodes.Status404NotFound, typeof(ErrorResponse), Constants.JsonContentType) + .Produces(StatusCodes.Status409Conflict, typeof(ErrorResponse), Constants.JsonContentType); + + return group; + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/CredentialServiceCollectionExtensions.cs b/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/CredentialServiceCollectionExtensions.cs new file mode 100644 index 00000000..e983c6a4 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/DependencyInjection/CredentialServiceCollectionExtensions.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 Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.DependencyInjection; + +public static class CredentialServiceCollectionExtensions +{ + public static IServiceCollection AddCredentialService(this IServiceCollection services, IConfigurationSection section) => + services + .ConfigureCredentialSettings(section) + .AddTransient(); +} diff --git a/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs new file mode 100644 index 00000000..8168311f --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/ErrorHandling/CredentialErrorMessageContainer.cs @@ -0,0 +1,92 @@ +/******************************************************************************** + * 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 CredentialErrorMessageContainer : IErrorMessageContainer +{ + private static readonly IReadOnlyDictionary _messageContainer = new Dictionary { + { CredentialErrors.INVALID_COMPANY, "company {companyId} is not a valid company" }, + { CredentialErrors.INVALID_COMPANY_STATUS, "Company Status is Incorrect" }, + { CredentialErrors.USE_CASE_NOT_FOUND, "UseCaseId {useCaseId} is not available" }, + { CredentialErrors.INVALID_LANGUAGECODE, "language {languageShortName} is not a valid languagecode" }, + { CredentialErrors.COMPANY_NOT_FOUND, "company {companyId} does not exist" }, + { CredentialErrors.COMPANY_ROLE_IDS_CONSENT_STATUS_NULL, "neither CompanyRoleIds nor ConsentStatusDetails should ever be null here" }, + { CredentialErrors.MISSING_AGREEMENTS, "All agreements need to get signed as Active or InActive. Missing consents: [{missingConsents}]" }, + { CredentialErrors.UNASSIGN_ALL_ROLES, "Company can't unassign from all roles, Atleast one Company role need to signed as active" }, + { CredentialErrors.AGREEMENTS_NOT_ASSIGNED_WITH_ROLES, "Agreements not associated with requested companyRoles: [{companyRoles}]" }, + { CredentialErrors.MULTIPLE_SSI_DETAIL, "There should only be one pending or active ssi detail be assigne" }, + { CredentialErrors.EXTERNAL_TYPE_DETAIL_NOT_FOUND, "VerifiedCredentialExternalTypeDetail {verifiedCredentialExternalTypeDetailId} does not exist" }, + { CredentialErrors.EXPIRY_DATE_IN_PAST, "The expiry date must not be in the past" }, + { CredentialErrors.CREDENTIAL_NO_CERTIFICATE, "{credentialTypeId} is not assigned to a certificate" }, + { CredentialErrors.EXTERNAL_TYPE_DETAIL_ID_NOT_SET, "The VerifiedCredentialExternalTypeDetailId must be set" }, + { CredentialErrors.CREDENTIAL_ALREADY_EXISTING, "Credential request already existing" }, + { CredentialErrors.CREDENTIAL_TYPE_NOT_FOUND, "VerifiedCredentialType {verifiedCredentialType} does not exists" }, + { CredentialErrors.SSI_DETAILS_NOT_FOUND, "CompanySsiDetail {credentialId} does not exists" }, + { CredentialErrors.CREDENTIAL_NOT_PENDING, "Credential {credentialId} must be {status}" }, + { CredentialErrors.BPN_NOT_SET, "Bpn should be set for company" }, + { CredentialErrors.EXPIRY_DATE_NOT_SET, "Expiry date must always be set for use cases" }, + { CredentialErrors.EMPTY_VERSION, "External Detail Version must not be null" }, + { CredentialErrors.EMPTY_TEMPLATE, "Template must not be null" }, + { CredentialErrors.KIND_NOT_SUPPORTED, "{kind} is currently not supported" }, + { CredentialErrors.MULTIPLE_USE_CASES, "There must only be one use case" }, + { 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" }, + }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); + + public Type Type { get => typeof(CredentialErrors); } + public IReadOnlyDictionary MessageContainer { get => _messageContainer; } +} + +public enum CredentialErrors +{ + INVALID_COMPANY, + INVALID_COMPANY_STATUS, + USE_CASE_NOT_FOUND, + INVALID_LANGUAGECODE, + COMPANY_NOT_FOUND, + COMPANY_ROLE_IDS_CONSENT_STATUS_NULL, + MISSING_AGREEMENTS, + UNASSIGN_ALL_ROLES, + AGREEMENTS_NOT_ASSIGNED_WITH_ROLES, + MULTIPLE_SSI_DETAIL, + EXTERNAL_TYPE_DETAIL_NOT_FOUND, + EXPIRY_DATE_IN_PAST, + CREDENTIAL_NO_CERTIFICATE, + EXTERNAL_TYPE_DETAIL_ID_NOT_SET, + CREDENTIAL_ALREADY_EXISTING, + CREDENTIAL_TYPE_NOT_FOUND, + SSI_DETAILS_NOT_FOUND, + CREDENTIAL_NOT_PENDING, + BPN_NOT_SET, + EXPIRY_DATE_NOT_SET, + EMPTY_VERSION, + EMPTY_TEMPLATE, + KIND_NOT_SUPPORTED, + MULTIPLE_USE_CASES, + DID_NOT_SET, + ALREADY_LINKED_PROCESS, + INVALID_DID_LOCATION +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Extensions/RouteHandlerBuilderExtensions.cs b/src/issuer/SsiCredentialIssuer.Service/Extensions/RouteHandlerBuilderExtensions.cs new file mode 100644 index 00000000..254fc62e --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Extensions/RouteHandlerBuilderExtensions.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Web; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Extensions; + +public static class RouteHandlerBuilderExtensions +{ + public static RouteHandlerBuilder WithSwaggerDescription(this RouteHandlerBuilder builder, string summary, string description, params string[] parameterDescriptions) => + builder.WithOpenApi(op => + { + op.Summary = summary; + op.Description = description; + for (var i = 0; i < parameterDescriptions.Length; i++) + { + if (i < op.Parameters.Count) + { + op.Parameters[i].Description = parameterDescriptions[i]; + } + } + + return op; + }); + + public static RouteHandlerBuilder WithDefaultResponses(this RouteHandlerBuilder builder) => + builder + .Produces(StatusCodes.Status401Unauthorized, typeof(ErrorResponse), contentType: "application/json") + .Produces(StatusCodes.Status500InternalServerError, typeof(ErrorResponse), "application/json"); +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimTypes.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimTypes.cs new file mode 100644 index 00000000..1dc65bea --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimTypes.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public static class ClaimTypes +{ + public const string PreferredUserName = "preferred_username"; + public const string Bpn = "bpn"; +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityDataBuilder.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityDataBuilder.cs new file mode 100644 index 00000000..8924ed10 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityDataBuilder.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * 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; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public class ClaimsIdentityDataBuilder : IClaimsIdentityDataBuilder +{ + private Guid? _identityId; + private string? _bpnl; + + public Guid IdentityId { get => _identityId ?? throw new UnexpectedConditionException("userId should never be null here (endpoint must be annotated with an identity policy)"); } + public string Bpnl { get => _bpnl ?? throw new UnexpectedConditionException("bpnl should never be null here (endpoint must be annotated with an identity policy)"); } + + public void AddIdentityId(Guid identityId) + { + _identityId = identityId; + } + + public void AddBpnl(string bpnl) + { + _bpnl = bpnl; + } + + public IClaimsIdentityDataBuilderStatus Status { get; set; } = IClaimsIdentityDataBuilderStatus.Initial; +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityService.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityService.cs new file mode 100644 index 00000000..615f4d01 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityService.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public class ClaimsIdentityService : IIdentityService, IIdentityIdService +{ + private readonly IIdentityData _identityData; + + public ClaimsIdentityService(IClaimsIdentityDataBuilder claimsIdentityDataBuilder) + { + _identityData = claimsIdentityDataBuilder; + } + + public IIdentityData IdentityData => _identityData; + + public Guid IdentityId => _identityData.IdentityId; +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityServiceCollectionExtensions.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityServiceCollectionExtensions.cs new file mode 100644 index 00000000..b1bd64cf --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/ClaimsIdentityServiceCollectionExtensions.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public static class ClaimsIdentityServiceCollectionExtensions +{ + public static IServiceCollection AddClaimsIdentityService(this IServiceCollection services) + { + return services + .AddScoped() + .AddTransient() + .AddTransient(); + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/IClaimsIdentityDataBuilder.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/IClaimsIdentityDataBuilder.cs new file mode 100644 index 00000000..fa683915 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/IClaimsIdentityDataBuilder.cs @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public interface IClaimsIdentityDataBuilder : IIdentityData +{ + void AddIdentityId(Guid identityId); + void AddBpnl(string bpnl); + IClaimsIdentityDataBuilderStatus Status { get; set; } +} + +public enum IClaimsIdentityDataBuilderStatus +{ + Initial, + Initialized, + Complete, + Empty +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityData.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityData.cs new file mode 100644 index 00000000..70184a6c --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityData.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public interface IIdentityData +{ + Guid IdentityId { get; } + string Bpnl { get; } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityService.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityService.cs new file mode 100644 index 00000000..6ff8f0cd --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/IIdentityService.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public interface IIdentityService +{ + IIdentityData IdentityData { get; } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/IdentityIdService.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/IdentityIdService.cs new file mode 100644 index 00000000..ebfa998e --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/IdentityIdService.cs @@ -0,0 +1,47 @@ +/******************************************************************************** + * 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.Entities.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public class IdentityIdService : IIdentityIdService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public IdentityIdService(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public Guid IdentityId => GetIdentityId(); + + private Guid GetIdentityId() + { + var preferredUserName = _httpContextAccessor?.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == ClaimTypes.PreferredUserName)?.Value ?? + throw new UnexpectedConditionException("Username must be set here"); + if (Guid.TryParse(preferredUserName, out var identityId)) + { + return identityId; + } + + throw new UnexpectedConditionException("Username must be a uuid"); + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/MandatoryIdentityClaimHandler.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/MandatoryIdentityClaimHandler.cs new file mode 100644 index 00000000..8d8ccca6 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/MandatoryIdentityClaimHandler.cs @@ -0,0 +1,99 @@ +/******************************************************************************** + * 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.Authorization; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using System.Security.Claims; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public class MandatoryIdentityClaimRequirement : IAuthorizationRequirement +{ + public MandatoryIdentityClaimRequirement(PolicyTypeId policyTypeId) + { + PolicyTypeId = policyTypeId; + } + + public PolicyTypeId PolicyTypeId { get; } +} + +public class MandatoryIdentityClaimHandler : AuthorizationHandler +{ + private readonly IClaimsIdentityDataBuilder _identityDataBuilder; + private readonly ILogger _logger; + + public MandatoryIdentityClaimHandler(IClaimsIdentityDataBuilder claimsIdentityDataBuilder, ILogger logger) + { + _identityDataBuilder = claimsIdentityDataBuilder; + _logger = logger; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MandatoryIdentityClaimRequirement requirement) + { + if (_identityDataBuilder.Status == IClaimsIdentityDataBuilderStatus.Initial) + { + InitializeClaims(context.User); + } + + if (_identityDataBuilder.Status == IClaimsIdentityDataBuilderStatus.Empty) + { + context.Fail(); + return Task.CompletedTask; + } + + if (requirement.PolicyTypeId switch + { + PolicyTypeId.ValidIdentity => _identityDataBuilder.IdentityId != Guid.Empty, + PolicyTypeId.ValidBpn => !string.IsNullOrWhiteSpace(_identityDataBuilder.Bpnl), + _ => throw new UnexpectedConditionException($"unexpected PolicyTypeId {requirement.PolicyTypeId}") + }) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + + private void InitializeClaims(ClaimsPrincipal principal) + { + var preferredUserName = principal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.PreferredUserName)?.Value; + if (!Guid.TryParse(preferredUserName, out var identityId)) + { + _logger.LogInformation("Preferred user name {PreferredUserName} couldn't be parsed to uuid", preferredUserName); + _identityDataBuilder.Status = IClaimsIdentityDataBuilderStatus.Empty; + return; + } + + var bpnl = principal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Bpn)?.Value; + if (string.IsNullOrWhiteSpace(bpnl)) + { + _logger.LogInformation("Bpn must be set for user {PreferredUserName}", preferredUserName); + _identityDataBuilder.Status = IClaimsIdentityDataBuilderStatus.Empty; + return; + } + + _identityDataBuilder.AddIdentityId(identityId); + _identityDataBuilder.AddBpnl(bpnl); + _identityDataBuilder.Status = IClaimsIdentityDataBuilderStatus.Complete; + } +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Identity/PolicyTypes.cs b/src/issuer/SsiCredentialIssuer.Service/Identity/PolicyTypes.cs new file mode 100644 index 00000000..14d7dcad --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Identity/PolicyTypes.cs @@ -0,0 +1,32 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +public static class PolicyTypes +{ + public const string ValidIdentity = "ValidIdentity"; + public const string ValidBpn = "ValidBpn"; +} + +public enum PolicyTypeId +{ + ValidIdentity, + ValidBpn +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/Constants.cs b/src/issuer/SsiCredentialIssuer.Service/Models/Constants.cs new file mode 100644 index 00000000..8779280b --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/Constants.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; + +public static class Constants +{ + public const string JsonContentType = "application/json"; +} diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/CreateBpnCredentialRequest.cs b/src/issuer/SsiCredentialIssuer.Service/Models/CreateBpnCredentialRequest.cs new file mode 100644 index 00000000..9554310a --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/CreateBpnCredentialRequest.cs @@ -0,0 +1,48 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; + +public record CreateBpnCredentialRequest( + [property: JsonPropertyName("holder")] string Holder, + [property: JsonPropertyName("businessPartnerNumber")] string BusinessPartnerNumber, + [property: JsonPropertyName("technicalUserDetails")] TechnicalUserDetails? TechnicalUserDetails, + [property: JsonPropertyName("callbackUrl")] string? CallbackUrl +); + +public record CreateMembershipCredentialRequest( + [property: JsonPropertyName("holder")] string Holder, + [property: JsonPropertyName("businessPartnerNumber")] string HolderBpn, + [property: JsonPropertyName("memberOf")] string MemberOf, + [property: JsonPropertyName("technicalUserDetails")] TechnicalUserDetails? TechnicalUserDetails, + [property: JsonPropertyName("callbackUrl")] string? CallbackUrl +); + +public record CreateFrameworkCredentialRequest( + [property: JsonPropertyName("holder")] string Holder, + [property: JsonPropertyName("businessPartnerNumber")] string HolderBpn, + [property: JsonPropertyName("useCaseFrameworkId")] VerifiedCredentialTypeId UseCaseFrameworkId, + [property: JsonPropertyName("useCaseFrameworkVersionId")] Guid UseCaseFrameworkVersionId, + [property: JsonPropertyName("technicalUserDetails")] TechnicalUserDetails? TechnicalUserDetails, + [property: JsonPropertyName("callbackUrl")] string? CallbackUrl +); diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs b/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs new file mode 100644 index 00000000..378379d6 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/CredentialData.cs @@ -0,0 +1,84 @@ +/******************************************************************************** + * 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.Service.Models; + +public record FrameworkCredential( + [property: JsonPropertyName("id")] Guid Id, + [property: JsonPropertyName("@context")] IEnumerable Context, + [property: JsonPropertyName("type")] IEnumerable Type, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("description")] string Description, + [property: JsonPropertyName("issuanceDate")] DateTimeOffset IssuanceDate, + [property: JsonPropertyName("expirationDate")] DateTimeOffset ExpirationDate, + [property: JsonPropertyName("issuer")] string Issuer, + [property: JsonPropertyName("credentialSubject")] FrameworkCredentialSubject CredentialSubject, + [property: JsonPropertyName("credentialStatus")] CredentialStatus CredentialStatus); + +public record FrameworkCredentialSubject( + [property: JsonPropertyName("id")] string Did, + [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("group")] string Group, + [property: JsonPropertyName("useCase")] string UseCase, + [property: JsonPropertyName("contractTemplate")] string ContractTemplate, + [property: JsonPropertyName("contractVersion")] string ContractVersion +); + +public record MembershipCredential( + [property: JsonPropertyName("id")] Guid Id, + [property: JsonPropertyName("@context")] IEnumerable Context, + [property: JsonPropertyName("type")] IEnumerable Type, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("description")] string Description, + [property: JsonPropertyName("issuanceDate")] DateTimeOffset IssuanceDate, + [property: JsonPropertyName("expirationDate")] DateTimeOffset ExpirationDate, + [property: JsonPropertyName("issuer")] string Issuer, + [property: JsonPropertyName("credentialSubject")] MembershipCredentialSubject CredentialSubject, + [property: JsonPropertyName("credentialStatus")] CredentialStatus CredentialStatus); + +public record MembershipCredentialSubject( + [property: JsonPropertyName("id")] string Did, + [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("memberOf")] string MemberOf +); + +public record BpnCredential( + [property: JsonPropertyName("id")] Guid Id, + [property: JsonPropertyName("@context")] IEnumerable Context, + [property: JsonPropertyName("type")] IEnumerable Type, + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("description")] string Description, + [property: JsonPropertyName("issuanceDate")] DateTimeOffset IssuanceDate, + [property: JsonPropertyName("expirationDate")] DateTimeOffset ExpirationDate, + [property: JsonPropertyName("issuer")] string Issuer, + [property: JsonPropertyName("credentialSubject")] BpnCredentialSubject CredentialSubject, + [property: JsonPropertyName("credentialStatus")] CredentialStatus CredentialStatus); + +public record CredentialStatus( + [property: JsonPropertyName("id")] string Id, + [property: JsonPropertyName("type")] string Type +); + +public record BpnCredentialSubject( + [property: JsonPropertyName("id")] string Did, + [property: JsonPropertyName("holderIndentifier")] string HolderIndentifier, + [property: JsonPropertyName("bpn")] string Bpn +); diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/DidDocument.cs b/src/issuer/SsiCredentialIssuer.Service/Models/DidDocument.cs new file mode 100644 index 00000000..de60516a --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/DidDocument.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * 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.Service.Models; + +public record DidDocument( + [property: JsonPropertyName("id")] string Id +); diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationCreationData.cs b/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationCreationData.cs new file mode 100644 index 00000000..085902b6 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationCreationData.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.Service.Models; + +public record UseCaseParticipationCreationData +( + Guid VerifiedCredentialExternalTypeDetailId, + VerifiedCredentialTypeId CredentialType, + IFormFile Document +); + +public record SsiCertificateCreationData +( + Guid? VerifiedCredentialExternalTypeDetailId, + VerifiedCredentialTypeId CredentialType, + IFormFile Document +); diff --git a/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationData.cs b/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationData.cs new file mode 100644 index 00000000..e50c222a --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Models/UseCaseParticipationData.cs @@ -0,0 +1,51 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Models; + +public record UseCaseParticipationData +( + string? UseCase, + string? Description, + VerifiedCredentialTypeId CredentialType, + IEnumerable VerifiedCredentials +); + +public record CertificateParticipationData +( + VerifiedCredentialTypeId CredentialType, + IEnumerable VerifiedCredentials +); + +public record CompanySsiExternalTypeDetailData +( + ExternalTypeDetailData ExternalDetailData, + CompanySsiDetailData? SsiDetailData +); + +public record CompanySsiDetailData +( + Guid CredentialId, + CompanySsiDetailStatusId ParticipationStatus, + DateTimeOffset? ExpiryDate, + IEnumerable Documents +); diff --git a/src/issuer/SsiCredentialIssuer.Service/Program.cs b/src/issuer/SsiCredentialIssuer.Service/Program.cs new file mode 100644 index 00000000..3b1dcd7f --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Program.cs @@ -0,0 +1,64 @@ +/******************************************************************************** + * 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.Authentication; +using Microsoft.AspNetCore.Authorization; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Service; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Web; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Authentication; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Controllers; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; +using System.Text.Json.Serialization; + +const string VERSION = "v1"; + +WebApplicationBuildRunner + .BuildAndRunWebApplication(args, "issuer", VERSION, ".Issuer", builder => + { + builder.Services + .AddTransient() + .AddTransient() + .AddTransient() + .AddClaimsIdentityService() + .AddEndpointsApiExplorer() + .AddIssuerRepositories(builder.Configuration) + .ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }) + .Configure(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }) + .AddCredentialService(builder.Configuration.GetSection("Credential")) + .AddPortalService(builder.Configuration.GetSection("Portal")) + .AddSingleton() + .AddSingleton(); + }, + (app, _) => + { + app.MapGroup("/api") + .WithOpenApi() + .MapIssuerApi(); + }); diff --git a/src/issuer/SsiCredentialIssuer.Service/Properties/launchSettings.json b/src/issuer/SsiCredentialIssuer.Service/Properties/launchSettings.json new file mode 100644 index 00000000..a97f121f --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:30092", + "sslPort": 44344 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/issuer/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "Cors__AllowedOrigins__0": "http://localhost:3000" + } + }, + "SsiCredentialIssuer.Service": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/issuer/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "Cors__AllowedOrigins__0": "http://localhost:3000", + "Cors__AllowedOrigins__1": "https://portal.example.org" + }, + "applicationUrl": "https://localhost:7001;http://localhost:7000" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "publishAllPorts": true, + "useSSL": true + } + } +} \ No newline at end of file diff --git a/src/issuer/SsiCredentialIssuer.Service/SsiCredentialIssuer.Service.csproj b/src/issuer/SsiCredentialIssuer.Service/SsiCredentialIssuer.Service.csproj new file mode 100644 index 00000000..10f89f32 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/SsiCredentialIssuer.Service.csproj @@ -0,0 +1,73 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Service + Org.Eclipse.TractusX.SsiCredentialIssuer.Service + net7.0 + enable + enable + 1c25fec6-9663-495e-9c65-3212002d71ab + Linux + ..\..\.. + True + CS1591 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + Program.cs + + + + + + + + + + + diff --git a/src/issuer/SsiCredentialIssuer.Service/appsettings.json b/src/issuer/SsiCredentialIssuer.Service/appsettings.json new file mode 100644 index 00000000..67731a38 --- /dev/null +++ b/src/issuer/SsiCredentialIssuer.Service/appsettings.json @@ -0,0 +1,71 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Information", + "Org.Eclipse.TractusX.SsiCredentialIssuer.Service": "Information" + } + }, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "WithCorrelationId" + ], + "Properties": { + "Application": "SsiCredentialIssuer.Service" + } + }, + "SwaggerEnabled": "", + "HealthChecks": [], + "Cors": { + "AllowedOrigins": [] + }, + "ConnectionStrings": { + "IssuerDb": "" + }, + "JwtBearerOptions": { + "RequireHttpsMetadata": true, + "MetadataAddress": "", + "SaveToken": true, + "TokenValidationParameters": { + "ValidateIssuer": true, + "ValidIssuer": "", + "ValidateIssuerSigningKey": true, + "ValidAudience": "", + "ValidateAudience": true, + "ValidateLifetime": true, + "ClockSkew": 600000 + } + }, + "Portal": { + "Username": "", + "Password": "", + "ClientId": "", + "GrantType": "", + "ClientSecret": "", + "Scope": "", + "KeycloakTokenAddress": "", + "BaseAddress": "" + }, + "Credential": { + "IssuerDid": "", + "IssuerBpn": "", + "StatusListUrl": "", + "MaxPageSize": 15, + "EncryptionConfigIndex": 0, + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] + } +} diff --git a/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj b/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj new file mode 100644 index 00000000..5a100318 --- /dev/null +++ b/src/processes/CredentialProcess.Library/CredentialProcess.Library.csproj @@ -0,0 +1,36 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library + Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library + net7.0 + enable + enable + + + + + + + + + diff --git a/src/processes/CredentialProcess.Library/CredentialProcessHandler.cs b/src/processes/CredentialProcess.Library/CredentialProcessHandler.cs new file mode 100644 index 00000000..0fa0f39a --- /dev/null +++ b/src/processes/CredentialProcess.Library/CredentialProcessHandler.cs @@ -0,0 +1,132 @@ +/******************************************************************************** + * 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.Callback.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; +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.Wallet.Service.BusinessLogic; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library; + +public class CredentialProcessHandler : ICredentialProcessHandler +{ + private readonly IIssuerRepositories _issuerRepositories; + private readonly IWalletBusinessLogic _walletBusinessLogic; + private readonly ICallbackService _callbackService; + + public CredentialProcessHandler(IIssuerRepositories issuerRepositories, IWalletBusinessLogic walletBusinessLogic, ICallbackService callbackService) + { + _issuerRepositories = issuerRepositories; + _walletBusinessLogic = walletBusinessLogic; + _callbackService = callbackService; + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCredential(Guid credentialId, CancellationToken cancellationToken) + { + var data = await _issuerRepositories.GetInstance().GetCredentialStorageInformationById(credentialId).ConfigureAwait(false); + await _walletBusinessLogic.CreateCredential(credentialId, data.Schema, cancellationToken).ConfigureAwait(false); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SIGN_CREDENTIAL, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SignCredential(Guid credentialId, CancellationToken cancellationToken) + { + var externalCredentialId = await _issuerRepositories.GetInstance().GetWalletCredentialId(credentialId).ConfigureAwait(false); + if (externalCredentialId is null) + { + throw new ConflictException("ExternalCredentialId must be set here"); + } + + await _walletBusinessLogic.SignCredential(credentialId, externalCredentialId!.Value, cancellationToken).ConfigureAwait(false); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SaveCredentialDocument(Guid credentialId, CancellationToken cancellationToken) + { + var (externalCredentialId, kindId, hasEncryptionInformation, callbackUrl) = await _issuerRepositories.GetInstance().GetExternalCredentialAndKindId(credentialId).ConfigureAwait(false); + if (externalCredentialId == null) + { + throw new ConflictException("ExternalCredentialId must be set here"); + } + + await _walletBusinessLogic.GetCredential(credentialId, externalCredentialId.Value, kindId, cancellationToken).ConfigureAwait(false); + var nextProcessStep = callbackUrl == null ? null : Enumerable.Repeat(ProcessStepTypeId.TRIGGER_CALLBACK, 1); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + hasEncryptionInformation + ? Enumerable.Repeat(ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER, 1) + : nextProcessStep, + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCredentialForHolder(Guid credentialId, CancellationToken cancellationToken) + { + var (holderWalletData, credential, encryptionInformation, callbackUrl) = await _issuerRepositories.GetInstance().GetCredentialData(credentialId).ConfigureAwait(false); + if (credential is null) + { + throw new ConflictException("Credential must be set here"); + } + + if (holderWalletData.ClientId == null || holderWalletData.WalletUrl == null) + { + throw new ConflictException("Wallet information must be set"); + } + + if (encryptionInformation.Secret == null || encryptionInformation.InitializationVector == null || encryptionInformation.EncryptionMode == null) + { + throw new ConflictException("Wallet secret must be set"); + } + + await _walletBusinessLogic.CreateCredentialForHolder(credentialId, holderWalletData.WalletUrl, holderWalletData.ClientId, new EncryptionInformation(encryptionInformation.Secret, encryptionInformation.InitializationVector, encryptionInformation.EncryptionMode.Value), credential, cancellationToken).ConfigureAwait(false); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + callbackUrl is null ? null : Enumerable.Repeat(ProcessStepTypeId.TRIGGER_CALLBACK, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerCallback(Guid credentialId, CancellationToken cancellationToken) + { + var (bpn, callbackUrl) = await _issuerRepositories.GetInstance().GetCallbackUrl(credentialId).ConfigureAwait(false); + if (callbackUrl is null) + { + throw new ConflictException("CallbackUrl must be set"); + } + + var issuerResponseData = new IssuerResponseData(bpn, IssuerResponseStatus.SUCCESSFUL, "Successfully created Credential"); + await _callbackService.TriggerCallback(callbackUrl, issuerResponseData, cancellationToken).ConfigureAwait(false); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.DONE, + false, + null); + } +} diff --git a/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs b/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs new file mode 100644 index 00000000..6296675f --- /dev/null +++ b/src/processes/CredentialProcess.Library/DependencyInjection/CredentialHandlerExtensions.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +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) + { + services + .AddTransient() + .AddWalletService(config); + + return services; + } +} diff --git a/src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs b/src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs new file mode 100644 index 00000000..d21b1c9e --- /dev/null +++ b/src/processes/CredentialProcess.Library/ICredentialProcessHandler.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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; + +public interface ICredentialProcessHandler +{ + 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); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SaveCredentialDocument(Guid credentialId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCredentialForHolder(Guid credentialId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> TriggerCallback(Guid credentialId, CancellationToken cancellationToken); +} diff --git a/src/processes/CredentialProcess.Worker/CredentialProcess.Worker.csproj b/src/processes/CredentialProcess.Worker/CredentialProcess.Worker.csproj new file mode 100644 index 00000000..a45ba693 --- /dev/null +++ b/src/processes/CredentialProcess.Worker/CredentialProcess.Worker.csproj @@ -0,0 +1,35 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker + Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker + net7.0 + enable + enable + + + + + + + + diff --git a/src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs b/src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs new file mode 100644 index 00000000..b260f92d --- /dev/null +++ b/src/processes/CredentialProcess.Worker/CredentialProcessTypeExecutor.cs @@ -0,0 +1,115 @@ +/******************************************************************************** + * 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; +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; + +public class CredentialProcessTypeExecutor : IProcessTypeExecutor +{ + private readonly IIssuerRepositories _issuerRepositories; + private readonly ICredentialProcessHandler _credentialProcessHandler; + + private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( + ProcessStepTypeId.CREATE_CREDENTIAL, + ProcessStepTypeId.SIGN_CREDENTIAL, + ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT, + ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER, + ProcessStepTypeId.TRIGGER_CALLBACK); + + private Guid _credentialId; + + public CredentialProcessTypeExecutor( + IIssuerRepositories issuerRepositories, + ICredentialProcessHandler credentialProcessHandler) + { + _issuerRepositories = issuerRepositories; + _credentialProcessHandler = credentialProcessHandler; + } + + public ProcessTypeId GetProcessTypeId() => ProcessTypeId.CREATE_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.CREATE_CREDENTIAL => await _credentialProcessHandler.CreateCredential(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.SIGN_CREDENTIAL => await _credentialProcessHandler.SignCredential(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT => await _credentialProcessHandler.SaveCredentialDocument(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER => await _credentialProcessHandler.CreateCredentialForHolder(_credentialId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.TRIGGER_CALLBACK => await _credentialProcessHandler.TriggerCallback(_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/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs b/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs new file mode 100644 index 00000000..ba49a183 --- /dev/null +++ b/src/processes/CredentialProcess.Worker/DependencyInjection/CredentialProcessCollectionExtensions.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Library.DependencyInjection; +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) => + services + .AddTransient() + .AddCredentialProcessHandler(config); +} diff --git a/src/processes/Processes.Library/ManualProcessStepData.cs b/src/processes/Processes.Library/ManualProcessStepData.cs new file mode 100644 index 00000000..dccd234e --- /dev/null +++ b/src/processes/Processes.Library/ManualProcessStepData.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Library; + +public record ManualProcessStepData( + ProcessStepTypeId ProcessStepTypeId, + Process Process, + IEnumerable ProcessSteps, + IIssuerRepositories PortalRepositories +); diff --git a/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs new file mode 100644 index 00000000..02e347e9 --- /dev/null +++ b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs @@ -0,0 +1,131 @@ +/******************************************************************************** + * 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.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +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; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Library; + +public static class VerifyProcessDataExtensions +{ + public static ManualProcessStepData CreateManualProcessData( + this VerifyProcessData? processData, + ProcessStepTypeId processStepTypeId, + IIssuerRepositories portalRepositories, + Func getProcessEntityName) + { + if (processData is null) + { + throw new NotFoundException($"{getProcessEntityName()} does not exist"); + } + + if (processData.Process == null) + { + throw new ConflictException($"{getProcessEntityName()} is not associated with any process"); + } + + if (processData.Process.IsLocked()) + { + throw new ConflictException($"process {processData.Process.Id} associated with {getProcessEntityName()} is locked, lock expiry is set to {processData.Process.LockExpiryDate}"); + } + + if (processData.ProcessSteps == null) + { + throw new UnexpectedConditionException("processSteps should never be null here"); + } + + if (processData.ProcessSteps.Any(step => step.ProcessStepStatusId != ProcessStepStatusId.TODO)) + { + throw new UnexpectedConditionException($"processSteps should never have any other status than TODO here"); + } + + if (processData.ProcessSteps.All(step => step.ProcessStepTypeId != processStepTypeId)) + { + throw new ConflictException($"{getProcessEntityName()}, process step {processStepTypeId} is not eligible to run"); + } + + return new(processStepTypeId, processData.Process, processData.ProcessSteps, portalRepositories); + } +} + +public static class ManualProcessStepDataExtensions +{ + public static void RequestLock(this ManualProcessStepData context, DateTimeOffset lockExpiryDate) + { + context.PortalRepositories.Attach(context.Process); + + var isLocked = context.Process.TryLock(lockExpiryDate); + if (!isLocked) + { + throw new UnexpectedConditionException("process TryLock should never fail here"); + } + } + + public static void SkipProcessSteps(this ManualProcessStepData context, IEnumerable processStepTypeIds) => + context.PortalRepositories.GetInstance() + .AttachAndModifyProcessSteps( + context.ProcessSteps + .Where(step => step.ProcessStepTypeId != context.ProcessStepTypeId) + .GroupBy(step => step.ProcessStepTypeId) + .IntersectBy(processStepTypeIds, group => group.Key) + .SelectMany(group => ModifyStepStatusRange(group, ProcessStepStatusId.SKIPPED))); + + public static void SkipProcessStepsExcept(this ManualProcessStepData context, IEnumerable processStepTypeIds) => + context.PortalRepositories.GetInstance() + .AttachAndModifyProcessSteps( + context.ProcessSteps + .Where(step => step.ProcessStepTypeId != context.ProcessStepTypeId) + .GroupBy(step => step.ProcessStepTypeId) + .ExceptBy(processStepTypeIds, group => group.Key) + .SelectMany(group => ModifyStepStatusRange(group, ProcessStepStatusId.SKIPPED))); + + public static void FinalizeProcessStep(this ManualProcessStepData context) + { + context.PortalRepositories.GetInstance().AttachAndModifyProcessSteps( + ModifyStepStatusRange(context.ProcessSteps.Where(step => step.ProcessStepTypeId == context.ProcessStepTypeId), ProcessStepStatusId.DONE)); + + context.PortalRepositories.Attach(context.Process); + if (!context.Process.ReleaseLock()) + { + context.Process.UpdateVersion(); + } + } + + private static IEnumerable<(Guid, Action?, Action)> ModifyStepStatusRange(IEnumerable steps, ProcessStepStatusId processStepStatusId) + { + var firstStep = steps.FirstOrDefault(); + + if (firstStep == null) + yield break; + + foreach (var step in steps) + { + yield return ( + step.Id, + null, + ps => ps.ProcessStepStatusId = ps.Id == firstStep.Id + ? processStepStatusId + : ProcessStepStatusId.DUPLICATE); + } + } +} diff --git a/src/processes/Processes.Library/Processes.Library.csproj b/src/processes/Processes.Library/Processes.Library.csproj new file mode 100644 index 00000000..56ca7374 --- /dev/null +++ b/src/processes/Processes.Library/Processes.Library.csproj @@ -0,0 +1,40 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Library + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Library + net7.0 + enable + enable + 76a1cf69-39e1-43a7-b6a7-fef83be5359f + + + + + + + + + + + + diff --git a/src/processes/Processes.Worker.Library/DependencyInjection/ServiceCollectionExtensions.cs b/src/processes/Processes.Worker.Library/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..2f560c87 --- /dev/null +++ b/src/processes/Processes.Worker.Library/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddProcessIdentity(this IServiceCollection services, IConfigurationSection section) + { + services.AddOptions().Bind(section); + return services + .AddTransient(); + } +} diff --git a/src/processes/Processes.Worker.Library/IProcessExecutor.cs b/src/processes/Processes.Worker.Library/IProcessExecutor.cs new file mode 100644 index 00000000..83f40a52 --- /dev/null +++ b/src/processes/Processes.Worker.Library/IProcessExecutor.cs @@ -0,0 +1,35 @@ +/******************************************************************************** + * 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.Processes.Worker.Library; + +public interface IProcessExecutor +{ + enum ProcessExecutionResult + { + SaveRequested = 1, + LockRequested = 2, + Unmodified = 3 + } + IAsyncEnumerable ExecuteProcess(Guid processId, ProcessTypeId processTypeId, CancellationToken cancellationToken); + IEnumerable GetRegisteredProcessTypeIds(); + IEnumerable GetExecutableStepTypeIds(); +} diff --git a/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs b/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs new file mode 100644 index 00000000..cb0f5240 --- /dev/null +++ b/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs @@ -0,0 +1,46 @@ +/******************************************************************************** + * 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.Entities.Enums; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +public interface IProcessTypeExecutor +{ + record InitializationResult(bool Modified, IEnumerable? ScheduleStepTypeIds); + record StepExecutionResult(bool Modified, ProcessStepStatusId ProcessStepStatusId, IEnumerable? ScheduleStepTypeIds, IEnumerable? SkipStepTypeIds, string? ProcessMessage); + + ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds); + ValueTask IsLockRequested(ProcessStepTypeId processStepTypeId); + + /// + /// tbd + /// + /// + /// + /// + /// Is thrown if entity is not found + /// Is thrown if ... + /// + ValueTask ExecuteProcessStep(ProcessStepTypeId processStepTypeId, IEnumerable processStepTypeIds, CancellationToken cancellationToken); + bool IsExecutableStepTypeId(ProcessStepTypeId processStepTypeId); + ProcessTypeId GetProcessTypeId(); + IEnumerable GetExecutableStepTypeIds(); +} diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionService.cs b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs new file mode 100644 index 00000000..a5edd1f0 --- /dev/null +++ b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs @@ -0,0 +1,158 @@ +/******************************************************************************** + * 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.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using System.Runtime.CompilerServices; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +/// +/// Service that reads all open/pending processSteps of a checklist and triggers their execution. +/// +public class ProcessExecutionService +{ + private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly TimeSpan _lockExpiryTime; + private readonly ILogger _logger; + + /// + /// Creates a new instance of + /// + /// access to the services + /// date time provider + /// access to the options + /// the logger + public ProcessExecutionService( + IServiceScopeFactory serviceScopeFactory, + IDateTimeProvider dateTimeProvider, + IOptions options, + ILogger logger) + { + _serviceScopeFactory = serviceScopeFactory; + _dateTimeProvider = dateTimeProvider; + _lockExpiryTime = new TimeSpan(options.Value.LockExpirySeconds * 10000000L); + _logger = logger; + } + + /// + /// Handles the checklist processing + /// + /// Cancellation Token + public async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + using var processServiceScope = _serviceScopeFactory.CreateScope(); + var executorRepositories = processServiceScope.ServiceProvider.GetRequiredService(); + var processExecutor = processServiceScope.ServiceProvider.GetRequiredService(); + + using var outerLoopScope = _serviceScopeFactory.CreateScope(); + var outerLoopRepositories = outerLoopScope.ServiceProvider.GetRequiredService(); + + var activeProcesses = outerLoopRepositories.GetInstance().GetActiveProcesses(processExecutor.GetRegisteredProcessTypeIds(), processExecutor.GetExecutableStepTypeIds(), _dateTimeProvider.OffsetNow); + await foreach (var process in activeProcesses.WithCancellation(stoppingToken).ConfigureAwait(false)) + { + try + { + if (process.IsLocked()) + { + _logger.LogInformation("skipping locked process {ProcessId} type {ProcessType}, lock expires at {LockExpireDate}", process.Id, process.ProcessTypeId, process.LockExpiryDate); + continue; + } + + _logger.LogInformation("start processing process {ProcessId} type {ProcessType}", process.Id, process.ProcessTypeId); + await foreach (var hasChanged in ExecuteProcess(processExecutor, process, stoppingToken).ConfigureAwait(false)) + { + if (hasChanged) + { + await executorRepositories.SaveAsync().ConfigureAwait(false); + } + + executorRepositories.Clear(); + } + + if (process.ReleaseLock()) + { + await executorRepositories.SaveAsync().ConfigureAwait(false); + executorRepositories.Clear(); + } + + _logger.LogInformation("finished processing process {ProcessId}", process.Id); + } + catch (Exception ex) when (ex is not SystemException) + { + _logger.LogInformation(ex, "error processing process {ProcessId} type {ProcessType}: {Message}", process.Id, process.ProcessTypeId, ex.Message); + executorRepositories.Clear(); + } + } + } + catch (Exception ex) + { + Environment.ExitCode = 1; + _logger.LogError(ex, "processing failed with following Exception {ExceptionMessage}", ex.Message); + } + } + + private async IAsyncEnumerable ExecuteProcess(IProcessExecutor processExecutor, Process process, [EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var executionResult in processExecutor.ExecuteProcess(process.Id, process.ProcessTypeId, cancellationToken).ConfigureAwait(false)) + { + yield return executionResult switch + { + IProcessExecutor.ProcessExecutionResult.LockRequested => EnsureLock(process), + IProcessExecutor.ProcessExecutionResult.SaveRequested => UpdateVersion(process), + _ => false + }; + } + } + + private bool EnsureLock(ILockableEntity entity) + { + if (entity.IsLocked()) + { + return false; + } + + if (!entity.TryLock(_dateTimeProvider.OffsetNow.Add(_lockExpiryTime))) + { + throw new UnexpectedConditionException("process TryLock should never fail here"); + } + + return true; + } + + private static bool UpdateVersion(ILockableEntity entity) + { + if (!entity.IsLocked()) + { + entity.UpdateVersion(); + } + + return true; + } +} diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs b/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs new file mode 100644 index 00000000..5bf7e36d --- /dev/null +++ b/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * 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.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Identity; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library.DependencyInjection; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +public static class ProcessExecutionServiceExtensions +{ + public static IServiceCollection AddProcessExecutionService(this IServiceCollection services, IConfigurationSection section) => + services + .AddProcessIdentity(section) + .AddTransient() + .AddTransient() + .AddTransient(); +} diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs b/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs new file mode 100644 index 00000000..ae00657c --- /dev/null +++ b/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs @@ -0,0 +1,31 @@ +/******************************************************************************** + * 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.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +public class ProcessExecutionServiceSettings +{ + [Required] + public int LockExpirySeconds { get; set; } + + [Required] + public Guid IdentityId { get; set; } +} diff --git a/src/processes/Processes.Worker.Library/ProcessExecutor.cs b/src/processes/Processes.Worker.Library/ProcessExecutor.cs new file mode 100644 index 00000000..1a45d729 --- /dev/null +++ b/src/processes/Processes.Worker.Library/ProcessExecutor.cs @@ -0,0 +1,213 @@ +/******************************************************************************** + * 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.Extensions.Logging; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +public class ProcessExecutor : IProcessExecutor +{ + private readonly ImmutableDictionary _executors; + private readonly IProcessStepRepository _processStepRepository; + private readonly ILogger _logger; + + public ProcessExecutor(IEnumerable executors, IIssuerRepositories portalRepositories, ILogger logger) + { + _processStepRepository = portalRepositories.GetInstance(); + _executors = executors.ToImmutableDictionary(executor => executor.GetProcessTypeId()); + _logger = logger; + } + + public IEnumerable GetRegisteredProcessTypeIds() => _executors.Keys; + public IEnumerable GetExecutableStepTypeIds() => _executors.Values.SelectMany(executor => executor.GetExecutableStepTypeIds()); + + public async IAsyncEnumerable ExecuteProcess(Guid processId, ProcessTypeId processTypeId, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (!_executors.TryGetValue(processTypeId, out var executor)) + { + throw new UnexpectedConditionException($"processType {processTypeId} is not a registered executable processType."); + } + + var allSteps = await _processStepRepository + .GetProcessStepData(processId) + .PreSortedGroupBy(x => x.ProcessStepTypeId, x => x.ProcessStepId) + .ToDictionaryAsync(g => g.Key, g => g.AsEnumerable(), cancellationToken) + .ConfigureAwait(false); + + var context = new ProcessContext( + processId, + allSteps, + new ProcessStepTypeSet(allSteps.Keys.Where(x => executor.IsExecutableStepTypeId(x))), + executor); + + var (modified, initialStepTypeIds) = await executor.InitializeProcess(processId, context.AllSteps.Keys).ConfigureAwait(false); + + modified |= ScheduleProcessStepTypeIds(initialStepTypeIds, context); + + yield return modified + ? IProcessExecutor.ProcessExecutionResult.SaveRequested + : IProcessExecutor.ProcessExecutionResult.Unmodified; + + while (context.ExecutableStepTypeIds.TryGetNext(out var stepTypeId)) + { + if (await executor.IsLockRequested(stepTypeId).ConfigureAwait(false)) + { + yield return IProcessExecutor.ProcessExecutionResult.LockRequested; + } + ProcessStepStatusId resultStepStatusId; + IEnumerable? scheduleStepTypeIds; + IEnumerable? skipStepTypeIds; + string? processMessage; + bool success; + try + { + (modified, resultStepStatusId, scheduleStepTypeIds, skipStepTypeIds, processMessage) = await executor.ExecuteProcessStep(stepTypeId, context.AllSteps.Keys, cancellationToken).ConfigureAwait(false); + success = true; + } + catch (Exception e) when (e is not SystemException) + { + resultStepStatusId = ProcessStepStatusId.FAILED; + processMessage = $"{e.GetType()}: {e.Message}"; + scheduleStepTypeIds = null; + skipStepTypeIds = null; + modified = false; + success = false; + } + if (!success) + { + yield return IProcessExecutor.ProcessExecutionResult.Unmodified; + } + modified |= SetProcessStepStatus(stepTypeId, resultStepStatusId, context, processMessage); + modified |= SkipProcessStepTypeIds(skipStepTypeIds, context); + modified |= ScheduleProcessStepTypeIds(scheduleStepTypeIds, context); + + yield return modified + ? IProcessExecutor.ProcessExecutionResult.SaveRequested + : IProcessExecutor.ProcessExecutionResult.Unmodified; + } + } + + private bool ScheduleProcessStepTypeIds(IEnumerable? scheduleStepTypeIds, ProcessContext context) + { + if (scheduleStepTypeIds == null || !scheduleStepTypeIds.Any()) + { + return false; + } + + var newStepTypeIds = scheduleStepTypeIds.Except(context.AllSteps.Keys).ToList(); + if (!newStepTypeIds.Any()) + { + return false; + } + foreach (var newStep in _processStepRepository.CreateProcessStepRange(newStepTypeIds.Select(stepTypeId => (stepTypeId, ProcessStepStatusId.TODO, context.ProcessId)))) + { + context.AllSteps.Add(newStep.ProcessStepTypeId, new[] { newStep.Id }); + if (context.Executor.IsExecutableStepTypeId(newStep.ProcessStepTypeId)) + { + context.ExecutableStepTypeIds.Add(newStep.ProcessStepTypeId); + } + } + return true; + } + + private bool SkipProcessStepTypeIds(IEnumerable? skipStepTypeIds, ProcessContext context) + { + if (skipStepTypeIds == null || !skipStepTypeIds.Any()) + { + return false; + } + var modified = false; + foreach (var skipStepTypeId in skipStepTypeIds) + { + var skippedStep = SetProcessStepStatus(skipStepTypeId, ProcessStepStatusId.SKIPPED, context, null); + if (skippedStep) + { + _logger.LogInformation("Skipped step {SkipStepTypeId} for process {ProcessId}", skipStepTypeId, context.ProcessId); + } + + modified |= skippedStep; + } + return modified; + } + + private bool SetProcessStepStatus(ProcessStepTypeId stepTypeId, ProcessStepStatusId stepStatusId, ProcessContext context, string? processMessage) + { + if ((stepStatusId == ProcessStepStatusId.TODO && processMessage == null) || !context.AllSteps.Remove(stepTypeId, out var stepIds)) + { + return false; + } + + var isFirst = true; + foreach (var stepId in stepIds) + { + _processStepRepository.AttachAndModifyProcessStep(stepId, null, step => + { + step.ProcessStepStatusId = isFirst ? stepStatusId : ProcessStepStatusId.DUPLICATE; + step.Message = processMessage; + }); + isFirst = false; + } + if (context.Executor.IsExecutableStepTypeId(stepTypeId)) + { + context.ExecutableStepTypeIds.Remove(stepTypeId); + } + return true; + } + + private sealed record ProcessContext( + Guid ProcessId, + IDictionary> AllSteps, + ProcessStepTypeSet ExecutableStepTypeIds, + IProcessTypeExecutor Executor + ); + + private sealed class ProcessStepTypeSet + { + private readonly HashSet _items; + + public ProcessStepTypeSet(IEnumerable items) + { + _items = new HashSet(items); + } + + public bool TryGetNext(out ProcessStepTypeId item) + { + using var enumerator = _items.GetEnumerator(); + if (!enumerator.MoveNext()) + { + item = default; + return false; + } + item = enumerator.Current; + _items.Remove(item); + return true; + } + + public void Add(ProcessStepTypeId item) => _items.Add(item); + + public void Remove(ProcessStepTypeId item) => _items.Remove(item); + } +} diff --git a/src/processes/Processes.Worker.Library/ProcessIdentityIdService.cs b/src/processes/Processes.Worker.Library/ProcessIdentityIdService.cs new file mode 100644 index 00000000..f553f834 --- /dev/null +++ b/src/processes/Processes.Worker.Library/ProcessIdentityIdService.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; + +public class ProcessIdentityIdService : IIdentityIdService +{ + private readonly ProcessExecutionServiceSettings _settings; + + public ProcessIdentityIdService(IOptions options) + { + _settings = options.Value; + } + + public Guid IdentityId => _settings.IdentityId; +} diff --git a/src/processes/Processes.Worker.Library/Processes.Worker.Library.csproj b/src/processes/Processes.Worker.Library/Processes.Worker.Library.csproj new file mode 100644 index 00000000..d2867026 --- /dev/null +++ b/src/processes/Processes.Worker.Library/Processes.Worker.Library.csproj @@ -0,0 +1,45 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/src/processes/Processes.Worker/Processes.Worker.csproj b/src/processes/Processes.Worker/Processes.Worker.csproj new file mode 100644 index 00000000..f61969f3 --- /dev/null +++ b/src/processes/Processes.Worker/Processes.Worker.csproj @@ -0,0 +1,56 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker + Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker + net7.0 + enable + enable + Exe + Linux + ..\..\.. + True + eea9b02b-62ae-457d-91b9-b6717b99dd93 + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/processes/Processes.Worker/Program.cs b/src/processes/Processes.Worker/Program.cs new file mode 100644 index 00000000..125aee70 --- /dev/null +++ b/src/processes/Processes.Worker/Program.cs @@ -0,0 +1,72 @@ +/******************************************************************************** + * 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.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.CredentialProcess.Worker.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Processes.Worker.Library; +using Serilog; + +LoggingExtensions.EnsureInitialized(); +Log.Information("Building worker"); +try +{ + var host = Host + .CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services + .AddTransient() + .AddIssuerRepositories(hostContext.Configuration) + .AddProcessExecutionService(hostContext.Configuration.GetSection("Processes")) + .AddPortalService(hostContext.Configuration.GetSection("Portal")) + .AddCallbackService(hostContext.Configuration.GetSection("Callback")) + .AddCredentialProcessExecutor(hostContext.Configuration); + }) + .AddLogging() + .Build(); + Log.Information("Building worker completed"); + + using var tokenSource = new CancellationTokenSource(); + Console.CancelKeyPress += (s, e) => + { + Log.Information("Canceling..."); + tokenSource.Cancel(); + e.Cancel = true; + }; + + Log.Information("Start processing"); + var workerInstance = host.Services.GetRequiredService(); + await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(false); + Log.Information("Execution finished shutting down"); +} +catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) +{ + Log.Fatal(ex, "Unhandled exception"); +} +finally +{ + Log.Information("Server Shutting down"); + Log.CloseAndFlush(); +} diff --git a/src/processes/Processes.Worker/Properties/launchSettings.json b/src/processes/Processes.Worker/Properties/launchSettings.json new file mode 100644 index 00000000..5de5fb55 --- /dev/null +++ b/src/processes/Processes.Worker/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Processes.Worker": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/processes/Processes.Worker/appsettings.json b/src/processes/Processes.Worker/appsettings.json new file mode 100644 index 00000000..50b53b47 --- /dev/null +++ b/src/processes/Processes.Worker/appsettings.json @@ -0,0 +1,59 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "System": "Information", + "Microsoft.Hosting.Lifetime": "Information", + "Org.Eclipse.TractusX.SsiCredentialIssuer": "Information" + } + }, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "FromLogContext" + ], + "Properties": { + "Application": "Dim.Process.Worker" + } + }, + "ConnectionStrings": { + "IssuerDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" + }, + "Processes": { + "LockExpirySeconds": 3600, + "IdentityId": "" + }, + "Portal": { + "Username": "", + "Password": "", + "ClientId": "", + "GrantType": "", + "ClientSecret": "", + "Scope": "", + "TokenAddress": "", + "BaseAddress": "" + }, + "Wallet": { + "Username": "", + "Password": "", + "ClientId": "", + "GrantType": "", + "ClientSecret": "", + "Scope": "", + "TokenAddress": "", + "BaseAddress": "", + "EncrptionConfigIndex": 0, + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] + } +} diff --git a/src/settings-coverage.xml b/src/settings-coverage.xml new file mode 100644 index 00000000..48a14895 --- /dev/null +++ b/src/settings-coverage.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Tests.Shared/Extensions/HttpExtensions.cs b/tests/Tests.Shared/Extensions/HttpExtensions.cs new file mode 100644 index 00000000..56a77d38 --- /dev/null +++ b/tests/Tests.Shared/Extensions/HttpExtensions.cs @@ -0,0 +1,52 @@ +/******************************************************************************** + * 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.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared.Extensions; + +public static class HttpExtensions +{ + public static async Task GetResultFromContent(this HttpResponseMessage response) + { + using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumConverter(allowIntegerValues: false) } + }; + return await JsonSerializer.DeserializeAsync(responseStream, options).ConfigureAwait(false) ?? throw new InvalidOperationException(); + } + + public static HttpContent ToJsonContent(this object data, JsonSerializerOptions options, string contentType) + { + var json = JsonSerializer.Serialize(data, options); + HttpContent content = new StringContent(json); + content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + return content; + } + + public static HttpContent ToFormContent(this string stringContent, string contentType) + { + HttpContent content = new StringContent(stringContent); + content.Headers.ContentType = new MediaTypeHeaderValue(contentType); + return content; + } +} diff --git a/tests/Tests.Shared/FakeIdentity.cs b/tests/Tests.Shared/FakeIdentity.cs new file mode 100644 index 00000000..32df8a14 --- /dev/null +++ b/tests/Tests.Shared/FakeIdentity.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.Service.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; + +public class FakeIdentity : IIdentityData +{ + public Guid IdentityId => new("ac1cf001-7fbc-1f2f-817f-bce058020001"); + public string Bpnl => "BPNL00000003AYRE"; +} diff --git a/tests/Tests.Shared/FakeIdentityIdService.cs b/tests/Tests.Shared/FakeIdentityIdService.cs new file mode 100644 index 00000000..75a45577 --- /dev/null +++ b/tests/Tests.Shared/FakeIdentityIdService.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.Auditing.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; + +public class FakeIdentityIdService : IIdentityIdService +{ + private readonly Guid _identityId = new("ac1cf001-7fbc-1f2f-817f-bce058020001"); + + public Guid IdentityId => _identityId; +} diff --git a/tests/Tests.Shared/FakeIdentityService.cs b/tests/Tests.Shared/FakeIdentityService.cs new file mode 100644 index 00000000..cc887a6c --- /dev/null +++ b/tests/Tests.Shared/FakeIdentityService.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.Service.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; + +public class FakeIdentityService : IIdentityService +{ + public Guid IdentityId => IdentityData.IdentityId; + public IIdentityData IdentityData { get; } = new FakeIdentity(); +} diff --git a/tests/Tests.Shared/HttpMessageHandlerMock.cs b/tests/Tests.Shared/HttpMessageHandlerMock.cs new file mode 100644 index 00000000..d6ab3c78 --- /dev/null +++ b/tests/Tests.Shared/HttpMessageHandlerMock.cs @@ -0,0 +1,61 @@ +/******************************************************************************** + * 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.Net; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; + +public class HttpMessageHandlerMock : HttpMessageHandler +{ + private readonly HttpStatusCode _statusCode; + private readonly Exception? _ex; + private readonly HttpContent? _httpContent; + private readonly bool _isRequestUri; + + public HttpMessageHandlerMock(HttpStatusCode statusCode, HttpContent? httpContent = null, Exception? ex = null, bool IsRequestUri = false) + { + _statusCode = statusCode; + _httpContent = httpContent; + _ex = ex; + _isRequestUri = IsRequestUri; + } + + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + RequestMessage = request; + + if (_ex != null) + { + throw _ex; + } + + var httpResponseMessage = new HttpResponseMessage(_statusCode); + httpResponseMessage.RequestMessage = _isRequestUri ? request : null; + if (_httpContent != null) + { + httpResponseMessage.Content = _httpContent; + } + + return Task.FromResult(httpResponseMessage); + } + + public HttpRequestMessage? RequestMessage { get; private set; } = null; +} diff --git a/tests/Tests.Shared/NoAuditHandler.cs b/tests/Tests.Shared/NoAuditHandler.cs new file mode 100644 index 00000000..eeb3d1a4 --- /dev/null +++ b/tests/Tests.Shared/NoAuditHandler.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * 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; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; + +public class NoAuditHandler : IAuditHandler +{ +#pragma warning disable CA1822 + public void HandleAuditForChangedEntries(IEnumerable changedEntries, DbContext context) +#pragma warning restore CA1822 + { + } +} diff --git a/tests/Tests.Shared/Tests.Shared.csproj b/tests/Tests.Shared/Tests.Shared.csproj new file mode 100644 index 00000000..33d05345 --- /dev/null +++ b/tests/Tests.Shared/Tests.Shared.csproj @@ -0,0 +1,33 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared + Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared + net7.0 + enable + enable + + + + + + + diff --git a/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs new file mode 100644 index 00000000..ffa94ee0 --- /dev/null +++ b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/ExpiryCheckServiceTests.cs @@ -0,0 +1,196 @@ +/******************************************************************************** + * 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 FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +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.Expiry.App.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.Tests; + +public class ExpiryCheckServiceTests +{ + private readonly IFixture _fixture; + private readonly ExpiryCheckService _sut; + + private readonly IDateTimeProvider _dateTimeProvider; + private readonly IIssuerRepositories _issuerRepositories; + private readonly IPortalService _portalService; + private readonly ICompanySsiDetailsRepository _companySsiDetailsRepository; + private readonly ExpiryCheckServiceSettings _settings; + + private readonly string Bpnl = "BPNL00000001TEST"; + private static readonly string IssuerBpnl = "BPNL000001ISSUER"; + + public ExpiryCheckServiceTests() + { + _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(); + _companySsiDetailsRepository = A.Fake(); + + A.CallTo(() => _issuerRepositories.GetInstance()) + .Returns(_companySsiDetailsRepository); + + _dateTimeProvider = A.Fake(); + _portalService = A.Fake(); + + var serviceProvider = _fixture.Create(); + A.CallTo(() => serviceProvider.GetService(typeof(IIssuerRepositories))).Returns(_issuerRepositories); + A.CallTo(() => serviceProvider.GetService(typeof(IDateTimeProvider))).Returns(_dateTimeProvider); + A.CallTo(() => serviceProvider.GetService(typeof(IPortalService))).Returns(_portalService); + var serviceScope = _fixture.Create(); + A.CallTo(() => serviceScope.ServiceProvider).Returns(serviceProvider); + var serviceScopeFactory = _fixture.Create(); + A.CallTo(() => serviceScopeFactory.CreateScope()).Returns(serviceScope); + + _settings = new ExpiryCheckServiceSettings + { + ExpiredVcsToDeleteInMonth = 12, + InactiveVcsToDeleteInWeeks = 8 + }; + _sut = new ExpiryCheckService(serviceScopeFactory, _fixture.Create>(), Options.Create(_settings)); + } + + [Fact] + public async Task ExecuteAsync_WithInactiveAndEligibleForDeletion_RemovesEntry() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var inactiveVcsToDelete = now.AddDays(-(_settings.InactiveVcsToDeleteInWeeks * 7)); + var credentialId = Guid.NewGuid(); + var credentialScheduleData = _fixture.Build() + .With(x => x.IsVcToDelete, true) + .Create(); + var data = new CredentialExpiryData[] + { + new(credentialId, Guid.NewGuid(), inactiveVcsToDelete.AddDays(-1), null, null, Bpnl, CompanySsiDetailStatusId.INACTIVE, VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, credentialScheduleData) + }; + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetExpiryData(A._, A._, A._)) + .Returns(data.ToAsyncEnumerable()); + + // Act + await _sut.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _companySsiDetailsRepository.RemoveSsiDetail(credentialId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task ExecuteAsync_WithPendingAndExpiryBeforeNow_DeclinesRequest() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var expiredVcsToDeleteInMonth = now.AddMonths(-_settings.ExpiredVcsToDeleteInMonth); + var ssiDetail = new CompanySsiDetail(Guid.NewGuid(), Bpnl, VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, CompanySsiDetailStatusId.PENDING, IssuerBpnl, Guid.NewGuid(), now) + { + ExpiryDate = expiredVcsToDeleteInMonth.AddDays(-2), + CreatorUserId = Guid.NewGuid() + }; + var credentialScheduleData = _fixture.Build() + .With(x => x.IsVcToDecline, true) + .Create(); + var data = new CredentialExpiryData[] + { + new(ssiDetail.Id, ssiDetail.CreatorUserId, ssiDetail.ExpiryDate!.Value, ssiDetail.ExpiryCheckTypeId, null, Bpnl, ssiDetail.CompanySsiDetailStatusId, ssiDetail.VerifiedCredentialTypeId, credentialScheduleData) + }; + 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); + + // 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); + } + + [Theory] + [InlineData(1, ExpiryCheckTypeId.ONE_DAY, ExpiryCheckTypeId.TWO_WEEKS)] + [InlineData(13, ExpiryCheckTypeId.TWO_WEEKS, ExpiryCheckTypeId.ONE_MONTH)] + [InlineData(27, ExpiryCheckTypeId.ONE_MONTH, null)] + public async Task ExecuteAsync_WithActiveCloseToExpiry_NotifiesCreator(int days, ExpiryCheckTypeId expiryCheckTypeId, ExpiryCheckTypeId? currentExpiryCheckTypeId) + { + // Arrange + var now = DateTimeOffset.UtcNow; + var ssiDetail = new CompanySsiDetail(Guid.NewGuid(), Bpnl, VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, CompanySsiDetailStatusId.ACTIVE, IssuerBpnl, Guid.NewGuid(), now) + { + ExpiryDate = now.AddDays(-days), + ExpiryCheckTypeId = currentExpiryCheckTypeId, + CreatorUserId = Guid.NewGuid() + }; + var credentialScheduleData = _fixture.Build() + .With(x => x.IsVcToDecline, false) + .With(x => x.IsOneDayNotification, expiryCheckTypeId == ExpiryCheckTypeId.ONE_DAY) + .With(x => x.IsTwoWeeksNotification, expiryCheckTypeId == ExpiryCheckTypeId.TWO_WEEKS) + .With(x => x.IsOneMonthNotification, expiryCheckTypeId == ExpiryCheckTypeId.ONE_MONTH) + .Create(); + var data = new CredentialExpiryData[] + { + new(ssiDetail.Id, ssiDetail.CreatorUserId, ssiDetail.ExpiryDate!.Value, ssiDetail.ExpiryCheckTypeId, null, Bpnl, ssiDetail.CompanySsiDetailStatusId, ssiDetail.VerifiedCredentialTypeId, credentialScheduleData) + }; + 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); + + // Assert + 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(); + + ssiDetail.ExpiryCheckTypeId.Should().Be(expiryCheckTypeId); + } +} diff --git a/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/GlobalUsings.cs b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/GlobalUsings.cs new file mode 100644 index 00000000..1fb85d28 --- /dev/null +++ b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/GlobalUsings.cs @@ -0,0 +1,23 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +global using AutoFixture; +global using AutoFixture.AutoFakeItEasy; +global using FakeItEasy; +global using Xunit; diff --git a/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/SsiCredentialIssuer.Expiry.App.Tests.csproj b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/SsiCredentialIssuer.Expiry.App.Tests.csproj new file mode 100644 index 00000000..b8f099c2 --- /dev/null +++ b/tests/credentials/SsiCredentialIssuer.Expiry.App.Tests/SsiCredentialIssuer.Expiry.App.Tests.csproj @@ -0,0 +1,55 @@ + + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.Tests + Org.Eclipse.TractusX.SsiCredentialIssuer.Expiry.App.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs new file mode 100644 index 00000000..09e18438 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CompanySsiDetailsRepositoryTests.cs @@ -0,0 +1,588 @@ +/******************************************************************************** + * 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 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 System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +public class CompanySsiDetailsRepositoryTests +{ + private const string ValidBpnl = "BPNL00000003AYRE"; + private readonly TestDbFixture _dbTestDbFixture; + private readonly Guid _userId = new("ac1cf001-7fbc-1f2f-817f-bce058020006"); + + public CompanySsiDetailsRepositoryTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region GetDetailsForCompany + + [Fact] + public async Task GetDetailsForCompany_WithValidData_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + 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( + x => x.Description == "T", + x => x.Description == "CO2", + x => x.Description == "BT", + x => x.Description == "CE", + x => x.Description == "QM"); + 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()); + } + + [Fact] + public async Task GetDetailsForCompany_WithExpiryFilter_ReturnsExpected() + { + // Arrange + var dt = new DateTimeOffset(2023, 9, 29, 0, 0, 0, TimeSpan.Zero); + var sut = await CreateSut(); + + // Act + 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( + x => x.Description == "T", + x => x.Description == "CO2", + x => x.Description == "BT", + x => x.Description == "CE", + x => x.Description == "QM"); + 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()); + } + + #endregion + + #region GetAllCredentialDetails + + [Fact] + public async Task GetAllCredentialDetails_WithValidData_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetAllCredentialDetails(null, null).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().NotBeNull(); + result.Count.Should().Be(7); + result.Should().HaveCount(7); + result.Where(x => x.Bpnl == ValidBpnl).Should().HaveCount(6) + .And.Satisfy( + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.PCF_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.BEHAVIOR_TWIN_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE); + result.Where(x => x.Bpnl == "BPNL00000001LLHA").Should().ContainSingle() + .And.Satisfy(x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); + } + + [Fact] + public async Task GetAllCredentialDetails_WithWithStatusId_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetAllCredentialDetails(CompanySsiDetailStatusId.PENDING, null).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().NotBeNull().And.HaveCount(4); + result.Count.Should().Be(4); + result.Where(x => x.Bpnl == ValidBpnl).Should().HaveCount(3) + .And.Satisfy( + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.PCF_FRAMEWORK, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE); + result.Should().ContainSingle(x => x.Bpnl == "BPNL00000001LLHA") + .Which.Should().Match(x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); + } + + [Fact] + public async Task GetAllCredentialDetails_WithWithCredentialType_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetAllCredentialDetails(null, VerifiedCredentialTypeId.PCF_FRAMEWORK).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().NotBeNull().And.ContainSingle().Which.Bpnl.Should().Be(ValidBpnl); + result.Count.Should().Be(1); + } + + #endregion + + #region GetSsiCertificates + + [Fact] + public async Task GetSsiCertificates_WithValidData_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetSsiCertificates(ValidBpnl, new DateTimeOffset(2023, 01, 01, 01, 01, 01, TimeSpan.Zero)).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(2) + .And.Satisfy( + x => x.CredentialType == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && + x.Credentials.Count() == 1 && + x.Credentials.Single().SsiDetailData.Count(ssi => ssi.ParticipationStatus == CompanySsiDetailStatusId.PENDING) == 1, + x => x.CredentialType == VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER && !x.Credentials.Any() + ); + } + + #endregion + + #region CreateSsiDetails + + [Fact] + public async Task CreateSsiDetails_WithValidData_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.CreateSsiDetails(new("9f5b9934-4014-4099-91e9-7b1aee696b03"), VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, "BPNL0001ISSUER", _userId, null); + + // Assert + context.ChangeTracker.HasChanges().Should().BeTrue(); + context.ChangeTracker.Entries().Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .Which.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.PENDING); + } + + #endregion + + #region CheckSsiDetailsExistsForCompany + + [Fact] + public async Task CheckCredentialDetailsExistsForCompany_WithExistingData_ReturnsTrue() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckSsiDetailsExistsForCompany(ValidBpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, VerifiedCredentialTypeKindId.FRAMEWORK, new Guid("1268a76a-ca19-4dd8-b932-01f24071d560")).ConfigureAwait(false); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task CheckCredentialDetailsExistsForCompany_WithNotExistingData_ReturnsTrue() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckSsiDetailsExistsForCompany("BPNL000000001TEST", VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, VerifiedCredentialTypeKindId.FRAMEWORK, new Guid("1268a76a-ca19-4dd8-b932-01f24071d560")).ConfigureAwait(false); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task CheckCredentialDetailsExistsForCompany_WithWrongTypeKindId_ReturnsTrue() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckSsiDetailsExistsForCompany("BPNL000000001TEST", VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, VerifiedCredentialTypeKindId.MEMBERSHIP, new Guid("1268a76a-ca19-4dd8-b932-01f24071d560")).ConfigureAwait(false); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task CheckCredentialDetailsExistsForCompany_WithInactive_ReturnsFalse() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckSsiDetailsExistsForCompany(ValidBpnl, VerifiedCredentialTypeId.BEHAVIOR_TWIN_FRAMEWORK, VerifiedCredentialTypeKindId.FRAMEWORK, new Guid("1268a76a-ca19-4dd8-b932-01f24071d562")).ConfigureAwait(false); + + // Assert + result.Should().BeFalse(); + } + + #endregion + + #region CheckUseCaseCredentialAndExternalTypeDetails + + [Theory] + [InlineData(VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, "1268a76a-ca19-4dd8-b932-01f24071d560", "2023-09-30 +0")] + [InlineData(VerifiedCredentialTypeId.PCF_FRAMEWORK, "1268a76a-ca19-4dd8-b932-01f24071d561", "2023-09-30 +0")] +#pragma warning disable xUnit1012 + [InlineData(default, "1268a76a-ca19-6666-b932-01f24071d561", default)] +#pragma warning restore xUnit1012 + public async Task CheckUseCaseCredentialAndExternalTypeDetails_WithTypeId_ReturnsTrue(VerifiedCredentialTypeId typeId, Guid detailId, DateTimeOffset expiry) + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(detailId, typeId).ConfigureAwait(false); + + // Assert + result.Expiry.Should().Be(expiry); + } + + #endregion + + #region CheckSsiCertificateType + + [Theory] + [InlineData(VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, false)] + [InlineData(VerifiedCredentialTypeId.PCF_FRAMEWORK, false)] + [InlineData(VerifiedCredentialTypeId.BEHAVIOR_TWIN_FRAMEWORK, false)] + [InlineData(VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, true)] + public async Task CheckSsiCertificateType_WithTypeId_ReturnsTrue(VerifiedCredentialTypeId typeId, bool expectedResult) + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.CheckSsiCertificateType(typeId).ConfigureAwait(false); + + // Assert + result.Exists.Should().Be(expectedResult); + } + + #endregion + + #region GetSsiApprovalData + + [Fact] + public async Task GetSsiApprovalData_WithValidData_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetSsiApprovalData(new("9f5b9934-4014-4099-91e9-7b1aee696b03")).ConfigureAwait(false); + + // Assert + result.exists.Should().BeTrue(); + result.data.Bpn.Should().Be("BPNL00000003AYRE"); + result.data.DetailData.Should().NotBeNull(); + result.data.DetailData!.VerifiedCredentialExternalTypeId.Should().Be(VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL); + } + + [Fact] + public async Task GetSsiApprovalData_WithNotExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetSsiApprovalData(Guid.NewGuid()).ConfigureAwait(false); + + // Assert + result.exists.Should().BeFalse(); + } + + #endregion + + #region GetAllCredentialDetails + + [Fact] + public async Task GetSsiRejectionData_WithValidData_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetSsiRejectionData(new("9f5b9934-4014-4099-91e9-7b1aee696b03")).ConfigureAwait(false); + + // Assert + result.Exists.Should().BeTrue(); + result.Status.Should().Be(CompanySsiDetailStatusId.PENDING); + result.Type.Should().Be(VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); + } + + [Fact] + public async Task GetSsiRejectionData_WithNotExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetSsiRejectionData(Guid.NewGuid()).ConfigureAwait(false); + + // Assert + result.Should().Be(default); + } + + #endregion + + #region AttachAndModifyCompanySsiDetails + + [Fact] + public async Task AttachAndModifyCompanySsiDetails_WithValidData_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyCompanySsiDetails(new("9f5b9934-4014-4099-91e9-7b1aee696b03"), null, ssi => + { + ssi.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + }); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .And.Match(x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE); + } + + [Fact] + public async Task AttachAndModifyCompanySsiDetails_WithNoChanges_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyCompanySsiDetails(new("9f5b9934-4014-4099-91e9-7b1aee696b03"), ssi => + { + ssi.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + }, ssi => + { + ssi.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE; + }); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeFalse(); + changedEntries.Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .And.Match(x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE); + } + + #endregion + + #region GetCertificateTypes + + [Fact] + public async Task GetCertificateTypes_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetCertificateTypes(ValidBpnl).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().ContainSingle().Which.Should().Be(VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER); + } + + [Fact] + public async Task GetCertificateTypes_WithoutCertificate_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetCertificateTypes("BPNL0000001TEST").ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(2).And.Satisfy( + x => x == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, + x => x == VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER); + } + + #endregion + + #region GetExpiryData + + [Fact] + public async Task GetExpiryData_ReturnsExpected() + { + // Arrange + var now = new DateTimeOffset(2024, 1, 1, 1, 1, 1, TimeSpan.Zero); + var inactiveVcsToDelete = now.AddMonths(-12); + var expiredVcsToDelete = now.AddDays(-42); + var sut = await CreateSut(); + + // Act + var result = await sut.GetExpiryData(now, inactiveVcsToDelete, expiredVcsToDelete).ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(6); + result.Where(x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING).Should().HaveCount(3); + result.Where(x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE).Should().HaveCount(3); + } + + #endregion + + #region CreateCredentialDetails + + [Fact] + public async Task CreateProcessData_WithValidData_ReturnsExpected() + { + // Arrange + var json = JsonDocument.Parse(""" + { + "root": "test123" + } + """); + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.CreateProcessData(Guid.NewGuid(), json, VerifiedCredentialTypeKindId.BPN, x => x.ClientId = "c1"); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .And.Match(x => x.ClientId == "c1"); + } + + #endregion + + #region RemoveSsiDetail + + [Fact] + public async Task RemoveSsiDetail_WithValidData_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.RemoveSsiDetail(Guid.NewGuid()); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().ContainSingle().Which.State.Should().Be(EntityState.Deleted); + } + + #endregion + + #region AttachAndModifyProcessData + + [Fact] + public async Task AttachAndModifyProcessData_WithValidData_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessData(new("9f5b9934-4014-4099-91e9-7b1aee696b03"), null, ssi => + { + ssi.EncryptionMode = 1; + }); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .And.Match(x => x.EncryptionMode == 1); + } + + [Fact] + public async Task AttachAndModifyProcessData_WithNoChanges_ReturnsExpected() + { + // Arrange + var (sut, context) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessData(new("9f5b9934-4014-4099-91e9-7b1aee696b03"), ssi => + { + ssi.EncryptionMode = 1; + }, ssi => + { + ssi.EncryptionMode = 1; + }); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeFalse(); + changedEntries.Should().ContainSingle() + .Which.Entity.Should().BeOfType() + .And.Match(x => x.EncryptionMode == 1); + } + + #endregion + + #region Setup + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + return new CompanySsiDetailsRepository(context); + } + + private async Task<(CompanySsiDetailsRepository sut, IssuerDbContext context)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + return (new CompanySsiDetailsRepository(context), context); + } + + #endregion +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/ContentTypeMapperExtensionsTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/ContentTypeMapperExtensionsTests.cs new file mode 100644 index 00000000..9a0f499d --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/ContentTypeMapperExtensionsTests.cs @@ -0,0 +1,83 @@ +/******************************************************************************** + * 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 FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Extensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +public class ContentTypeMapperExtensionsTests +{ + [Theory] + [InlineData(MediaTypeId.GIF, "image/gif")] + [InlineData(MediaTypeId.PDF, "application/pdf")] + [InlineData(MediaTypeId.PNG, "image/png")] + [InlineData(MediaTypeId.SVG, "image/svg+xml")] + [InlineData(MediaTypeId.JSON, "application/json")] + [InlineData(MediaTypeId.JPEG, "image/jpeg")] + [InlineData(MediaTypeId.TIFF, "image/tiff")] + [InlineData(MediaTypeId.PEM, "application/x-pem-file")] + [InlineData(MediaTypeId.CA_CERT, "application/x-x509-ca-cert")] + [InlineData(MediaTypeId.PKX_CER, "application/pkix-cert")] + [InlineData(MediaTypeId.OCTET, "application/octet-stream")] + public void MapToMediaType_WithValid_ReturnsExpected(MediaTypeId mediaTypeId, string result) + { + var mediaType = mediaTypeId.MapToMediaType(); + mediaType.Should().Be(result); + } + + [Fact] + public void MapToMediaType_WithInvalid_ThrowsConflictException() + { + void Act() => ((MediaTypeId)666).MapToMediaType(); + + var ex = Assert.Throws((Action)Act); + ex.Message.Should().Be($"document mediatype 666 is not supported"); + } + + [Theory] + [InlineData(MediaTypeId.GIF, "image/gif")] + [InlineData(MediaTypeId.PDF, "application/pdf")] + [InlineData(MediaTypeId.PNG, "image/png")] + [InlineData(MediaTypeId.SVG, "image/svg+xml")] + [InlineData(MediaTypeId.JSON, "application/json")] + [InlineData(MediaTypeId.JPEG, "image/jpeg")] + [InlineData(MediaTypeId.TIFF, "image/tiff")] + [InlineData(MediaTypeId.PEM, "application/x-pem-file")] + [InlineData(MediaTypeId.CA_CERT, "application/x-x509-ca-cert")] + [InlineData(MediaTypeId.PKX_CER, "application/pkix-cert")] + [InlineData(MediaTypeId.OCTET, "application/octet-stream")] + public void ParseMediaTypeId_WithValid_ReturnsExpected(MediaTypeId expectedResult, string mediaType) + { + var result = mediaType.ParseMediaTypeId(); + result.Should().Be(expectedResult); + } + + [Fact] + public void ParseMediaTypeId_WithInvalid_ThrowsUnsupportedMediaTypeException() + { + void Act() => "just a test".ParseMediaTypeId(); + + var ex = Assert.Throws((Action)Act); + ex.Message.Should().Be($"mediaType 'just a test' is not supported"); + } +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs new file mode 100644 index 00000000..8b31a0ea --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/CredentialRepositoryTests.cs @@ -0,0 +1,148 @@ +/******************************************************************************** + * 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 FluentAssertions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Repositories; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +/// +/// Tests the functionality of the +/// +public class CredentialRepositoryTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + + public CredentialRepositoryTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region GetDataForProcessId + + [Fact] + public async Task GetDataForProcessId_ReturnsExpectedDocument() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetDataForProcessId(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2")); + + // Assert + result.Exists.Should().BeTrue(); + result.CredentialId.Should().Be(new Guid("9f5b9934-4014-4099-91e9-7b1aee696b03")); + } + + #endregion + + #region GetCredentialData + + [Fact] + public async Task GetCredentialData_ReturnsExpectedDocument() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetCredentialData(new Guid("9f5b9934-4014-4099-91e9-7b1aee696b03")); + + // Assert + result.HolderWalletData.WalletUrl.Should().Be("https://example.org/wallet"); + result.HolderWalletData.ClientId.Should().Be("c123"); + result.EncryptionInformation.EncryptionMode.Should().Be(1); + } + + #endregion + + #region GetWalletCredentialId + + [Fact] + public async Task GetWalletCredentialId_ReturnsExpectedDocument() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetWalletCredentialId(new Guid("9f5b9934-4014-4099-91e9-7b1aee696b03")); + + // Assert + result.Should().Be(new Guid("bd474c60-e7ce-450f-bdf4-73604546fc5e")); + } + + #endregion + + #region GetCredentialStorageInformationById + + [Fact] + public async Task GetCredentialStorageInformationById_ReturnsExpectedDocument() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetCredentialStorageInformationById(new Guid("9f5b9934-4014-4099-91e9-7b1aee696b03")); + + // Assert + result.CredentialTypeKindId.Should().Be(VerifiedCredentialTypeKindId.FRAMEWORK); + result.Schema.RootElement.GetRawText().Should().Be("{\"root\": \"test123\"}"); + } + + #endregion + + #region GetExternalCredentialAndKindId + + [Fact] + public async Task GetExternalCredentialAndKindId_ReturnsExpectedDocument() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetExternalCredentialAndKindId(new Guid("9f5b9934-4014-4099-91e9-7b1aee696b03")); + + // Assert + result.ExternalCredentialId.Should().Be(new Guid("bd474c60-e7ce-450f-bdf4-73604546fc5e")); + result.KindId.Should().Be(VerifiedCredentialTypeKindId.FRAMEWORK); + } + + #endregion + + #region Setup + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new CredentialRepository(context); + return sut; + } + + #endregion +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs new file mode 100644 index 00000000..2d24a59f --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/DocumentRepositoryTests.cs @@ -0,0 +1,116 @@ +/******************************************************************************** + * 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 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.Enums; +using System.Text; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +/// +/// Tests the functionality of the +/// +public class DocumentRepositoryTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + + public DocumentRepositoryTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region Create Document + + [Fact] + public async Task CreateDocument_ReturnsExpectedDocument() + { + // Arrange + var (sut, context) = await CreateSut().ConfigureAwait(false); + var test = "This is just test content"; + var content = Encoding.UTF8.GetBytes(test); + + // Act + var result = sut.CreateDocument("New Document", content, content, MediaTypeId.PDF, DocumentTypeId.CREDENTIAL, doc => + { + doc.DocumentStatusId = DocumentStatusId.INACTIVE; + }); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + result.DocumentTypeId.Should().Be(DocumentTypeId.CREDENTIAL); + result.DocumentStatusId.Should().Be(DocumentStatusId.INACTIVE); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Added); + } + + #endregion + + #region AssignDocumentToCompanySsiDetails + + [Fact] + public async Task AssignDocumentToCompanySsiDetails_ReturnsExpectedDocument() + { + // Arrange + var (sut, context) = await CreateSut().ConfigureAwait(false); + var companySsiDetailId = Guid.NewGuid(); + var documentId = Guid.NewGuid(); + + // Act + sut.AssignDocumentToCompanySsiDetails(documentId, companySsiDetailId); + + // Assert + var changeTracker = context.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Added); + } + + #endregion + + #region Setup + + private async Task<(DocumentRepository, IssuerDbContext)> CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new DocumentRepository(context); + return (sut, context); + } + + #endregion +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerDbContextTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerDbContextTests.cs new file mode 100644 index 00000000..35ec7098 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerDbContextTests.cs @@ -0,0 +1,117 @@ +/******************************************************************************** + * 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 Microsoft.EntityFrameworkCore; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.AuditEntities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Enums; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +public class IssuerDbContextTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + private readonly IDateTimeProvider _dateTimeProvider; + + public IssuerDbContextTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + _dateTimeProvider = A.Fake(); + } + + #region SaveAuditableEntity + + [Fact] + public async Task SaveCreatedAuditableEntity_SetsLastEditorId() + { + // Arrange + var now = DateTimeOffset.UtcNow; + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + + var before = now.AddDays(-1); + var id = Guid.NewGuid(); + var ca = new CompanySsiDetail(id, "BPNL00000001TEST", VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, "BPNL0001ISSUER", new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), before); + + var sut = await CreateContext().ConfigureAwait(false); + using var trans = await sut.Database.BeginTransactionAsync().ConfigureAwait(false); + + // Act + sut.Add(ca); + await sut.SaveChangesAsync().ConfigureAwait(false); + + // Assert + ca.LastEditorId.Should().NotBeNull().And.Be(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001")); + ca.DateLastChanged.Should().Be(now); + var auditEntries = await sut.AuditCompanySsiDetail20240228.Where(x => x.Id == id).ToListAsync(); + auditEntries.Should().ContainSingle().Which.Should().Match( + x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && (x.DateCreated - before) < TimeSpan.FromSeconds(1) && x.AuditV1OperationId == AuditOperationId.INSERT && (x.AuditV1DateLastChanged - now) < TimeSpan.FromSeconds(1) && x.LastEditorId == new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001")); + await trans.RollbackAsync().ConfigureAwait(false); + } + + [Fact] + public async Task SaveDeletedAuditableEntity_SetsLastEditorId() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var later = now.AddMinutes(1); + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now).Once().Then.Returns(later); + + var before = now.AddDays(-1); + var id = Guid.NewGuid(); + var ca = new CompanySsiDetail(id, "BPNL00000001TEST", VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, "BPNL0001ISSUER", new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), before); + + var sut = await CreateContext().ConfigureAwait(false); + using var trans = await sut.Database.BeginTransactionAsync().ConfigureAwait(false); + + // Act + sut.Add(ca); + await sut.SaveChangesAsync().ConfigureAwait(false); + sut.Remove(ca); + await sut.SaveChangesAsync().ConfigureAwait(false); + + // Assert + ca.LastEditorId.Should().NotBeNull().And.Be(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001")); + ca.DateLastChanged.Should().Be(later); + var auditEntries = await sut.AuditCompanySsiDetail20240228.Where(x => x.Id == id).ToListAsync(); + auditEntries.Should().HaveCount(2).And.Satisfy( + x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && (x.DateCreated - before) < TimeSpan.FromSeconds(1) && x.AuditV1OperationId == AuditOperationId.INSERT && x.LastEditorId == new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), + x => x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.ACTIVE && (x.DateCreated - before) < TimeSpan.FromSeconds(1) && x.AuditV1OperationId == AuditOperationId.DELETE && (x.AuditV1DateLastChanged - later) < TimeSpan.FromSeconds(1) && x.LastEditorId == new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001")); + await trans.RollbackAsync().ConfigureAwait(false); + } + + #endregion + + private async Task CreateContext() => + await _dbTestDbFixture.GetDbContext(_dateTimeProvider).ConfigureAwait(false); +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerRepositoriesTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerRepositoriesTests.cs new file mode 100644 index 00000000..9757b7d0 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/IssuerRepositoriesTests.cs @@ -0,0 +1,168 @@ +/******************************************************************************** + * 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 FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; +using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess; +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.Collections.Immutable; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +public class IssuerRepositoriesTests : IAssemblyFixture +{ + private readonly IFixture _fixture; + private readonly TestDbFixture _dbTestDbFixture; + + public IssuerRepositoriesTests(TestDbFixture testDbFixture) + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region GetInstance + + [Fact] + public async Task GetInstance_CompanySsiDetails_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task GetInstance_Credential_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task GetInstance_DocumentRepo_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task GetInstance_ProcessStep_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + #endregion + + #region Clear + + [Fact] + public async Task Clear_CreateSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + var changeTracker = dbContext.ChangeTracker; + dbContext.Processes.Add(new Process(Guid.NewGuid(), ProcessTypeId.CREATE_CREDENTIAL, Guid.NewGuid())); + + // Act + sut.Clear(); + + // Assert + changeTracker.HasChanges().Should().BeFalse(); + changeTracker.Entries().Should().BeEmpty(); + } + + #endregion + + #region Attach + + [Fact] + public async Task Attach_CreateSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + var changeTracker = dbContext.ChangeTracker; + var now = DateTimeOffset.Now; + + // Act + sut.Attach(new Process(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2"), default, Guid.Empty), p => + { + p.LockExpiryDate = now; + p.ProcessTypeId = ProcessTypeId.CREATE_CREDENTIAL; + }); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .ContainSingle() + .Which.State.Should().Be(EntityState.Modified); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy(x => x.ProcessTypeId == ProcessTypeId.CREATE_CREDENTIAL); + } + + #endregion + + private async Task<(IssuerRepositories sut, IssuerDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new IssuerRepositories(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new IssuerRepositories(context); + return sut; + } +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/ProcessStepRepositoryTests.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/ProcessStepRepositoryTests.cs new file mode 100644 index 00000000..e7c6d62c --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/ProcessStepRepositoryTests.cs @@ -0,0 +1,359 @@ +/******************************************************************************** + * 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 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 System.Collections.Immutable; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests; + +public class ProcessStepRepositoryTests : IAssemblyFixture +{ + private readonly IFixture _fixture; + private readonly TestDbFixture _dbTestDbFixture; + + public ProcessStepRepositoryTests(TestDbFixture testDbFixture) + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region CreateProcess + + [Fact] + public async Task CreateProcess_CreatesSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + var changeTracker = dbContext.ChangeTracker; + + // Act + var result = sut.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should().HaveCount(1) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == result.Id && x.ProcessTypeId == ProcessTypeId.CREATE_CREDENTIAL + ); + } + + #endregion + + #region CreateProcessStepRange + + [Fact] + + public async Task CreateProcessStepRange_CreateSuccessfully() + { + // Arrange + var processId = Guid.NewGuid(); + var processStepTypeIds = _fixture.CreateMany(3).ToImmutableArray(); + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + var changeTracker = dbContext.ChangeTracker; + + // Act + var result = sut.CreateProcessStepRange(processStepTypeIds.Select(processStepTypeId => (processStepTypeId, ProcessStepStatusId.TODO, processId))); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .HaveSameCount(processStepTypeIds) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == result.ElementAt(0).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[0] && x.ProcessStepStatusId == ProcessStepStatusId.TODO, + x => x.Id == result.ElementAt(1).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[1] && x.ProcessStepStatusId == ProcessStepStatusId.TODO, + x => x.Id == result.ElementAt(2).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[2] && x.ProcessStepStatusId == ProcessStepStatusId.TODO + ); + } + + #endregion + + #region CreateProcessStep + + [Fact] + public async Task CreateProcessStep_CreateSuccessfully() + { + // Arrange + var processId = Guid.NewGuid(); + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + var changeTracker = dbContext.ChangeTracker; + + // Act + sut.CreateProcessStep(ProcessStepTypeId.SIGN_CREDENTIAL, ProcessStepStatusId.TODO, processId); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .ContainSingle() + .Which.State.Should().Be(EntityState.Added); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.SIGN_CREDENTIAL && x.ProcessStepStatusId == ProcessStepStatusId.TODO + ); + } + + #endregion + + #region AttachAndModifyProcessStep + + [Fact] + public async Task AttachAndModifyProcessStep_WithExistingProcessStep_UpdatesStatus() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + + // Act + sut.AttachAndModifyProcessStep(new Guid("48f35f84-8d98-4fbd-ba80-8cbce5eeadb5"), + existing => + { + existing.ProcessStepStatusId = ProcessStepStatusId.TODO; + }, + modify => + { + modify.ProcessStepStatusId = ProcessStepStatusId.DONE; + } + ); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Modified); + changedEntity.Entity.Should().BeOfType().Which.ProcessStepStatusId.Should().Be(ProcessStepStatusId.DONE); + } + + #endregion + + #region AttachAndModifyProcessSteps + + [Fact] + public async Task AttachAndModifyProcessSteps_UpdatesStatus() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep, ProcessStep ModifiedStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + step => + { + step.ProcessStepStatusId = data.ModifiedStep.ProcessStepStatusId; + step.DateLastChanged = data.ModifiedStep.DateLastChanged; + step.Message = data.ModifiedStep.Message; + }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Modified)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[0].ModifiedStep.DateLastChanged && step.Message == stepData[0].ModifiedStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[1].ModifiedStep.DateLastChanged && step.Message == stepData[1].ModifiedStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[2].ModifiedStep.DateLastChanged && step.Message == stepData[2].ModifiedStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[3].ModifiedStep.DateLastChanged && step.Message == stepData[3].ModifiedStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[4].ModifiedStep.DateLastChanged && step.Message == stepData[4].ModifiedStep.Message + ); + } + + [Fact] + public async Task AttachAndModifyProcessSteps_WithUnmodifiedData_SkipsUpdateStatus() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + step => + { + step.DateLastChanged = data.InitialStep.DateLastChanged; + }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeFalse(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Unchanged)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[0].InitialStep.DateLastChanged && step.Message == stepData[0].InitialStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[1].InitialStep.DateLastChanged && step.Message == stepData[1].InitialStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[2].InitialStep.DateLastChanged && step.Message == stepData[2].InitialStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[3].InitialStep.DateLastChanged && step.Message == stepData[3].InitialStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[4].InitialStep.DateLastChanged && step.Message == stepData[4].InitialStep.Message + ); + } + + [Fact] + public async Task AttachAndModifyProcessSteps_WithUnmodifiedData_UpdatesLastChanged() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext().ConfigureAwait(false); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + step => { }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Modified)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[0].InitialStep.DateLastChanged && step.Message == stepData[0].InitialStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[1].InitialStep.DateLastChanged && step.Message == stepData[1].InitialStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[2].InitialStep.DateLastChanged && step.Message == stepData[2].InitialStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[3].InitialStep.DateLastChanged && step.Message == stepData[3].InitialStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[4].InitialStep.DateLastChanged && step.Message == stepData[4].InitialStep.Message + ); + } + + #endregion + + #region GetActiveProcesses + + [Fact] + public async Task GetActiveProcess_LockExpired_ReturnsExpected() + { + // Arrange + var processTypeIds = new[] { ProcessTypeId.CREATE_CREDENTIAL }; + var processStepTypeIds = new[] { + ProcessStepTypeId.CREATE_CREDENTIAL, + ProcessStepTypeId.SIGN_CREDENTIAL, + ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT, + ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER, + }; + + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetActiveProcesses(processTypeIds, processStepTypeIds, DateTimeOffset.Parse("2023-03-02 00:00:00.000000 +00:00")).ToListAsync().ConfigureAwait(false); + result.Should().HaveCount(1) + .And.Satisfy( + x => x.Id == new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2") && x.ProcessTypeId == ProcessTypeId.CREATE_CREDENTIAL && x.LockExpiryDate == DateTimeOffset.Parse("2023-03-01 00:00:00.000000 +00:00") + ); + } + + [Fact] + public async Task GetActiveProcess_Locked_ReturnsExpected() + { + // Arrange + var processTypeIds = new[] { ProcessTypeId.CREATE_CREDENTIAL }; + var processStepTypeIds = new[] { + ProcessStepTypeId.CREATE_CREDENTIAL, + ProcessStepTypeId.SIGN_CREDENTIAL, + ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT, + ProcessStepTypeId.CREATE_CREDENTIAL_FOR_HOLDER, + }; + + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetActiveProcesses(processTypeIds, processStepTypeIds, DateTimeOffset.Parse("2023-02-28 00:00:00.000000 +00:00")).ToListAsync().ConfigureAwait(false); + result.Should().BeEmpty(); + } + + #endregion + + #region GetProcessStepData + + [Fact] + public async Task GetProcessStepData_ReturnsExpected() + { + // Arrange + var processId = new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2"); + var sut = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetProcessStepData(processId).ToListAsync().ConfigureAwait(false); + result.Should().HaveCount(1) + .And.Satisfy( + x => x.ProcessStepId == new Guid("cd231cb8-55de-4ae4-b93f-d440512341fb") && x.ProcessStepTypeId == ProcessStepTypeId.SAVE_CREDENTIAL_DOCUMENT + ); + } + + #endregion + + private async Task<(ProcessStepRepository sut, IssuerDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new ProcessStepRepository(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext().ConfigureAwait(false); + var sut = new ProcessStepRepository(context); + return sut; + } +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_details.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_details.test.json new file mode 100644 index 00000000..201e84d9 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_details.test.json @@ -0,0 +1,95 @@ +[ + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b03", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 1, + "company_ssi_detail_status_id": 1, + "document_id": "e020787d-1e04-4c0b-9c06-bd1cd44724b1", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "1268a76a-ca19-4dd8-b932-01f24071d560", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "external_credential_id": "bd474c60-e7ce-450f-bdf4-73604546fc5e" + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b04", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 2, + "company_ssi_detail_status_id": 1, + "document_id": "5adbdf90-c6ef-47a5-b596-2f00a731c39b", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "1268a76a-ca19-4dd8-b932-01f24071d561", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b05", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 4, + "company_ssi_detail_status_id": 1, + "document_id": "5adbdf90-c6ef-47a5-b596-2f00a731c39a", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b06", + "bpnl": "BPNL00000001LLHA", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 1, + "company_ssi_detail_status_id": 1, + "document_id": "3291cae8-3c7b-4862-8cec-93ea0dc8c61e", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "1268a76a-ca19-4dd8-b932-01f24071d560", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b07", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 3, + "company_ssi_detail_status_id": 4, + "document_id": "e020787d-1e04-4c0b-9c06-bd1cd44724b2", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "1268a76a-ca19-4dd8-b932-01f24071d562", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b08", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 4, + "company_ssi_detail_status_id": 4, + "document_id": "9685f744-9d90-4102-a949-fcd0bb86f954", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b09", + "bpnl": "BPNL00000003AYRE", + "issuer_bpn": "BPNL000003ISSUER", + "verified_credential_type_id": 4, + "company_ssi_detail_status_id": 4, + "document_id": "88793f9f-c5a4-4621-847b-3d47cd839283", + "expiry_date": "2023-09-30 00:00:00.000000 +00:00", + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "verified_credential_external_type_detail_version_id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", + "date_created": "2023-06-01 00:00:00.000000 +00:00", + "expiry_check_type_id": null + } +] diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_process_datas.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_process_datas.test.json new file mode 100644 index 00000000..6921b95b --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/company_ssi_process_datas.test.json @@ -0,0 +1,12 @@ +[ + { + "company_ssi_detail_id": "9f5b9934-4014-4099-91e9-7b1aee696b03", + "schema": { + "root": "test123" + }, + "credential_type_kind_id": 1, + "encryption_mode": 1, + "holder_wallet_url": "https://example.org/wallet", + "client_id": "c123" + } +] \ No newline at end of file diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/documents.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/documents.test.json new file mode 100644 index 00000000..73c3ac4e --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/documents.test.json @@ -0,0 +1,79 @@ +[ + { + "id": "e020787d-1e04-4c0b-9c06-bd1cd44724b1", + "date_created": "2023-01-04T16:05:14.294575+00:00", + "document_name": "Default_App_Image.png", + "media_type_id": 3, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "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", + "document_status_id": 2 + }, + { + "id": "5adbdf90-c6ef-47a5-b596-2f00a731c39b", + "date_created": "2023-01-04T19:29:08.602237+00:00", + "document_name": "Terms&Conditions-Active_Participant.pdf", + "media_type_id": 6, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020001", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "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MDAwMDAwMDkzMyAwMDAwMCBuIAowMDAwMDExNzI2IDAwMDAwIG4gCjAwMDAwMTQ1NTggMDAwMDAgbiAKMDAwMDAxNTA2NiAwMDAwMCBuIAowMDAwMDE0NzcwIDAwMDAwIG4gCjAwMDAwMTUzMDIgMDAwMDAgbiAKMDAwMDAyMjcyMyAwMDAwMCBuIAowMDAwMDIyMzQ0IDAwMDAwIG4gCjAwMDAwMjI5NTkgMDAwMDAgbiAKMDAwMDAzMzI4NSAwMDAwMCBuIAowMDAwMDMyOTc4IDAwMDAwIG4gCjAwMDAwMzM1MjEgMDAwMDAgbiAKMDAwMDA0MDk5MiAwMDAwMCBuIAowMDAwMDQwNjM3IDAwMDAwIG4gCjAwMDAwNDEyMjggMDAwMDAgbiAKMDAwMDA0OTg5MyAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDMwIC9Sb290IDE2IDAgUiAvSW5mbyAyOSAwIFIgL0lEIFsgPDQzZjZkMjJmYzFiM2NiZjk1MDhjNTliMWE0NjViYTdjPgo8NDNmNmQyMmZjMWIzY2JmOTUwOGM1OWIxYTQ2NWJhN2M+IF0gPj4Kc3RhcnR4cmVmCjUwMTEwCiUlRU9GCg==", + "document_status_id": 2 + }, + { + "id": "5adbdf90-c6ef-47a5-b596-2f00a731c39a", + "date_created": "2023-01-04T19:29:08.602237+00:00", + "document_name": "Terms&Conditions-Active_Participant.pdf", + "media_type_id": 6, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019992", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "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MDAwMDAwMDkzMyAwMDAwMCBuIAowMDAwMDExNzI2IDAwMDAwIG4gCjAwMDAwMTQ1NTggMDAwMDAgbiAKMDAwMDAxNTA2NiAwMDAwMCBuIAowMDAwMDE0NzcwIDAwMDAwIG4gCjAwMDAwMTUzMDIgMDAwMDAgbiAKMDAwMDAyMjcyMyAwMDAwMCBuIAowMDAwMDIyMzQ0IDAwMDAwIG4gCjAwMDAwMjI5NTkgMDAwMDAgbiAKMDAwMDAzMzI4NSAwMDAwMCBuIAowMDAwMDMyOTc4IDAwMDAwIG4gCjAwMDAwMzM1MjEgMDAwMDAgbiAKMDAwMDA0MDk5MiAwMDAwMCBuIAowMDAwMDQwNjM3IDAwMDAwIG4gCjAwMDAwNDEyMjggMDAwMDAgbiAKMDAwMDA0OTg5MyAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDMwIC9Sb290IDE2IDAgUiAvSW5mbyAyOSAwIFIgL0lEIFsgPDQzZjZkMjJmYzFiM2NiZjk1MDhjNTliMWE0NjViYTdjPgo8NDNmNmQyMmZjMWIzY2JmOTUwOGM1OWIxYTQ2NWJhN2M+IF0gPj4Kc3RhcnR4cmVmCjUwMTEwCiUlRU9GCg==", + "document_status_id": 2 + }, + { + "id": "3291cae8-3c7b-4862-8cec-93ea0dc8c61e", + "date_created": "2023-05-23T16:05:14.294575+00:00", + "document_name": "InactiveDocument.pdf", + "media_type_id": 6, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993", + "document_hash": "", + "document_content": "", + "document_status_id": 3 + }, + { + "id": "e020787d-1e04-4c0b-9c06-bd1cd44724b2", + "date_created": "2023-01-04T16:05:14.294575+00:00", + "document_name": "Default_App_Image.png", + "media_type_id": 3, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "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", + "document_status_id": 2 + }, + { + "id": "9685f744-9d90-4102-a949-fcd0bb86f954", + "date_created": "2023-01-04T16:05:14.294575+00:00", + "document_name": "Default_App_Image.png", + "media_type_id": 3, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "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", + "document_status_id": 2 + }, + { + "id": "88793f9f-c5a4-4621-847b-3d47cd839283", + "date_created": "2023-01-04T16:05:14.294575+00:00", + "document_name": "Test.pdf", + "media_type_id": 6, + "document_type_id": 1, + "company_user_id": "ac1cf001-7fbc-1f2f-817f-bce058019993", + "document_hash": "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", + "document_content": "iVBORw0KGgoAAAANSUhEUgAAAP4AAAD6CAYAAACBB/pHAAAAAXNSR0IArs4c6QAAAHhlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAAP6gAwAEAAAAAQAAAPoAAAAADPFyXQAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAJdlJREFUeAHt3QmXJEXVBuBCcd8QFQUFGhEZYAB15P//ABQBAREYHXXEBfd996sn8dZX9FR3V2VFZEZU3jinTu2ZkW/c925xI/Ku/67bKlsikAgsBoG71u19i7navNBEIBHYIJDE30CRLxKB5SCQxF/OWOeVJgIbBJL4GyjyRSKwHASS+MsZ67zSRGCDQBJ/A0W+SASWg0ASfzljnVeaCGwQSOJvoMgXicByEEjiL2es80oTgQ0CSfwNFPkiEVgOAkn85Yx1XmkisEEgib+BIl8kAstBIIm/nLHOK00ENggk8TdQ5ItEYDkIJPGXM9Z5pYnABoEk/gaKfJEILAeBJP5yxjqvNBHYIJDE30CRLxKB5SCQxF/OWOeVJgIbBJL4GyjyRSKwHASS+MsZ67zSRGCDQBJ/A0W+SASWg0ASfzljnVeaCGwQSOJvoMgXicByELh7OZfa15X+85//XP3tb39b/fGPf1z94x//WN1zzz2rj33sY6v3v//9q/e9L/V1X6PZXm+T+I2NyX/+85+B6L/4xS9Wt27dWv3lL39ZudnR3XffvXr44YdXZ2dnqw996EON9Tq70xsCSfyGRuzf//73YOER/uc///nqX//612p9t6OhhzyAH/7wh6sPf/jDgwJoqNvZlQ4RSOI3MGgsOnce2X/84x8P5PdZkD66SDH85je/WT3wwAOrD3zgA/FxPicCByOQxD8YsrJ/4Npz53/wgx+suPcUwGUN+f0nWyJwDAJJ/GPQO+K/LDpX/re//e3qzTffXP3+978fYvmrDnneC7jq9/l9IrALgST+LlQqf8Zi//nPfx7c+p/+9KeDAqAIsiUCUyGQxJ8K6fV5kFuSTpz+ox/9aHj2WZJ+wkHIUw0IJPEnEgTkNi/PwiO919kSgbkQSOJXRh7hufasvIy9BF5a+Mqg5+GvRCCJfyVE43+A4H/9619XP/vZz1a3b99e/elPfxp/sPxnIlAQgSR+QTDjUCy8h3JbxTjvvPPOENvH9/mcCMyNQBK/8Aiw8ubaWXmVdmnlCwOchyuCQBK/CIzvHoSVV4xz8+bNTcltwcPnoRKBYggk8QtBifS/+93vVm+99dbqV7/6VSbwCuGah6mDQBK/AK7ce/H8G2+8sfr1r39d4Ih5iESgLgK5sPtIfJHenDz33pRdtkSgBwSS+EeMEtJrinKsrIv3Rxwy/5oITIJAEv9ImGXtTdkl6Y8EMv8+KQJJ/CPgltBTfqv+Plsi0BMCSfwjRssKO8tq09ofAWL+dRYEkvhHwM7NV5KbLRHoDYEk/sgRY+VZfJtppMUfCWL+bTYEkvgjoVeWy9on6UcCmH+bFYEk/kj4EZ+1z5YI9IhAEv+IUcv9744AL/86KwJJ/Fnhz5MnAvMgkMSfB/c8ayIwKwK5SGdW+PPkxyBwPrGaodf+aCbx98cqfzkjAkiuUtIjXutOkB/ptx9uLOoGo9l2I5DE341LftoAAlY9mjL9+9//vnm405ASaQ8zK4gflt6NRd1azLMbi24/3HPQ+/htA5c3axeS+LPCnyeHAPKG5UZ2y5uVQtvjwI5GHgjP2gfZ4z/xv21r73XcThzhPdxi/OMf//jq05/+9Oree+9dfeQjH3mP0liaQkjiJ/dmQyBIi9CI7v6Bv/zlLwcrHxY9FMNVnYxjxe9i4RSlEaTm/lMCH/zgB1ef/exnV1/+8pdXn/nMZwYl4f/xuzjGKT8n8U95dBu9NkRX/BR3CHbvQNuVIR4CnifxsZcRx+MthAfhXoUUDet/7dq1QREIBYQJFMSptyT+yBEmTAQ42/4IwIsl/sMf/jDcWMQNRmJ/wiD9/kcb98tQAvHMw3CTEx7A2dnZ6nOf+9zqk5/85JAPOGUFkMQfIT+E1956rEe2/RBgbbnzb7/99rCHgY1JKQKEn9PFjvMbT7mFe+65Z/XAAw+sHnzwwSEM4AGcYjvNq6o4UtxTLqJ983NJ7tVAs6wUpHsMsPAIT3EG4a4+wjS/COWjf5KKtlKjAB599NHVpz71qWk6MeFZkvh7gE14PcSFdtIlHLlA52rgYASrl156aXM3ITgGya4+wjy/CO9ESCIUePLJJwclYKag9b7vi1gS/wqkCCorTwDsmZ/u/RWArb/mwsPM/QJfeOGFLu8MbNwpLuPOA5AAZP3NCrTmrVw9Inf+Iol/JyabT2h+g25fPa6999kuRwBZxMqUJPf+FDAT0vFaeAAUgDxA71WBSfwL5FghiWwvq8XFZwGyXYwAfGD2k5/8ZCC9ZNkpYcaLocwYgieeeGJw/VUJ9tqS+FsjF4JqLz3WCvG5rNkuRiAwY+W///3vD4qSAjjFxsUnE1FC/Mgjjwzz/j3G/Un8/0koATaAbo4ha4/8tHy2ixGAGYxk67/73e8OrvCpY0ZGeIBcf2HNV77ylWF9QG/kT+Kv5ZqwiuMIMNfedFNYsovFftnfwMxmo6+//vpw+7ClYUZeXn755SHWZ/mVAffUFk185I5klASeuLR1ixWLT+YSMpjFLIepTUlPny1RUcLhW9/61lDii/w9TfctlvgEldZWSSYh1cs0nXryuTLKYeXlPyLR1ZuLW1phMhwsv/Les3XJ71xjc+h1LZL4BJh1v7W+553nXqacCNdHP/rR2RaRyH+w8ua2o/ruUIE7td9TfEKeV155ZZjjV+3XgzJcFPHDTRXHh5XvyUUVRyL+lA0+sthW0IWVn/L8vZxLwu+1114bxsc8f+vkXwTxCS8rrwBDLG9KhovWUyNIVo1NVTcOLw/TdK+++upQu87KZ7sYAXJFQT777LPD6r6Lfzn/NydPfKQnsLEqjFvWk5UnIvprldj9998/SfYY4Vl5XpGsfRYw7UdUuMl/2Nyj9Xj/pIkvdjcfz8pbbdWrxRLbcx8///nP7yeBR/wKZuG2iukpgGz7I6B46Xvf+95Afh5aq2v6T5b4tK9NHm7evDkIsvc9NtZeXB+FIjWvIaw8wbWqDmatx6o18RhzbHgJj3hKN27cSOKPAXHsf8TvXHuk72Wa7vy1EiCkl9CzN5xNIms05/CQ/xCfclWj5DZJPx5xxWAy/A899ND4g1T858lZfEQHOve+twTe+XFGeoUhX/ziF6tZjlCSrHxPU5vnsWrtPeVpFkS8z2NrTYmeDPFZLQU5rJZqsl5dewIiLlSow1rYAqrG9k+R/7AuAWZh5VsjUM/9EWryPK3jT+JXGknxKQ3b+7r5SOSJ6VmL0gJDQbLypp7MOyvGoQRKn6fSMHd1WDKpSOxLX/rSsI9/S53v3uITZDXTSE+7EuIeG+Kx8tz6WPRRmozhFYWVN+Phs9Ln6RH/Wn22wahkX2sVfd0TPyy9Oede3Xv13Qpzzta13vfdd1+Vem9Wnusp22yazvskfC26v3tcSjVmSkzF1gjZxl5B18Q3L8/SK8HtjfRBOru4fOELXxhI7xZP8fnYAT3/v7DysvVmOUzTaaXPc/68td7r97aX4rV2/nri81r92Pe4+iGcgru9+1tp3RKfS0+YWa9e3XtWnlsfVv688B4rJJQhgbNJhtxH77sJBT5mOyhJ2XL3wNMkJ83oCF9cZyiIYzEs8X8FUWZMauRsxvavS+ITaIJs2q430hNIVp7rd7Z27T/xiU8MYxdCPXYgt//HyiACjKwaM0ffm0e0fT0SnjBDdrGy0mWk5zrHMlihiwfSv/POO0O5McJ5P/e1k1F9evjhh4cVfNvXNtfr7ohPqGOxjfipp4bc7tpqio4QhNCWvAZCxuqZopO1h1dJpVKyr/scK/Ifipi++tWvXhgnS4xGcxssv5X3sQ+gBNuc5doUl1kU60Rsz91C64r4hBjZufi0eU+NdSKQCO9GjTXICJtYM8/CaDXOMxXurDol+dhjjw2YHXJeeAujxNVmMeQ35lygZWzILHe/hdYd8Qk27UkJ9NAQj4tqLpebWkvjs2oSncIfAt5rM64sJMKy2nDbtuaHXpdQyp1wjIFch+2x55AdnphZFcqoBWXcDfHFaTQma996XB8DS4BZd4PtubRrT4DFtRShNfMEq3VsLiMu3CTuzta5D6SX/CyBmfyAKkjH/853vjOLYiS/Eq3GLOTjMixqf9cN8cVotn3qJTNN2Lj1hJgwl24ESALPvDxcest3nMeDkpT/eOqpp6pgZjzkCeQ/JDwpzCmb8TLrwBuLhO6U5z9/ri6IDzQuPnfW65ab2JIAu9tKrXiOEuT92OQRLq1jctl4sX6R/3jmmWeGqc3Lfn/sd26BZWpN4m/K5jopG6GGsGNuq98F8YHVeg2+gRSLKsZh6Q1u6YbgLIY4Xta+twTneTxYeZtVwMtClhqYnT8ny//0008P02tTL0xC/FbyL80TX8yK9MjfWkN2ZCTA4lFxpPl5wlWyOYeHWD7WJBDaua3G2GvUb1aekhTLT1nO6tyUzdk6BLMUeSoMnQfxrSA1llOd96Ixap74gDI1NXcRxi4ADaDkU8zLc/FrDKjzRCzPYlCGNc6z6xprfCbnEbF8Lcwu6zfFTOlIFE+ZGyHDzmc8525NEx9ArFysIpsbrO3zs/Km5sSMpp5YsJJkdO0ExbW/+OKLw8pDFqMFodnGYd/XsKEkJbaee+65oaahRMZ+3/Nv/05fWH2hxdSeE0MmRzPXtQcOTRNfFhTxW5qiIjRILnGnsIQAlW7IzTJY3OHmjJZ1llQqpft71fH0Xf5DKGTr6aivv+p/Nb/naXiYAp2qwaEVz7VZ4gOIwMdqsqkG57LzGDjCoqjEuvkaxTiu2zVzQ1WcsRA9k56SVMMgeSeJV2Nq87Ixu+w7yojlnZKMrYxls8TnDrH2BqUFsPSBlTcXXKMYh4C6Zhl75aUskfctXPtl5LnsO4pR8VJsFio8aqXBVayvT1MSv5Xrb5b4pqpYvrkFP86v3JYAi1HjsxKDyK33kLSzoMQmoax8Kwrv0GuEjYdZDgk8K+kogJKYHdqnXb/XH8RvrV+7+lrjs2aJz+K1UKWHlLL2CnJqJPAcX2GS2y2L6b3vuSGSMMie8pJnLVn587gulfRwaI74BF/2GhnmtnqElsUy11ya9K6NZVdBJoE35bTSeQKUeC9WtprOJqGPP/548VoGfYSZRG/IhTFpWbGUwLXWMZojPi0sqYcUczYCFTF96SSe2J11l8C7td6FlSD33CTJFOFI4El81rCkvD9ywRNkGCgaNxmx1Nn5sx2GQHPERwLx/ZyJLYLLeonpS07X8WbMGyN87H/Xq2sPI32X82DlZexLYhVi7ByqNu2rKNlrijcsvvshwrPFfeuj/60+N0d8hEf8OQnBhYyltCUHjgA///zzg9Xq3bVHPvPysRipdJly4M7CW5dg16Wo5wj3nhdgFkTilaLOtj8CzREfIWS45yI+S8Z9lNAr0VwHgZWttxbc9fXq2sPGA8nF8R7c7CBiCbwcA2bceaRWy8AY7JIHnyE/TJP4h6HfJPG5b3M18byKPAJ+bEN4Vt40Hff+IgE+9jxT/B8e4mq360b4s/UiF+9LN4SHGbwsztqn7VIK+/xvyb9pivgGUG36XBaRcIvrS5SUskIEF+lbXWR0iOBTiLwgpEf+Eopx+/zGnsKHmZkOcuAcSeptlMq9bor4CE/bzzXYBJqbf6wlO5X974gZ8kXGXixdQinuEl+Yce0pyajfmEsOdvXv1D5rjvhzTeMhu6WaY6fuQkjtiGNrJ1NPc85MlBBUsfzZ2qVn5Uvtf7fdL5h5yH8g/Zy5ne1+LeF1c8QPbT81+JJD5u3HWnveCuFVjBNeS2l3eCpM9BseSm5NlZUuXnIdCM+dN62ppkFsH8pzqutc8nmaIr4YjwBM3WSllZeOtfaSeOaYkd60U8/N6jnhjv3vKMIaGXuekGk6CTx4heXvGbfe+t4U8eea6mLlxfdj56LVHdgso2fSI7hiHPULZjVqxPK8IgU4knduaT7n7E1vRC3d3yT+GlFWTtXZGOtGmO3dJinVs2tvYQ3Cy3Nw7Us3nhyMxPPyH3DrFa/S2MxxvPIjfMRVzOXysW422BjTTD9J6PUoxPAW3ojlWXpxfY3rYNm59dtWvsZ5xozfUv+zeOITQMQf49pKRKosmyshOVZoXTPvxl6BtsISy48Ncy7rg9yHPRVgZC97Vj5bGwg0RXyWYWrhQIKxq7sIdW8CHYouinHGhjgXiW9k5ilDnhBLL3cTn1/0v/x8WgSaIv60l/5ucYrE3tg6b9N25p57aax87Ixj+WyN/e8obsnOW+vlxqbpWP1s7SGwaOKzQsgwJplFoGXxp/ZQxoiQ66TcWHnbgSM/y1+6sfJyHhJ45ujTypdGuNzxFk18MI519WWp56oyPGT4XZ8NKxC+1i63lB/vB+ERf45ajEMwyd82uPXW1INyjMWXk2jZqknYmaZzf3jkH1uVeNmYIDmyc+2jYvGy3+d3bSCQFn+ky8vKtezmc+ftjGO1Ya0189x5yTtVi6rxsvWDwOKJ389QXd1Tbj0rrwjHwhqr6nzmUarxcOQ3FOOw8hJ5FGDLnk+paz+l4yTxT2g0JfBYeRV4Y2cqLoMDuU3NWYwknu+tfuGya1vad0n8ExhxsbuFNWJ5a+blLUo3Vt3CGoRn7dPCl0Z42uMl8afFu/jZWHZLZ8Xy4voazcIaxTge6hZKhg41+pvHvBqBJP7VGDX3i4jbldyapmPlS5fcsuisvJ1xJPAsrIlinLT2zYnEwR1K4h8M2fx/UHFnUY07/JQuuXV1iG2aLkpu1SucItlbnpWpLWVJ/NoIFzy+WB7RWXnEH1NxeFV3kIE7b2GN+XnvWyf9mJyGa1KHEV7MVbic2vdJ/E5GlJVXjIP0Enk1moy9BB7Sm6ZrPZbXP6Qfs3MS4qfFryFFecwiCBBuuwOZpjtbb3w5ZvnwVR1BAkS3M47bUpmma530cU2Sm2MXGyk6Wir50+KHBDX4zJWP5bNKbksn8OKSEd7cvEq8nohAYUWRUlzLvs+UWywX7kXJ7Xtt+/wuib8PShP+hhB6sOyq70zTcWXHxLGXdTti3Lfeemuw8lFy6/NeGktvZuPQ5hpNUfawyOrQa9v390n8fZGa6HcIzrW/cePGUHpb47QSWjYRsVdg7HJb4zw1j4m8ch42CB1jsZGexR/z37HX1ZJSTeKPHcXC/yOA9v2zdNYdaGvF8iydbD33vmeLBytu/piZDYrPSkJZ/amb/k6pbC66viT+RchM+LlpOgtruPV2xqkRyxN2GXuEt11Yz2vmkUfR0thKRWEN5ccCT01C4ckYZVVaHJP4pRE98HhIrhAnSm5rxPJc2tgkg8VvyeU8BC4khY+4fqyCdO3qFCwlnpL0zovw8jVTnvcifJP4FyEzwed2t/36178+CDIFUFogCBuX9o033hisPKvfM+kNidkN24GP3SDVrMXt27eH/QNKK9mrRMb5xk49XnXsQ79P4h+K2JG/N/g0v2k6t6kyD116ZxzkNl2lvt5tumNhTc+kh5mY/vr164PVH6skxfX2EZia9MSGch+bjDxS7O74exL/DkjqfUDYxKVW03mMtVqX9ZBFY+XV2ZubZ+W1XklPKUbSk3t/LGHdpJMinLrBn/KqsU/CmGtJ4o9BbcR/kNz0E8KzXMcK8K4usPKSVkhvuq5Xsse1sZCSnnDj4h+LWVQnhjKM80z1zM13c9ax3krJfibxj0BzH2L5DaFVjMNi0fg1Bp6VV2Mvcx8VaUdc2qx/hRmXWMLTugRK81jMzGLAZ64bm4a3VzqsGztQSfyRyBlIFumiRlD9hsUSyyvK4eodK8Dnz8d6WTP/2muvDdVoXP19FNL547TwHjYepuqsPuTiw7AEZqYw7Ssw1zQmwo+dfqwxNkn8kahy2yJRc55oBNW0DYv19NNPV8nkOifX3jTdnAI9Er47/obgMFXA9NBDDxXFTKHS66+/PsT258fqjo5U+oDSN4tTQomV6GISfySKBlJm3m2iWFxFIQaV8Bpgc/MEuEZjtWTsZafj3n3OPZdQH3uNsBQOweu+++47Opbf7g/l+Oabbw536p0LH+e1j4L4vpWWxD9iJBD8G9/4xpA9lzjizikuoRC4dTW0+/n976L7cwl1nH/MM3zkPO6///7BvedBlWzCHh6ROgYKoMZ47NNfY0MuxuwbsM/xx/wmiT8Gtf/9B9Fl6MXv2xb/sth/zOmC1Kx7WPm5YtUx/T//nyAgxXm23mPg3nvvvTRfcv7/+7xHetOZr7766pD7iHPu89/Sv+EFIn5puTimn0n8Y9D7339lnWvMyUfXCK04Xla694y9axLPI7x4vlYJqynNb3/72wPpA8e5nnl/jENLLYnf0mic60sU4yg6sZf9XPPP57o16i3lhfCIbjchmXuflbbEPCGkf+GFF5pYfeia5S1ayugbwMUTnxsdrvQoia7wJ/0ROkgcsvRzzT2XujTkDnfXTIdEV+kGM9l7Mf0rr7zSBOldI0+QkqMAWmpNEb/GPPdVYLOqEj8ttagws4KMa99zI/CSdoqXJPFqhERIzyOym5Blx61gRuGZrZDDaK0tmvgGhvs8x4YMuwRBX95+++0hKcXKU0o9N6RHeLMcprIo9tIN6U3XeShTbikccr3KjWsou2NxLD8SR/SIoEzpEhEaMaFy17maPmiUj+SdWnseSHw+V7+OPa9YXi2D+JabXzqWh4+pzZdffnlYZsvKt6Qo9Y+lb9HNN7ZNEd90x5TEB4ABsrss4s01z6q+XgJPIVBLwgufQ5vxI/BIL5NdmvD6Q1kLg1566aWhkKlFzMLat5bUi/FsivhcoqmJDwjLNMXVUxKfwqFsYmvr1vIMISD7Phs3+LFwXPtaewbyzihJ3hGL32qLSsRW+9cU8QnOHKuXuIky6DUKSXYNvDhUMY4CE9a+pbh0V38v+4wCY91gh/BW09WI5WHEyqu5d9OPlguYyLDl1xYZtdqaIj6BmaO6iVAhoOSQCqsa7ikBQBICi/Asfc/738X18NLO1sU4LH2tJcdwkrGXwGsdM2GHNQeKk+bwXvdVNE0RH1C0pAUoUzcCdWu96MX5a+2SIpfAYrm+li3WvthzZxXjKL2tYeX1gydmybGkp9qGlhvFbupSfqNGQrPktTdHfMBRAHMkbAiZ2JQwl4z3hRLcU8U44vqeXXveEK/MnDxLD68alg1minEoyl5u+kH5wYTXWAOTkyU+oUL8Wq72PsBxwWnuIP8xfaG8JKMIcMSljt1jg4OH+XixPNeeVSvdYBabhBqLXpKesJHfOFsTvwYupXFuyuK7OJaWq80tnoMkBE+tt/JPCRru7BjtzS1VTSb7jPyuZY7rKSUwrDzBtjNOeGWljh3HCSsvlpf8PEbpxjGneNZPns9jjz02yMsU5zz2HM0RX7JILTfiz9Ui2Sfu59KqvhL77yOIFIf5eHu3y0K3HpdehTFlZS5awso2YrWq0ChHNfasPAWg9aAo9VEWn4dYYhfgq8aj1Pd3rTvelO+pO7LeYjuk2YdspcDYdRyDyr2NmmvWzmfbXgBFoa9iUW6q2YHYwrkxeHdd4h2fwVy/XWdYeeT3vnSDm8SdBJ6ZFYqzt8YLeu655walOLe87oPduo93NWfxAWdOmGVpwVoiNUKzSEIA8Zu+bcdxEnaslP4S3B6Fd1tg9F+4JZb3cL2lBZpigakbftxaz6bAsDclqb9yHTZTLZkM3h6LWq+bIz4wWVgWppU52xBIU3Ae+rVNhPjeIG2/rjVoNY/LqpueMw9NAXu/fa0lzg1DyU636Wblve8NN5jI3rsFGi+wt9Yc8UPIxJOm1whFC+28YJ5/30Ifj+kD3Fkt+QxWvlYtgxCIhRfKtaLYD8UNVtYhPPvss6OTv4ees/TvmyN+XCBgJfl6yu5G33t7lq+A99l6KspquhqxvPDBLAfCm97suUn0Xrt2bUh21sBqCmyaJb4YWpZ0jiq+KYBv5RxwNnMRN7Co0S9To6Y1ld1aDMVi9ugx6TNj5F4JdhLaTvDWwK3mMZslPk0qxpRNNz2WrSwC4a4ifCRTy57h3XwHosvYm97saZpuFxZietup91CZt6v/2581S3ydFGeyRrK/rcT62+D1+BrhlZbaFpxrL5Faw11lHU1rPv/8891vFMqyw0tMfwqkJ7dNE5+QIr4MMKvfo3vYknKAJ2XKTYUrBeCzGs303IsvvjgUMfU8bjAScj711FPdJvJ2jW/TxNdhc8iAlw0OV3HXheRnlyOg5JZLb+VY7fUQ6hlsiaXuoUfS6zOFSPaUbT/55JPVZjkuH7V63zZPfAPAzZLdtxFlj4JUb/iuPjL8ZKFjmo4w1248NNWXvY6V0Ectg9p79Qxz7BFRe4yaJz4AZJ7ViksUzVnDX3swSh9fbKrklvBKknJbazdemem6HivxYEMxkjWe0dgFWrUxLnH8+pJQopfrY5hnNhjixt5LYgtBsvMwLDxLqxjnbJ28E8t7PdXUk0o88/W9WXu4SXRev359syX2VJjtHMjKH3ZDfAPDelkFZdlmb4JVeRw3hyes5poff/zxoex5Ciu/Ofn6BTdfRV4vjVzBiGyZqoPd1JjNgVVXxBd7KSeV6Mt4/05x4aYqdZa1tz6cUE/ZJPUsaDL1OvW5x1ynPrLyZ2vP6Gtf+1oXfR5znbv+0w3xdd5AEW5FJ2JJCb+0/O/efdaiJrG8ktu5klEUsiq91huZgZGVdTxI4dDSWlfENzjIbzrKNAvLIuG3ZPJThATYg5s6Z1xKGdsqq3VrT34QnqKsVcDUuiLpjvgAJdzmpM2v2rWFe0nYlqYAQgGqJjPzMSfhYI/0re+Rx7orxoEZqz8nZnMqhy6JDzADJtNvPbSy0J4SSscOOMUnlpfAY/G9X6oA74slxcjKMxZz5D/27edUv+ua+EAyiN/85jeHSjGW/5Sn+hBcya1KRm5q1Ngn6S+miwy9EMgy2rN1Ei+V5LtYdUv8GGrCL07jvln6aQ751Bb0RDKKe2pW41QWisQY1npmFODF0qvESwX5/0h3T3yXYkBltRX4GOzYj5127z3u13+xPCsvPk039f+F97JXUfOhVLnWbkKXnb/1706C+EBGfjXp5rBZRPu5RdKv9UHY1b+wTjEvz6vhtsbnu/6Tn72LgNyHOnsKcwnFOGPG/WSI7+KRQhJHxv/GjRvDji+2bhb392L5g9iuI2L5SOCNGeCl/Id3pzTZ7jhn61hext5n2XYjcFLEd4mII+5n/WMNtZViiktaj/31PZJRj6yLlLirKby7BXf7UySHFdIrYArluf2bfP1eBE6O+NuXRwGwmqb91JBL/Fnd16ICYOHlKQiwYhzvs12NgDGWvOPec+2z7YfASROf5vcwnSPBg1Q277SCzI4+asvntKjCDwSXk/CwDFQ/CXO2qxGAH6wsozXG2fZH4KSJvw0DF5rlZxVs7MHy8wLi/nahJGrlAhw/ju1ZPCpLTxlJ3Injk/DbI7b/a7jBFMbZ9kNgMcQHB8GgAMT/LGvssiIBGMtJJQJrJAN5Fh7OjfCy9ax9kD2Fdj+BzV+VQWBRxA/IkMwDERFPIk0lnN18rfjzbAcZoYCH++exKGGx4zi7nuPYFIwHcrPuXFHWncKJ38TzruPkZ4lATQQWSfxtQCPGpwBMA3qw+Gr/LTGlACw8sfIs7op7kVsZRPeM7Nx3RFd0E+fZPne+TgTmQmDxxN8FPEss7vbQEB3prwoDKA+PbZI71j6ewq5+5GeJQC0Ekvg7kEXW7eY9Kz62nT/e2OPk/xKBUghkaVMpJPM4iUBHCCTxOxqs7GoiUAqBJH4pJPM4iUBHCCTxOxqs7OrFCGQe5WJsdn2TxN+FSn7WDQIIb+o022EIJPEPwyt/3RgCUSDVWLea704Sv/khyg5ehoB1F+nmX4bQ7u+S+LtxyU87QEChlK21tgumOuh2E11M4jcxDNmJQxFQRWntgxLrbIcjkMQ/HLP8RwMI2HXnbL3FVix6aqBLXXUhid/VcGVnAwFLm+1UZG1EtsMRSOIfjln+Y2YEbKZiF91YRDVzd7o8fRK/y2FbbqeR/fr168NmJpnUGy8H45ecjT9n/jMROBgBS5ttnWbnZPdOyCm8gyF8zx+S+O+BI9+0iIA43lZlbnhp3j4t/fGjlMQ/HsM8QkUE7FH46KOPDlaem5+WvgzYSfwyOOZRCiKA3Fx7N8dg5T0rzU1LXw7kJH45LPNIhRCw25EboLL0NilNwhcCduswSfwtMPLlvAgguLsJPfPMM5s5+nTt64xJEr8OrnnUAxDg1lta63ZnboXljkKUQJL+ABAP/GkS/0DA8udlEUBu9fbuf/fggw9mCW5ZeC88WhL/Qmjyi5oIsPJRb68Kj5U/Zifjmn09xWMn8U9xVDu4JmW3TzzxxHAHIzceyQTetIOWxJ8W78WfjVV3z8K4l73inIzlpxeLJP70mJ/kGZEXqS9aLed7Vl4cL4GXC2zmFYMk/rz4n9TZ1dIrqb158+ZwXchuwwwKwTJasbznjOXnH/Yk/vxjcDI9MCV37dq1werfvn17uOmopJ27EbP0LH669m0M913r7Op/2+hK9uIUEHBzUXcYdrdh1l7m3i45EngZy7cxwutxuCuJ38ZYZC8SgckQwPvciGMyuPNEiUA7CCTx2xmL7EkiMBkCSfzJoM4TJQLtIJDEb2cssieJwGQIJPEngzpPlAi0g0ASv52xyJ4kApMhkMSfDOo8USLQDgJJ/HbGInuSCEyGQBJ/MqjzRIlAOwgk8dsZi+xJIjAZAkn8yaDOEyUC7SCQxG9nLLInicBkCCTxJ4M6T5QItINAEr+dscieJAKTIZDEnwzqPFEi0A4CSfx2xiJ7kghMhkASfzKo80SJQDsIJPHbGYvsSSIwGQJJ/MmgzhMlAu0gkMRvZyyyJ4nAZAgk8SeDOk+UCLSDQBK/nbHIniQCkyGQxJ8M6jxRItAOAkn8dsYie5IITIZAEn8yqPNEiUAikAgkAonAjAj8H+4FyMWonSP/AAAAAElFTkSuQmCC", + "document_status_id": 2 + } +] \ No newline at end of file diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/process_steps.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/process_steps.test.json new file mode 100644 index 00000000..5b84489a --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/process_steps.test.json @@ -0,0 +1,26 @@ +[ + { + "id": "5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa", + "process_step_type_id": 1, + "process_step_status_id": 2, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "629cc08f-bb0b-43ba-b20a-45c3538789b5", + "process_step_type_id": 2, + "process_step_status_id": 2, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "cd231cb8-55de-4ae4-b93f-d440512341fb", + "process_step_type_id": 3, + "process_step_status_id": 1, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + } +] diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/processes.test.json b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/processes.test.json new file mode 100644 index 00000000..4ed3254e --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/processes.test.json @@ -0,0 +1,8 @@ +[ + { + "id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "process_type_id" : 1, + "lock_expiry_date" : "2023-03-01 00:00:00.000000 +00:00", + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + } +] 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 new file mode 100644 index 00000000..02688638 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_external_type_detail_versions.test.json @@ -0,0 +1,34 @@ +[ + { + "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", + "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", + "valid_from": "2024-01-01 00:00:00.000000 +00:00", + "expiry": "2024-12-31 00:00:00.000000 +00:00" + }, + { + "id": "df3bd7d2-3349-410b-9b30-9a5238eb605e", + "verified_credential_external_type_id": 4, + "version": null, + "template": null, + "valid_from": "2024-01-01 00:00:00.000000 +00:00", + "expiry": "2999-12-31 23:59:59.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 new file mode 100644 index 00000000..b66a8edc --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Seeder/Data/verified_credential_type_assigned_use_cases.test.json @@ -0,0 +1,10 @@ +[ + { + "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/database/SsiCredentialIssuer.DbAccess.Tests/Setup/TestDbFixture.cs b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Setup/TestDbFixture.cs new file mode 100644 index 00000000..742d9c28 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/Setup/TestDbFixture.cs @@ -0,0 +1,114 @@ +/******************************************************************************** + * 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; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Auditing.Handler; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Migrations.Seeder; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; +using Testcontainers.PostgreSql; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +[assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests.Setup; + +public class TestDbFixture : IAsyncLifetime +{ + private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() + .WithDatabase("test_db") + .WithImage("postgres") + .WithCleanUp(true) + .WithName(Guid.NewGuid().ToString()) + .Build(); + + /// + /// Foreach test a new portalDbContext will be created and filled with the custom seeding data. + /// + /// + /// In this method the migrations don't need to get executed since they are already on the testcontainer. + /// Because of that the EnsureCreatedAsync is enough. + /// + /// the datetime provider + /// Additional data for the database + /// Returns the created PortalDbContext + public async Task GetDbContext(IDateTimeProvider? dateTimeProvider = null, params Action[] seedActions) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseNpgsql( + _container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_issuer") + ); + var context = new IssuerDbContext(optionsBuilder.Options, new AuditHandlerV1(new FakeIdentityIdService(), dateTimeProvider ?? new UtcDateTimeProvider())); + await context.Database.EnsureCreatedAsync().ConfigureAwait(false); + foreach (var seedAction in seedActions) + { + seedAction.Invoke(context); + } + + await context.SaveChangesAsync().ConfigureAwait(false); + return context; + } + + /// + /// This method is used to initially setup the database and run all migrations + /// + public async Task InitializeAsync() + { + await _container.StartAsync() + .ConfigureAwait(false); + + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseNpgsql( + _container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_issuer") + ); + var context = new IssuerDbContext(optionsBuilder.Options, new AuditHandlerV1(new FakeIdentityIdService(), new UtcDateTimeProvider())); + await context.Database.MigrateAsync(); + + var seederOptions = Options.Create(new SeederSettings + { + TestDataEnvironments = new[] { "test" }, + DataPaths = new[] { "Seeder/Data" } + }); + var insertSeeder = new BatchInsertSeeder(context, + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(), + seederOptions); + await insertSeeder.ExecuteAsync(CancellationToken.None); + var updateSeeder = new BatchUpdateSeeder(context, + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(), + seederOptions); + await updateSeeder.ExecuteAsync(CancellationToken.None); + } + + /// + public async Task DisposeAsync() + { + await _container.DisposeAsync() + .ConfigureAwait(false); + } +} diff --git a/tests/database/SsiCredentialIssuer.DbAccess.Tests/SsiCredentialIssuer.DbAccess.Tests.csproj b/tests/database/SsiCredentialIssuer.DbAccess.Tests/SsiCredentialIssuer.DbAccess.Tests.csproj new file mode 100644 index 00000000..ba3f11f3 --- /dev/null +++ b/tests/database/SsiCredentialIssuer.DbAccess.Tests/SsiCredentialIssuer.DbAccess.Tests.csproj @@ -0,0 +1,67 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests + Org.Eclipse.TractusX.SsiCredentialIssuer.DbAccess.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + diff --git a/tests/externalservices/Callback.Service.Tests/Callback.Service.Tests.csproj b/tests/externalservices/Callback.Service.Tests/Callback.Service.Tests.csproj new file mode 100644 index 00000000..0804c9db --- /dev/null +++ b/tests/externalservices/Callback.Service.Tests/Callback.Service.Tests.csproj @@ -0,0 +1,60 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Tests + Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/externalservices/Callback.Service.Tests/CallbackServiceTests.cs b/tests/externalservices/Callback.Service.Tests/CallbackServiceTests.cs new file mode 100644 index 00000000..70543600 --- /dev/null +++ b/tests/externalservices/Callback.Service.Tests/CallbackServiceTests.cs @@ -0,0 +1,122 @@ +/******************************************************************************** + * 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 Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; +using System.Net; +using System.Net.Http.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Callback.Service.Tests; + +public class CallbackServiceTests +{ + #region Initialization + + private readonly ITokenService _tokenService; + private readonly IOptions _options; + private readonly IFixture _fixture; + + public CallbackServiceTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _options = Options.Create(new CallbackSettings + { + Password = "passWord", + Scope = "test", + Username = "user@name", + ClientId = "CatenaX", + ClientSecret = "pass@Secret", + GrantType = "cred", + TokenAddress = "https://example.org/token" + }); + _tokenService = A.Fake(); + } + + #endregion + + #region TriggerCallback + + [Fact] + public async Task TriggerCallback_WithValid_DoesNotThrowException() + { + // Arrange + var data = new IssuerResponseData("Test1", IssuerResponseStatus.SUCCESSFUL, "test 123"); + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + + // Act + await sut.TriggerCallback("https://example.org/callback", data, CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(IssuerResponseData) && + ((x.Content as JsonContent)!.Value as IssuerResponseData)!.Status == IssuerResponseStatus.SUCCESSFUL && + ((x.Content as JsonContent)!.Value as IssuerResponseData)!.Bpn == "Test1" + ); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system callback failed with statuscode 409")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system callback failed with statuscode 400")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system callback failed with statuscode 400")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system callback failed with statuscode 403")] + public async Task TriggerCallback_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var httpMessageHandlerMock = content == null + ? new HttpMessageHandlerMock(statusCode) + : new HttpMessageHandlerMock(statusCode, new StringContent(content)); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new CallbackService(_tokenService, _options); + async Task Act() => await sut.TriggerCallback("https://example.org/callback", _fixture.Create(), CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion +} diff --git a/tests/externalservices/Portal.Service.Tests/Portal.Service.Tests.csproj b/tests/externalservices/Portal.Service.Tests/Portal.Service.Tests.csproj new file mode 100644 index 00000000..34f1ce9f --- /dev/null +++ b/tests/externalservices/Portal.Service.Tests/Portal.Service.Tests.csproj @@ -0,0 +1,60 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Tests + Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs b/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs new file mode 100644 index 00000000..65fb167f --- /dev/null +++ b/tests/externalservices/Portal.Service.Tests/PortalServiceTests.cs @@ -0,0 +1,178 @@ +/******************************************************************************** + * 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 Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Services; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Portal.Service.Tests; + +public class PortalServiceTests +{ + #region Initialization + + private readonly ITokenService _tokenService; + private readonly IOptions _options; + + public PortalServiceTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _options = Options.Create(new PortalSettings + { + Password = "passWord", + Scope = "test", + Username = "user@name", + BaseAddress = "https://base.address.com", + ClientId = "CatenaX", + ClientSecret = "pass@Secret", + GrantType = "cred", + TokenAddress = "https://example.org/token" + }); + _tokenService = A.Fake(); + } + + #endregion + + #region AddNotification + + [Fact] + public async Task AddNotification_WithValid_DoesNotThrowException() + { + // Arrange + var requester = Guid.NewGuid(); + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new PortalService(_tokenService, _options); + + // Act + await sut.AddNotification("Test", requester, NotificationTypeId.CREDENTIAL_APPROVAL, CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(NotificationRequest) && + ((x.Content as JsonContent)!.Value as NotificationRequest)!.Content == "Test" && + ((x.Content as JsonContent)!.Value as NotificationRequest)!.Requester == requester && + ((x.Content as JsonContent)!.Value as NotificationRequest)!.NotificationTypeId == NotificationTypeId.CREDENTIAL_APPROVAL + ); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system notification failed with statuscode 409")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system notification failed with statuscode 400")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system notification failed with statuscode 400")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system notification failed with statuscode 403")] + public async Task AddNotification_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var requester = Guid.NewGuid(); + var httpMessageHandlerMock = content == null + ? new HttpMessageHandlerMock(statusCode) + : new HttpMessageHandlerMock(statusCode, new StringContent(content)); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new PortalService(_tokenService, _options); + + // Act + async Task Act() => await sut.AddNotification("Test", requester, NotificationTypeId.CREDENTIAL_APPROVAL, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion + + #region TriggerMail + + [Fact] + public async Task TriggerMail_WithValid_DoesNotThrowException() + { + // Arrange + var requesterId = Guid.NewGuid(); + var httpMessageHandlerMock = + new HttpMessageHandlerMock(HttpStatusCode.OK); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)) + .Returns(httpClient); + var sut = new PortalService(_tokenService, _options); + + // Act + await sut.TriggerMail("Test", requesterId, new Dictionary(), CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(MailData) && + ((x.Content as JsonContent)!.Value as MailData)!.Template == "Test" && + ((x.Content as JsonContent)!.Value as MailData)!.Requester == requesterId + ); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system mail failed with statuscode 409")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system mail failed with statuscode 400")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system mail failed with statuscode 400")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system mail failed with statuscode 403")] + public async Task TriggerMail_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var requesterId = Guid.NewGuid(); + var httpMessageHandlerMock = content == null + ? new HttpMessageHandlerMock(statusCode) + : new HttpMessageHandlerMock(statusCode, new StringContent(content)); + using var httpClient = new HttpClient(httpMessageHandlerMock); + httpClient.BaseAddress = new Uri("https://base.address.com"); + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new PortalService(_tokenService, _options); + + // Act + async Task Act() => await sut.TriggerMail("Test", requesterId, new Dictionary(), 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/externalservices/Wallet.Service.Tests/BusinessLogic/WalletBusinessLogicTests.cs b/tests/externalservices/Wallet.Service.Tests/BusinessLogic/WalletBusinessLogicTests.cs new file mode 100644 index 00000000..a35049f1 --- /dev/null +++ b/tests/externalservices/Wallet.Service.Tests/BusinessLogic/WalletBusinessLogicTests.cs @@ -0,0 +1,238 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; +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.Wallet.Service.BusinessLogic; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Security.Cryptography; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Tests.BusinessLogic; + +public class WalletBusinessLogicTests +{ + private static readonly string IssuerBpnl = "BPNL000001ISSUER"; + + private readonly WalletBusinessLogic _sut; + private readonly IWalletService _walletService; + private readonly ICompanySsiDetailsRepository _companySsiDetailRepository; + private readonly IDocumentRepository _documentRepository; + private readonly EncryptionModeConfig _encryptionModeConfig; + + public WalletBusinessLogicTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _encryptionModeConfig = new EncryptionModeConfig + { + Index = 0, + CipherMode = CipherMode.ECB, + PaddingMode = PaddingMode.PKCS7, + EncryptionKey = "202048656c6c6f20202048656c6c6f20202048656c6c6f20202048656c6c6f20" + }; + var options = Options.Create(new WalletSettings + { + BaseAddress = "https://base.address.com", + ClientId = "CatenaX", + ClientSecret = "pass@Secret", + TokenAddress = "https://example.org/token", + EncryptionConfigs = Enumerable.Repeat(_encryptionModeConfig, 1), + EncrptionConfigIndex = 0 + }); + _walletService = A.Fake(); + var issuerRepositories = A.Fake(); + _companySsiDetailRepository = A.Fake(); + _documentRepository = A.Fake(); + A.CallTo(() => issuerRepositories.GetInstance()) + .Returns(_companySsiDetailRepository); + A.CallTo(() => issuerRepositories.GetInstance()) + .Returns(_documentRepository); + + _sut = new WalletBusinessLogic(_walletService, issuerRepositories, options); + } + + #region CreateCredential + + [Fact] + public async Task CreateCredential_CallsExpected() + { + // Arrange + var id = Guid.NewGuid(); + var externalId = Guid.NewGuid(); + var schema = JsonDocument.Parse("{}"); + var ssiDetail = new CompanySsiDetail(id, null!, VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, IssuerBpnl, Guid.NewGuid(), DateTimeOffset.UtcNow); + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyCompanySsiDetails(A._, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action setupOptionalFields) => + { + initialize?.Invoke(ssiDetail); + setupOptionalFields(ssiDetail); + }); + A.CallTo(() => _walletService.CreateCredential(schema, A._)) + .Returns(externalId); + + // Act + await _sut.CreateCredential(id, schema, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyCompanySsiDetails(id, A>._, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _walletService.CreateCredential(schema, A._)) + .MustHaveHappenedOnceExactly(); + ssiDetail.ExternalCredentialId = externalId; + } + + #endregion + + #region SignCredential + + [Fact] + public async Task SignCredential_CallsExpected() + { + // Arrange + var id = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + var ssiDetail = new CompanySsiDetail(id, null!, VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, IssuerBpnl, Guid.NewGuid(), DateTimeOffset.UtcNow); + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyCompanySsiDetails(A._, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action setupOptionalFields) => + { + initialize?.Invoke(ssiDetail); + setupOptionalFields(ssiDetail); + }); + A.CallTo(() => _walletService.SignCredential(credentialId, A._)) + .Returns("cred"); + + // Act + await _sut.SignCredential(id, credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyCompanySsiDetails(id, A>._, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _walletService.SignCredential(credentialId, A._)) + .MustHaveHappenedOnceExactly(); + ssiDetail.Credential.Should().Be("cred"); + } + + #endregion + + #region CreateCredentialForHolder + + [Fact] + public async Task CreateCredentialForHolder_CallsExpected() + { + // Arrange + var id = Guid.NewGuid(); + var processData = new CompanySsiProcessData(id, null!, VerifiedCredentialTypeKindId.BPN) { ClientId = "123" }; + var (secret, vector) = CryptoHelper.Encrypt("test", Convert.FromHexString(_encryptionModeConfig.EncryptionKey), _encryptionModeConfig.CipherMode, _encryptionModeConfig.PaddingMode); + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyProcessData(A._, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action setupOptionalFields) => + { + initialize?.Invoke(processData); + setupOptionalFields(processData); + }); + + // Act + await _sut.CreateCredentialForHolder(id, "https://example.org/wallet", "test1", new EncryptionInformation(secret, vector, 0), "thisisatestsecret", CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _companySsiDetailRepository.AttachAndModifyProcessData(id, A>._, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _walletService.CreateCredentialForHolder(A._, A._, A._, A._, A._)) + .MustHaveHappenedOnceExactly(); + processData.ClientId.Should().BeNull(); + processData.ClientSecret.Should().BeNull(); + } + + #endregion + + #region GetCredential + + [Fact] + public async Task GetCredential_CallsExpected() + { + // Arrange + const string data = """ + { + "id": "2e70ee49-5fae-438a-9435-0cce3854650d", + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/catenax/credentials/v1.0.0" + ], + "type": [ + "VerifiableCredential", + "BpnCredential" + ], + "issuanceDate": "2022-06-16T18:56:59Z", + "expirationDate": "2022-06-16T18:56:59Z", + "issuer": "2e70ee49-5fae-438a-9435-0cce3854650d", + "credentialSubject": { + "id": "2e70ee49-5fae-438a-9435-0cce3854650d", + "holderIdentifier": "2e70ee49-5fae-438a-9435-0cce3854650d", + "bpn": "2e70ee49-5fae-438a-9435-0cce3854650d" + } + } + """; + var id = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + var jsonDocument = JsonDocument.Parse(data); + A.CallTo(() => _walletService.GetCredential(credentialId, A._)) + .Returns(jsonDocument); + + // Act + await _sut.GetCredential(id, credentialId, VerifiedCredentialTypeKindId.BPN, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _documentRepository.CreateDocument(A._, A._, A._, MediaTypeId.JSON, DocumentTypeId.VERIFIED_CREDENTIAL, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _documentRepository.AssignDocumentToCompanySsiDetails(A._, id)) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task GetCredential_WithSchemaNotMatching_CallsExpected() + { + // Arrange + const string data = """ + { + "id": "2e70ee49-5fae-438a-9435-0cce3854650d", + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/catenax/credentials/v1.0.0" + ], + "expirationDate": "2022-06-16T18:56:59Z", + "credentialSubject": { + "id": "2e70ee49-5fae-438a-9435-0cce3854650d", + "holderIdentifier": "2e70ee49-5fae-438a-9435-0cce3854650d", + "bpn": "2e70ee49-5fae-438a-9435-0cce3854650d" + } + } + """; + var id = Guid.NewGuid(); + var credentialId = Guid.NewGuid(); + var jsonDocument = JsonDocument.Parse(data); + A.CallTo(() => _walletService.GetCredential(credentialId, A._)) + .Returns(jsonDocument); + async Task Act() => await _sut.GetCredential(id, credentialId, VerifiedCredentialTypeKindId.BPN, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be("Invalid schema for type BPN"); + } + + #endregion +} diff --git a/tests/externalservices/Wallet.Service.Tests/Services/BasicAuthTokenServiceTests.cs b/tests/externalservices/Wallet.Service.Tests/Services/BasicAuthTokenServiceTests.cs new file mode 100644 index 00000000..37117803 --- /dev/null +++ b/tests/externalservices/Wallet.Service.Tests/Services/BasicAuthTokenServiceTests.cs @@ -0,0 +1,127 @@ +/******************************************************************************** + * 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.Portal.Backend.Framework.ErrorHandling.Web; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared.Extensions; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Net; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Tests.Services; + +public class BasicAuthTokenServiceTests +{ + private readonly string _accessToken; + private readonly CancellationToken _cancellationToken; + private readonly TestException _testException; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IFixture _fixture; + private readonly Uri _validBaseAddress = new("https://validurl.com"); + + public BasicAuthTokenServiceTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _accessToken = _fixture.Create(); + _cancellationToken = new CancellationToken(); + _testException = _fixture.Create(); + + _httpClientFactory = A.Fake(); + } + + #region GetAuthorizedClient + + [Fact] + public async Task GetAuthorizedClient_Success() + { + var authResponse = JsonSerializer.Serialize(_fixture.Build().With(x => x.AccessToken, _accessToken).Create()); + SetupForGetAuthorized(new HttpMessageHandlerMock(HttpStatusCode.OK, authResponse.ToFormContent("application/json"))); + + var settings = _fixture.Create(); + + var sut = new BasicAuthTokenService(_httpClientFactory); + + var result = await sut.GetBasicAuthorizedClient(settings, _cancellationToken).ConfigureAwait(false); + + result.Should().NotBeNull(); + result.BaseAddress.Should().Be(_validBaseAddress); + } + + [Fact] + public async Task GetAuthorizedClient_HttpClientThrows_Throws() + { + SetupForGetAuthorized(new HttpMessageHandlerMock(HttpStatusCode.InternalServerError, ex: _testException)); + var settings = _fixture.Create(); + + var sut = new BasicAuthTokenService(_httpClientFactory); + + var act = () => sut.GetBasicAuthorizedClient(settings, _cancellationToken); + + var error = await Assert.ThrowsAsync(act).ConfigureAwait(false); + + error.Should().NotBeNull(); + error.InnerException.Should().Be(_testException); + error.Message.Should().Be("call to external system token-post failed"); + } + + #endregion + + #region Setup + + private void SetupForGetAuthorized(HttpMessageHandler httpMessageHandler) + { + var httpClientAuth = new HttpClient(httpMessageHandler) + { + BaseAddress = _fixture.Create() + }; + var httpClient = new HttpClient(httpMessageHandler) + { + BaseAddress = _validBaseAddress + }; + A.CallTo(() => _httpClientFactory.CreateClient($"{typeof(T).Name}Auth")) + .Returns(httpClientAuth); + A.CallTo(() => _httpClientFactory.CreateClient(typeof(T).Name)) + .Returns(httpClient); + } + + #endregion + + [Serializable] + public class TestException : Exception + { + public TestException() { } + public TestException(string message) : base(message) { } + public TestException(string message, Exception inner) : base(message, inner) { } + + protected TestException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs b/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs new file mode 100644 index 00000000..7c72d5ca --- /dev/null +++ b/tests/externalservices/Wallet.Service.Tests/Services/WalletServiceTests.cs @@ -0,0 +1,274 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using FakeItEasy; +using FluentAssertions; +using Json.More; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Tests.Shared; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.DependencyInjection; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services; +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Tests.Services; + +public class WalletServiceTests +{ + private readonly WalletService _sut; + private readonly IBasicAuthTokenService _basicAuthTokenService; + private readonly IOptions _options; + + public WalletServiceTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _basicAuthTokenService = A.Fake(); + + _options = Options.Create(new WalletSettings + { + BaseAddress = "https://base.address.com", + ClientId = "CatenaX", + ClientSecret = "pass@Secret", + TokenAddress = "https://example.org/token", + EncrptionConfigIndex = 0 + }); + _sut = new WalletService(_basicAuthTokenService, _options); + } + + #region CreateCredential + + [Fact] + public async Task CreateCredential_WithValid_DoesNotThrowException() + { + // Arrange + var payload = JsonDocument.Parse("{}"); + 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(_options.Value, A._)) + .Returns(httpClient); + + // Act + var result = await _sut.CreateCredential(payload, CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(CreateCredentialRequest) && + ((x.Content as JsonContent)!.Value as CreateCredentialRequest)!.Application == "catena-x-portal" + ); + result.Should().Be(id); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system create-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system create-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system create-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system create-credential failed with statuscode 403")] + public async Task CreateCredential_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var payload = JsonDocument.Parse("{}"); + 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(_options.Value, A._)).Returns(httpClient); + + // Act + async Task Act() => await _sut.CreateCredential(payload, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion + + #region SignCredential + + [Fact] + public async Task SignCredential_WithValid_DoesNotThrowException() + { + // Arrange + var credentialId = Guid.NewGuid(); + const string jwt = "thisisonlyatestexample"; + var response = new SignCredentialResponse(jwt); + 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(_options.Value, A._)) + .Returns(httpClient); + + // Act + var result = await _sut.SignCredential(credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(SignCredentialRequest) && + ((x.Content as JsonContent)!.Value as SignCredentialRequest)!.Payload.Sign.ProofMechanism == "external" && + ((x.Content as JsonContent)!.Value as SignCredentialRequest)!.Payload.Sign.ProofType == "jwt" + ); + result.Should().Be(jwt); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system sign-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system sign-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system sign-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system sign-credential failed with statuscode 403")] + public async Task SignCredential_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var credentialId = Guid.NewGuid(); + 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(_options.Value, A._)).Returns(httpClient); + + // Act + async Task Act() => await _sut.SignCredential(credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion + + #region GetCredential + + [Fact] + public async Task GetCredential_WithValid_DoesNotThrowException() + { + // Arrange + var credentialId = Guid.NewGuid(); + var json = """ + { + "root": "123" + } + """; + var response = new GetCredentialResponse("test", JsonDocument.Parse(json), "test123"); + 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(_options.Value, A._)) + .Returns(httpClient); + + // Act + var result = await _sut.GetCredential(credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + result.RootElement.ToJsonString().Should().Be("{\"root\":\"123\"}"); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system get-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system get-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system get-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system get-credential failed with statuscode 403")] + public async Task GetCredential_WithConflict_ThrowsServiceExceptionWithErrorContent(HttpStatusCode statusCode, string? content, string message) + { + // Arrange + var credentialId = Guid.NewGuid(); + 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(_options.Value, A._)).Returns(httpClient); + + // Act + async Task Act() => await _sut.GetCredential(credentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be(message); + ex.StatusCode.Should().Be(statusCode); + } + + #endregion + + #region CreateCredentialForHolder + + [Fact] + public async Task CreateCredentialForHolder_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 + var result = await _sut.CreateCredentialForHolder("https://example.org", "test", "testSec", "testCred", CancellationToken.None).ConfigureAwait(false); + + // Assert + httpMessageHandlerMock.RequestMessage.Should().Match(x => + x.Content is JsonContent && + (x.Content as JsonContent)!.ObjectType == typeof(DeriveCredentialData) && + ((x.Content as JsonContent)!.Value as DeriveCredentialData)!.Application == "catena-x-portal" + ); + result.Should().Be(id); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "{ \"message\": \"Framework test!\" }", "call to external system create-holder-credential failed with statuscode 409 - Message: { \"message\": \"Framework test!\" }")] + [InlineData(HttpStatusCode.BadRequest, "{ \"test\": \"123\" }", "call to external system create-holder-credential failed with statuscode 400 - Message: { \"test\": \"123\" }")] + [InlineData(HttpStatusCode.BadRequest, "this is no json", "call to external system create-holder-credential failed with statuscode 400 - Message: this is no json")] + [InlineData(HttpStatusCode.Forbidden, null, "call to external system create-holder-credential failed with statuscode 403")] + public async Task CreateCredentialForHolder_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.CreateCredentialForHolder("https://example.org", "test", "testSec", "testCred", 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/externalservices/Wallet.Service.Tests/Wallet.Service.Tests.csproj b/tests/externalservices/Wallet.Service.Tests/Wallet.Service.Tests.csproj new file mode 100644 index 00000000..8334c270 --- /dev/null +++ b/tests/externalservices/Wallet.Service.Tests/Wallet.Service.Tests.csproj @@ -0,0 +1,61 @@ + + + + + Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Tests + Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Tests + net7.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/issuer/SsiCredentialIssuer.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs b/tests/issuer/SsiCredentialIssuer.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs new file mode 100644 index 00000000..45a7a059 --- /dev/null +++ b/tests/issuer/SsiCredentialIssuer.Service.Tests/Authentication/KeycloakClaimsTransformationTests.cs @@ -0,0 +1,97 @@ +/******************************************************************************** + * 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.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Authentication; +using System.Security.Claims; +using System.Text.Json; +using IssuerClaims = Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Identity; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Tests.Authentication; + +public class KeycloakClaimsTransformationTests +{ + private readonly KeycloakClaimsTransformation _sut; + + public KeycloakClaimsTransformationTests() + { + var options = Options.Create(new JwtBearerOptions + { + TokenValidationParameters = new TokenValidationParameters + { + ValidAudience = "validAudience" + } + }); + _sut = new KeycloakClaimsTransformation(options); + } + + [Fact] + public async Task TransformAsync_WithoutRoles_ReturnsExpected() + { + // Arrange + var claims = new Claim[] + { + new(IssuerClaims.ClaimTypes.Bpn, "BPNL00001243TEST") + }; + var identity = new ClaimsIdentity(claims); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().ContainSingle() + .And.Satisfy(x => x.Type == IssuerClaims.ClaimTypes.Bpn && x.Value == "BPNL00001243TEST"); + } + + [Fact] + public async Task TransformAsync_WithRoles_ReturnsExpected() + { + // Arrange + var json = JsonSerializer.Serialize(new { validAudience = new { roles = Enumerable.Repeat("testRole", 1) } }); + var identity = new ClaimsIdentity(Enumerable.Repeat(new Claim(CustomClaimTypes.ResourceAccess, json, "JSON"), 1)); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().HaveCount(2).And.Satisfy( + x => x.Type == CustomClaimTypes.ResourceAccess && x.Value == json, + x => x.Type == ClaimTypes.Role && x.Value == "testRole"); + } + + [Fact] + public async Task TransformAsync_WithIntRole_ReturnsExpected() + { + // Arrange + var json = JsonSerializer.Serialize(new { validAudience = new { roles = Enumerable.Repeat(1, 1) } }); + var identity = new ClaimsIdentity(Enumerable.Repeat(new Claim(CustomClaimTypes.ResourceAccess, json, "JSON"), 1)); + var principal = new ClaimsPrincipal(identity); + + // Act + var result = await _sut.TransformAsync(principal).ConfigureAwait(false); + + // Assert + result.Claims.Should().ContainSingle() + .And.Satisfy(x => x.Type == CustomClaimTypes.ResourceAccess && x.Value == json); + } +} diff --git a/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs new file mode 100644 index 00000000..4f35795d --- /dev/null +++ b/tests/issuer/SsiCredentialIssuer.Service.Tests/BusinessLogic/IssuerBusinessLogicTests.cs @@ -0,0 +1,815 @@ +/******************************************************************************** + * 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.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +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.Models; +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.Service.Models; +using Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Tests.Setup; +using System.Net; +using System.Security.Cryptography; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.Tests.BusinessLogic; + +public class IssuerBusinessLogicTests +{ + private static readonly Guid CredentialId = Guid.NewGuid(); + private static readonly string Bpnl = "BPNL00000001TEST"; + private static readonly string IssuerBpnl = "BPNL000001ISSUER"; + + private readonly IFixture _fixture; + private readonly ICompanySsiDetailsRepository _companySsiDetailsRepository; + private readonly IDocumentRepository _documentRepository; + private readonly IProcessStepRepository _processStepRepository; + + private readonly IIssuerBusinessLogic _sut; + private readonly IIdentityService _identityService; + private readonly IDateTimeProvider _dateTimeProvider; + private readonly IHttpClientFactory _clientFactory; + private readonly IPortalService _portalService; + private readonly IIssuerRepositories _issuerRepositories; + private readonly IIdentityData _identity; + + public IssuerBusinessLogicTests() + { + _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(); + _companySsiDetailsRepository = A.Fake(); + _documentRepository = A.Fake(); + _processStepRepository = A.Fake(); + _identity = A.Fake(); + + _identityService = A.Fake(); + _dateTimeProvider = A.Fake(); + _clientFactory = A.Fake(); + _portalService = A.Fake(); + + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_companySsiDetailsRepository); + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_documentRepository); + A.CallTo(() => _issuerRepositories.GetInstance()).Returns(_processStepRepository); + + A.CallTo(() => _identity.IdentityId).Returns(Guid.NewGuid()); + A.CallTo(() => _identity.Bpnl).Returns(Bpnl); + A.CallTo(() => _identityService.IdentityData).Returns(_identity); + + var options = A.Fake>(); + A.CallTo(() => options.Value).Returns(new IssuerSettings + { + EncryptionConfigs = Enumerable.Repeat(new EncryptionModeConfig + { + Index = 0, + CipherMode = CipherMode.ECB, + PaddingMode = PaddingMode.PKCS7, + EncryptionKey = "zlWxjv54PrNDbjYx7d3m4nz88qmCHG0AhYwu0UYSFGTo9psPbcVsNiqr14zhRgSd" + }, 1), + MaxPageSize = 15, + IssuerDid = "did:web:example:org:bpn:18273z682734rt", + IssuerBpn = IssuerBpnl, + EncrptionConfigIndex = 0, + StatusListUrl = "https://example.org/statuslist" + }); + + _sut = new IssuerBusinessLogic(_issuerRepositories, _identityService, _dateTimeProvider, _clientFactory, _portalService, options); + } + + #region GetUseCaseParticipationAsync + + [Fact] + public async Task GetUseCaseParticipationAsync_ReturnsExpected() + { + // Arrange + Setup_GetUseCaseParticipationAsync(); + + // Act + var result = await _sut.GetUseCaseParticipationAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(5); + } + + #endregion + + #region GetSsiCertificatesAsync + + [Fact] + public async Task GetSsiCertificatesAsync_ReturnsExpected() + { + // Arrange + Setup_GetSsiCertificatesAsync(); + + // Act + var result = await _sut.GetSsiCertificatesAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(5); + } + + #endregion + + #region ApproveCredential + + [Fact] + public async Task ApproveCredential_WithoutExistingSsiDetail_ThrowsNotFoundException() + { + // Arrange + var notExistingId = Guid.NewGuid(); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(notExistingId)) + .Returns(new ValueTuple()); + async Task Act() => await _sut.ApproveCredential(notExistingId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be(CredentialErrors.SSI_DETAILS_NOT_FOUND.ToString()); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Theory] + [InlineData(CompanySsiDetailStatusId.ACTIVE)] + [InlineData(CompanySsiDetailStatusId.INACTIVE)] + public async Task ApproveCredential_WithStatusNotPending_ThrowsConflictException(CompanySsiDetailStatusId statusId) + { + // Arrange + var alreadyActiveId = Guid.NewGuid(); + var approvalData = _fixture.Build() + .With(x => x.Status, statusId) + .Create(); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId)) + .Returns(new ValueTuple(true, approvalData)); + async Task Act() => await _sut.ApproveCredential(alreadyActiveId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be(CredentialErrors.CREDENTIAL_NOT_PENDING.ToString()); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Fact] + public async Task ApproveCredential_WithBpnNotSetActiveSsiDetail_ThrowsConflictException() + { + // Arrange + var alreadyActiveId = Guid.NewGuid(); + var approvalData = _fixture.Build() + .With(x => x.Status, CompanySsiDetailStatusId.PENDING) + .With(x => x.Bpn, (string?)null) + .Create(); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId)) + .Returns(new ValueTuple(true, approvalData)); + async Task Act() => await _sut.ApproveCredential(alreadyActiveId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be(CredentialErrors.BPN_NOT_SET.ToString()); + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Fact] + public async Task ApproveCredential_WithExpiryInThePast_ReturnsExpected() + { + // Arrange + const VerifiedCredentialTypeId typeId = VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK; + var now = DateTimeOffset.Now; + var detailData = new DetailData( + VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, + "test", + "1.0.0", + DateTimeOffset.Now.AddDays(-5) + ); + + var data = new SsiApprovalData( + CompanySsiDetailStatusId.PENDING, + typeId, + null, + VerifiedCredentialTypeKindId.FRAMEWORK, + Bpnl, + detailData + ); + + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId)) + .Returns(new ValueTuple(true, data)); + async Task Act() => await _sut.ApproveCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be(CredentialErrors.EXPIRY_DATE_IN_PAST.ToString()); + + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _portalService.AddNotification(A._, A._, A._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Fact] + public async Task ApproveCredential_WithInvalidCredentialType_ThrowsException() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var useCaseData = new DetailData( + VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, + "test", + "1.0.0", + DateTimeOffset.UtcNow + ); + + var data = new SsiApprovalData( + CompanySsiDetailStatusId.PENDING, + default, + null, + VerifiedCredentialTypeKindId.FRAMEWORK, + Bpnl, + useCaseData + ); + + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId)) + .Returns(new ValueTuple(true, data)); + + // Act + async Task Act() => await _sut.ApproveCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + ex.Message.Should().Be(CredentialErrors.CREDENTIAL_TYPE_NOT_FOUND.ToString()); + } + + [Fact] + public async Task ApproveCredential_WithDetailVersionNotSet_ThrowsConflictException() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var data = new SsiApprovalData( + CompanySsiDetailStatusId.PENDING, + VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + null, + VerifiedCredentialTypeKindId.FRAMEWORK, + Bpnl, + null + ); + + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId)) + .Returns(new ValueTuple(true, data)); + async Task Act() => await _sut.ApproveCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + + A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) + .MustNotHaveHappened(); + ex.Message.Should().Be(CredentialErrors.EXTERNAL_TYPE_DETAIL_ID_NOT_SET.ToString()); + } + + [Fact] + public async Task ApproveCredential_WithAlreadyLinkedProcess_ThrowsConflictException() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var data = new SsiApprovalData( + CompanySsiDetailStatusId.PENDING, + VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + Guid.NewGuid(), + VerifiedCredentialTypeKindId.FRAMEWORK, + Bpnl, + new DetailData( + VerifiedCredentialExternalTypeId.TRACEABILITY_CREDENTIAL, + "test", + "1.0.0", + DateTimeOffset.UtcNow + ) + ); + + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId)) + .Returns(new ValueTuple(true, data)); + async Task Act() => await _sut.ApproveCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + A.CallTo(() => _portalService.TriggerMail("CredentialApproval", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + + A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) + .MustNotHaveHappened(); + ex.Message.Should().Be(CredentialErrors.ALREADY_LINKED_PROCESS.ToString()); + } + + [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 now = DateTimeOffset.UtcNow; + var detailData = new DetailData( + externalTypeId, + "test", + "1.0.0", + DateTimeOffset.UtcNow + ); + + var data = new SsiApprovalData( + CompanySsiDetailStatusId.PENDING, + typeId, + null, + kindId, + Bpnl, + detailData + ); + + var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, typeId, CompanySsiDetailStatusId.PENDING, "", Guid.NewGuid(), DateTimeOffset.Now); + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(CredentialId)) + .Returns(new ValueTuple(true, data)); + A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A?>._, A>._!)) + .Invokes((Guid _, Action? initialize, Action updateFields) => + { + initialize?.Invoke(detail); + updateFields.Invoke(detail); + }); + + // 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(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _processStepRepository.CreateProcess(ProcessTypeId.CREATE_CREDENTIAL)) + .MustHaveHappenedOnceExactly(); + + detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.ACTIVE); + detail.DateLastChanged.Should().Be(now); + } + + #endregion + + #region RejectCredential + + [Fact] + public async Task RejectCredential_WithoutExistingSsiDetail_ThrowsNotFoundException() + { + // Arrange + var notExistingId = Guid.NewGuid(); + A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(notExistingId)) + .Returns(new ValueTuple>()); + async Task Act() => await _sut.RejectCredential(notExistingId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be(CredentialErrors.SSI_DETAILS_NOT_FOUND.ToString()); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Theory] + [InlineData(CompanySsiDetailStatusId.ACTIVE)] + [InlineData(CompanySsiDetailStatusId.INACTIVE)] + public async Task RejectCredential_WithNotPendingSsiDetail_ThrowsNotFoundException(CompanySsiDetailStatusId status) + { + // Arrange + var alreadyInactiveId = Guid.NewGuid(); + A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(alreadyInactiveId)) + .Returns(new ValueTuple>( + true, + status, + VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + null, + Enumerable.Empty() + )); + async Task Act() => await _sut.RejectCredential(alreadyInactiveId, CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + + // Assert + ex.Message.Should().Be(CredentialErrors.CREDENTIAL_NOT_PENDING.ToString()); + A.CallTo(() => _portalService.TriggerMail("CredentialRejected", A._, A>._, A._)).MustNotHaveHappened(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustNotHaveHappened(); + } + + [Fact] + public async Task RejectCredential_WithValidRequest_ReturnsExpected() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, IssuerBpnl, Guid.NewGuid(), DateTimeOffset.Now); + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(CredentialId)) + .Returns(new ValueTuple>( + true, + CompanySsiDetailStatusId.PENDING, + VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + null, + Enumerable.Empty())); + A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A?>._, A>._!)) + .Invokes((Guid _, Action? initialize, Action updateFields) => + { + initialize?.Invoke(detail); + updateFields.Invoke(detail); + }); + + // Act + await _sut.RejectCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + 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(); + + detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.INACTIVE); + detail.DateLastChanged.Should().Be(now); + } + + [Fact] + public async Task RejectCredential_WithValidRequestAndPendingProcessStepIds_ReturnsExpectedAndSkipsSteps() + { + // Arrange + var now = DateTimeOffset.UtcNow; + var detail = new CompanySsiDetail(CredentialId, _identity.Bpnl, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, CompanySsiDetailStatusId.PENDING, IssuerBpnl, Guid.NewGuid(), DateTimeOffset.Now); + A.CallTo(() => _dateTimeProvider.OffsetNow).Returns(now); + A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(CredentialId)) + .Returns(new ValueTuple>( + true, + CompanySsiDetailStatusId.PENDING, + VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, + Guid.NewGuid(), + Enumerable.Repeat(Guid.NewGuid(), 1))); + A.CallTo(() => _companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(CredentialId, A?>._, A>._!)) + .Invokes((Guid _, Action? initialize, Action updateFields) => + { + initialize?.Invoke(detail); + updateFields.Invoke(detail); + }); + + // Act + await _sut.RejectCredential(CredentialId, CancellationToken.None).ConfigureAwait(false); + + // Assert + 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(); + + detail.CompanySsiDetailStatusId.Should().Be(CompanySsiDetailStatusId.INACTIVE); + detail.DateLastChanged.Should().Be(now); + } + + #endregion + + #region GetCertificateTypes + + [Fact] + public async Task GetCertificateTypes_ReturnsExpected() + { + // Arrange + A.CallTo(() => _companySsiDetailsRepository.GetCertificateTypes(A._)) + .Returns(Enum.GetValues().ToAsyncEnumerable()); + + // Act + var result = await _sut.GetCertificateTypes().ToListAsync().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(7); + } + + #endregion + + #region CreateBpnCredential + + [Fact] + public async Task CreateBpnCredential_ReturnsExpected() + { + // Arrange + var didId = Guid.NewGuid().ToString(); + var didDocument = new DidDocument(didId); + var data = new CreateBpnCredentialRequest("https://example.org/holder/BPNL12343546/did.json", Bpnl, null, null); + HttpRequestMessage? request = null; + ConfigureHttpClientFactoryFixture(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonSerializer.Serialize(didDocument)) + }, requestMessage => request = requestMessage); + + // Act + await _sut.CreateBpnCredential(data, CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _documentRepository.CreateDocument("schema.json", A._, A._, MediaTypeId.JSON, DocumentTypeId.PRESENTATION, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _companySsiDetailsRepository.CreateSsiDetails(_identity.Bpnl, VerifiedCredentialTypeId.BUSINESS_PARTNER_NUMBER, CompanySsiDetailStatusId.ACTIVE, IssuerBpnl, _identity.IdentityId, A>._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _documentRepository.AssignDocumentToCompanySsiDetails(A._, A._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _issuerRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + } + + [Theory] + [InlineData("htt://test.com")] + [InlineData("http://test.com")] + [InlineData("abc://test.com")] + [InlineData("test.com/example")] + [InlineData("test")] + [InlineData("https://testsite.test/