From 347e1873e7504639aa0e4961ebf0252e7a4e951e Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Fri, 13 Sep 2024 07:59:14 +0200 Subject: [PATCH] feat(technicalUser): adjust service account to new provisioning api Refs: #79 --- src/clients/Dim.Clients/Api/Dim/DimClient.cs | 4 +- .../Api/Div/IProvisioningClient.cs | 1 + .../Api/Div/Models/CreateOperationRequest.cs | 1 - .../ServiceKeyOperationCreationRequest.cs | 34 +++ .../Dim.Clients/Api/Div/ProvisioningClient.cs | 44 +++- .../Dim.DbAccess/Models/ProcessData.cs | 33 +++ .../Dim.DbAccess/Models}/VerifyProcessData.cs | 2 +- .../Repositories/IProcessStepRepository.cs | 4 + .../Repositories/ITenantRepository.cs | 3 +- .../Repositories/ProcessStepRepository.cs | 43 ++++ .../Repositories/TenantRepository.cs | 18 +- .../Dim.Entities/Entities/TechnicalUser.cs | 1 + src/database/Dim.Entities/Entities/Tenant.cs | 1 + .../Dim.Entities/Enums/ProcessStepTypeId.cs | 13 +- .../Extensions/ProcessStepTypeIdExtensions.cs | 40 ++++ ...6091209_79-AdjustProvisioning.Designer.cs} | 69 +++++- ...> 20240916091209_79-AdjustProvisioning.cs} | 219 +++++++++++++----- .../Migrations/DimDbContextModelSnapshot.cs | 66 +++++- .../DimProcessTypeExecutor.cs | 21 +- .../TechnicalUserProcessTypeExecutor.cs | 19 +- .../Callback/CallbackService.cs | 6 +- .../DimProcess.Library/DimProcessHandler.cs | 2 + .../TechnicalUserProcessHandler.cs | 87 +++++-- .../ManualProcessStepDataExtensions.cs | 1 + .../ProcessExecutionService.cs | 6 +- .../ProcessExecutor.cs | 1 + src/processes/Processes.Worker/Program.cs | 2 +- .../Dim.Web/BusinessLogic/DimBusinessLogic.cs | 67 +++++- .../BusinessLogic/IDimBusinessLogic.cs | 5 + src/web/Dim.Web/Controllers/DimController.cs | 54 +++++ src/web/Dim.Web/Dim.Web.csproj | 1 + .../DimProcessTypeExecutorTests.cs | 7 +- .../TechnicalUserProcessTypeExecutorTests.cs | 7 +- .../DimProcessHandlerTests.cs | 20 +- .../TechnicalUserProcessHandlerTests.cs | 129 ++++++++++- .../ManualProcessDataExtensionsTests.cs | 1 + .../ProcessExecutorTests.cs | 4 +- 37 files changed, 884 insertions(+), 152 deletions(-) create mode 100644 src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs create mode 100644 src/database/Dim.DbAccess/Models/ProcessData.cs rename src/{processes/Processes.Library => database/Dim.DbAccess/Models}/VerifyProcessData.cs (97%) create mode 100644 src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs rename src/database/Dim.Migrations/Migrations/{20240912045058_AddDivProvisioning.Designer.cs => 20240916091209_79-AdjustProvisioning.Designer.cs} (88%) rename src/database/Dim.Migrations/Migrations/{20240912045058_AddDivProvisioning.cs => 20240916091209_79-AdjustProvisioning.cs} (71%) diff --git a/src/clients/Dim.Clients/Api/Dim/DimClient.cs b/src/clients/Dim.Clients/Api/Dim/DimClient.cs index 5ba1249..ec0073a 100644 --- a/src/clients/Dim.Clients/Api/Dim/DimClient.cs +++ b/src/clients/Dim.Clients/Api/Dim/DimClient.cs @@ -41,7 +41,7 @@ public async Task GetCompanyData(BasicAuthSettings dimAuth, string { var response = await result.Content .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); if (response?.Data == null || response.Data.Count() != 1) { throw new ConflictException("There is no matching company"); @@ -85,7 +85,7 @@ public async Task CreateStatusList(BasicAuthSettings dimAuth, string dim .CatchingIntoServiceExceptionFor("assign-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async m => { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); + var message = await m.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); return (false, message); }).ConfigureAwait(false); try diff --git a/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs b/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs index d1f12cc..944137f 100644 --- a/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs +++ b/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs @@ -26,4 +26,5 @@ public interface IProvisioningClient { public Task CreateOperation(Guid customerId, string customerName, string applicationName, string companyName, string didDocumentLocation, bool isIssuer, CancellationToken cancellationToken); public Task GetOperation(Guid operationId, CancellationToken cancellationToken); + Task CreateServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken); } diff --git a/src/clients/Dim.Clients/Api/Div/Models/CreateOperationRequest.cs b/src/clients/Dim.Clients/Api/Div/Models/CreateOperationRequest.cs index ba2af39..fa74af0 100644 --- a/src/clients/Dim.Clients/Api/Div/Models/CreateOperationRequest.cs +++ b/src/clients/Dim.Clients/Api/Div/Models/CreateOperationRequest.cs @@ -18,7 +18,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Dim; using System.Text.Json.Serialization; namespace Dim.Clients.Api.Div.Models; diff --git a/src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs b/src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs new file mode 100644 index 0000000..98a3b1f --- /dev/null +++ b/src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Clients.Api.Div.Models; + +public record ServiceKeyOperationCreationRequest( + [property: JsonPropertyName("entity")] string Entity, + [property: JsonPropertyName("action")] string Action, + [property: JsonPropertyName("payload")] ServiceKeyPayloadData Payload +); + +public record ServiceKeyPayloadData( + [property: JsonPropertyName("customerWalletId")] Guid CustomerWalletId, + [property: JsonPropertyName("divWalletServiceName")] string ServiceKeyName +); diff --git a/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs b/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs index 9d21f77..0f76d30 100644 --- a/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs +++ b/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs @@ -67,7 +67,7 @@ [new ApplicationCompanyService("CredentialService", "https://dis-agent-prod.eu10 ); var client = await basicAuthTokenService .GetBasicAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var result = await client.PostAsJsonAsync("/api/v1.0.0/operations", data, JsonSerializerExtensions.Options, cancellationToken) .CatchingIntoServiceExceptionFor("create-operation", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async response => @@ -99,7 +99,7 @@ public async Task GetOperation(Guid operationId, Cancellation { var client = await basicAuthTokenService .GetBasicAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var result = await client.GetAsync($"/api/v1.0.0/operations/{operationId}", cancellationToken) .CatchingIntoServiceExceptionFor("get-operation", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async response => @@ -129,4 +129,44 @@ public async Task GetOperation(Guid operationId, Cancellation throw new ServiceException(je.Message); } } + + public async Task CreateServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken) + { + var data = new ServiceKeyOperationCreationRequest( + "customer-wallet-key", + "create", + new ServiceKeyPayloadData( + walletId, + technicalUserName + ) + ); + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.PostAsJsonAsync("/api/v1.0.0/operations", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("create-service-key", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }) + .ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + + if (response == null) + { + throw new ServiceException("response should never be null here"); + } + + return response.OperationId; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } } diff --git a/src/database/Dim.DbAccess/Models/ProcessData.cs b/src/database/Dim.DbAccess/Models/ProcessData.cs new file mode 100644 index 0000000..2656d8e --- /dev/null +++ b/src/database/Dim.DbAccess/Models/ProcessData.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Entities.Enums; + +namespace Dim.DbAccess.Models; + +public record ProcessData( + Guid ProcessId, + IEnumerable ProcessStepData +); + +public record ProcessStepData( + ProcessStepTypeId ProcessStepTypeId, + ProcessStepStatusId ProcessStepStatusId +); diff --git a/src/processes/Processes.Library/VerifyProcessData.cs b/src/database/Dim.DbAccess/Models/VerifyProcessData.cs similarity index 97% rename from src/processes/Processes.Library/VerifyProcessData.cs rename to src/database/Dim.DbAccess/Models/VerifyProcessData.cs index 4af7c41..e07b2af 100644 --- a/src/processes/Processes.Library/VerifyProcessData.cs +++ b/src/database/Dim.DbAccess/Models/VerifyProcessData.cs @@ -20,7 +20,7 @@ using Dim.Entities.Entities; -namespace Dim.Processes.Library; +namespace Dim.DbAccess.Models; public record VerifyProcessData( Process? Process, diff --git a/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs b/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs index f9b26df..37bed69 100644 --- a/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -35,4 +36,7 @@ public interface IProcessStepRepository 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); + Task GetWalletProcessForTenant(string bpn, string companyName); + Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName); + Task<(bool ProcessExists, VerifyProcessData ProcessData)> IsValidProcess(Guid processId, ProcessTypeId processTypeId, IEnumerable processSetpTypeIds); } diff --git a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs index dbe7e23..5bce5c4 100644 --- a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs @@ -38,11 +38,12 @@ public interface ITenantRepository Task GetExternalIdForTechnicalUser(Guid technicalUserId); void RemoveTechnicalUser(Guid technicalUserId); Task IsTenantExisting(string companyName, string bpn); - Task GetTenantBpn(Guid tenantId); Task GetOperationId(Guid tenantId); Task<(string? BaseUrl, WalletData WalletData)> GetCompanyRequestData(Guid tenantId); Task<(bool Exists, Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetCompanyAndWalletDataForBpn(string bpn); Task<(Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetStatusListCreationData(Guid tenantId); Task<(string Bpn, string? BaseUrl, WalletData WalletData, string? Did, string? DownloadUrl)> GetCallbackData(Guid tenantId); Task<(string? DownloadUrl, bool IsIssuer)> GetDownloadUrlAndIsIssuer(Guid tenantId); + Task<(Guid? WalletId, string TechnicalUserName)> GetTechnicalUserNameAndWalletId(Guid technicalUserId); + Task GetOperationIdForTechnicalUser(Guid technicalUserId); } diff --git a/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs b/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs index 15c33c0..b7e14f7 100644 --- a/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -88,4 +89,46 @@ public IAsyncEnumerable GetActiveProcesses(IEnumerable p step.Id, step.ProcessStepTypeId)) .AsAsyncEnumerable(); + + public Task GetWalletProcessForTenant(string bpn, string companyName) => + dbContext.Tenants + .Where(x => + x.Bpn == bpn && + x.CompanyName == companyName && + x.Process!.ProcessTypeId == ProcessTypeId.SETUP_DIM) + .Select(x => new ProcessData( + x.ProcessId, + x.Process!.ProcessSteps.Select(ps => new ProcessStepData( + ps.ProcessStepTypeId, + ps.ProcessStepStatusId)))) + .SingleOrDefaultAsync(); + + public Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName) => + dbContext.TechnicalUsers + .Where(x => + x.TechnicalUserName == technicalUserName && + x.Tenant!.Bpn == bpn && + x.Tenant!.CompanyName == companyName && + x.Process!.ProcessTypeId == ProcessTypeId.TECHNICAL_USER) + .Select(x => new ProcessData( + x.ProcessId, + x.Process!.ProcessSteps.Select(ps => new ProcessStepData( + ps.ProcessStepTypeId, + ps.ProcessStepStatusId)))) + .SingleOrDefaultAsync(); + + public Task<(bool ProcessExists, VerifyProcessData ProcessData)> IsValidProcess(Guid processId, ProcessTypeId processTypeId, IEnumerable processStepTypeIds) => + dbContext.Processes + .AsNoTracking() + .Where(x => x.Id == processId && x.ProcessTypeId == processTypeId) + .Select(x => new ValueTuple( + true, + new VerifyProcessData( + x, + x.ProcessSteps + .Where(step => + processStepTypeIds.Contains(step.ProcessStepTypeId) && + step.ProcessStepStatusId == ProcessStepStatusId.TODO)) + )) + .SingleOrDefaultAsync(); } diff --git a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs index 576902b..99dc0ef 100644 --- a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs @@ -106,12 +106,6 @@ public Task IsTenantExisting(string companyName, string bpn) => dbContext.Tenants .AnyAsync(x => x.CompanyName == companyName && x.Bpn == bpn); - public Task GetTenantBpn(Guid tenantId) => - dbContext.Tenants - .Where(x => x.Id == tenantId) - .Select(x => x.Bpn) - .SingleAsync(); - public Task GetOperationId(Guid tenantId) => dbContext.Tenants .Where(x => x.Id == tenantId) @@ -185,4 +179,16 @@ public Task GetTenantBpn(Guid tenantId) => .Where(x => x.Id == tenantId) .Select(x => new ValueTuple(x.DidDownloadUrl, x.IsIssuer)) .SingleOrDefaultAsync(); + + public Task<(Guid? WalletId, string TechnicalUserName)> GetTechnicalUserNameAndWalletId(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple(x.Tenant!.WalletId, x.TechnicalUserName)) + .SingleOrDefaultAsync(); + + public Task GetOperationIdForTechnicalUser(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => x.OperationId) + .SingleOrDefaultAsync(); } diff --git a/src/database/Dim.Entities/Entities/TechnicalUser.cs b/src/database/Dim.Entities/Entities/TechnicalUser.cs index 5b64a6e..6773ad1 100644 --- a/src/database/Dim.Entities/Entities/TechnicalUser.cs +++ b/src/database/Dim.Entities/Entities/TechnicalUser.cs @@ -31,6 +31,7 @@ public class TechnicalUser( public Guid TenantId { get; set; } = tenantId; public Guid ExternalId { get; set; } = externalId; public string TechnicalUserName { get; set; } = technicalUserName; + public Guid? OperationId { get; set; } public string? TokenAddress { get; set; } public string? ClientId { get; set; } public byte[]? ClientSecret { get; set; } diff --git a/src/database/Dim.Entities/Entities/Tenant.cs b/src/database/Dim.Entities/Entities/Tenant.cs index d3f354e..6536806 100644 --- a/src/database/Dim.Entities/Entities/Tenant.cs +++ b/src/database/Dim.Entities/Entities/Tenant.cs @@ -37,6 +37,7 @@ public class Tenant( public Guid OperatorId { get; set; } = operatorId; public Guid ProcessId { get; set; } = processId; public Guid? OperationId { get; set; } + public Guid? WalletId { get; set; } public string? TokenAddress { get; set; } public string? BaseUrl { get; set; } public string? ClientId { get; set; } diff --git a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs index 651aa4a..fdc6ab6 100644 --- a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs @@ -29,13 +29,24 @@ public enum ProcessStepTypeId GET_DID_DOCUMENT = 4, CREATE_STATUS_LIST = 5, SEND_CALLBACK = 6, + RETRIGGER_CREATE_WALLET = 7, + RETRIGGER_CHECK_OPERATION = 8, + RETRIGGER_GET_COMPANY = 9, + RETRIGGER_GET_DID_DOCUMENT = 10, + RETRIGGER_CREATE_STATUS_LIST = 11, + RETRIGGER_SEND_CALLBACK = 12, // Create Technical User CREATE_TECHNICAL_USER = 100, GET_TECHNICAL_USER_DATA = 101, SEND_TECHNICAL_USER_CREATION_CALLBACK = 102, + RETRIGGER_CREATE_TECHNICAL_USER = 103, + RETRIGGER_GET_TECHNICAL_USER_DATA = 104, + RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK = 105, // Delete Technical User DELETE_TECHNICAL_USER = 200, - SEND_TECHNICAL_USER_DELETION_CALLBACK = 201 + SEND_TECHNICAL_USER_DELETION_CALLBACK = 201, + RETRIGGER_DELETE_TECHNICAL_USER = 202, + RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK = 203 } diff --git a/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs b/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs new file mode 100644 index 0000000..bfe7ecd --- /dev/null +++ b/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs @@ -0,0 +1,40 @@ +using Dim.Entities.Enums; + +namespace Dim.Entities.Extensions; + +public static class ProcessStepTypeIdExtensions +{ + public static ProcessStepTypeId GetRetriggerStep(this ProcessStepTypeId processStepTypeId, ProcessTypeId processTypeId) => + processStepTypeId switch + { + ProcessStepTypeId.CREATE_WALLET when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CREATE_WALLET, + ProcessStepTypeId.CHECK_OPERATION when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CHECK_OPERATION, + ProcessStepTypeId.GET_COMPANY when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_GET_COMPANY, + ProcessStepTypeId.GET_DID_DOCUMENT when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT, + ProcessStepTypeId.CREATE_STATUS_LIST when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST, + ProcessStepTypeId.SEND_CALLBACK when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_SEND_CALLBACK, + ProcessStepTypeId.CREATE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER, + ProcessStepTypeId.GET_TECHNICAL_USER_DATA when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK, + ProcessStepTypeId.DELETE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER, + ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK, + _ => throw new ArgumentOutOfRangeException(nameof(processStepTypeId), processStepTypeId, null) + }; + + public static ProcessStepTypeId GetStepForRetrigger(this ProcessStepTypeId processStepTypeId, ProcessTypeId processTypeId) => + processStepTypeId switch + { + ProcessStepTypeId.RETRIGGER_CREATE_WALLET when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CREATE_WALLET, + ProcessStepTypeId.RETRIGGER_CHECK_OPERATION when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CHECK_OPERATION, + ProcessStepTypeId.RETRIGGER_GET_COMPANY when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.GET_COMPANY, + ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.GET_DID_DOCUMENT, + ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CREATE_STATUS_LIST, + ProcessStepTypeId.RETRIGGER_SEND_CALLBACK when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.SEND_CALLBACK, + ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.CREATE_TECHNICAL_USER, + ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, + ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.DELETE_TECHNICAL_USER, + ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, + _ => throw new ArgumentOutOfRangeException(nameof(processStepTypeId), processStepTypeId, null) + }; +} diff --git a/src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.Designer.cs b/src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.Designer.cs similarity index 88% rename from src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.Designer.cs rename to src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.Designer.cs index 919dafe..529d9a0 100644 --- a/src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.Designer.cs +++ b/src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.Designer.cs @@ -1,4 +1,4 @@ -/******************************************************************************** +/******************************************************************************** * Copyright (c) 2024 BMW Group AG * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. * @@ -32,8 +32,8 @@ namespace Dim.Migrations.Migrations { [DbContext(typeof(DimDbContext))] - [Migration("20240912045058_AddDivProvisioning")] - partial class AddDivProvisioning + [Migration("20240916091209_79-AdjustProvisioning")] + partial class _79AdjustProvisioning { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -216,6 +216,36 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) Label = "SEND_CALLBACK" }, new + { + Id = 7, + Label = "RETRIGGER_CREATE_WALLET" + }, + new + { + Id = 8, + Label = "RETRIGGER_CHECK_OPERATION" + }, + new + { + Id = 9, + Label = "RETRIGGER_GET_COMPANY" + }, + new + { + Id = 10, + Label = "RETRIGGER_GET_DID_DOCUMENT" + }, + new + { + Id = 11, + Label = "RETRIGGER_CREATE_STATUS_LIST" + }, + new + { + Id = 12, + Label = "RETRIGGER_SEND_CALLBACK" + }, + new { Id = 100, Label = "CREATE_TECHNICAL_USER" @@ -231,6 +261,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" }, new + { + Id = 103, + Label = "RETRIGGER_CREATE_TECHNICAL_USER" + }, + new + { + Id = 104, + Label = "RETRIGGER_GET_TECHNICAL_USER_DATA" + }, + new + { + Id = 105, + Label = "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new { Id = 200, Label = "DELETE_TECHNICAL_USER" @@ -239,6 +284,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { Id = 201, Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" + }, + new + { + Id = 202, + Label = "RETRIGGER_DELETE_TECHNICAL_USER" + }, + new + { + Id = 203, + Label = "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" }); }); @@ -299,6 +354,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("bytea") .HasColumnName("initialization_vector"); + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + b.Property("ProcessId") .HasColumnType("uuid") .HasColumnName("process_id"); @@ -402,6 +461,10 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("token_address"); + b.Property("WalletId") + .HasColumnType("uuid") + .HasColumnName("wallet_id"); + b.HasKey("Id") .HasName("pk_tenants"); diff --git a/src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.cs b/src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.cs similarity index 71% rename from src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.cs rename to src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.cs index 24f483e..29a61af 100644 --- a/src/database/Dim.Migrations/Migrations/20240912045058_AddDivProvisioning.cs +++ b/src/database/Dim.Migrations/Migrations/20240916091209_79-AdjustProvisioning.cs @@ -28,47 +28,11 @@ namespace Dim.Migrations.Migrations { /// - public partial class AddDivProvisioning : Migration + public partial class _79AdjustProvisioning : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 7); - - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 8); - - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 9); - - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 10); - - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 11); - - migrationBuilder.DeleteData( - schema: "dim", - table: "process_step_types", - keyColumn: "id", - keyValue: 12); - migrationBuilder.DeleteData( schema: "dim", table: "process_step_types", @@ -115,13 +79,14 @@ protected override void Up(MigrationBuilder migrationBuilder) schema: "dim", table: "tenants"); - migrationBuilder.DropColumn( - name: "space_id", + migrationBuilder.RenameColumn( + name: "sub_account_id", schema: "dim", - table: "tenants"); + table: "tenants", + newName: "wallet_id"); migrationBuilder.RenameColumn( - name: "sub_account_id", + name: "space_id", schema: "dim", table: "tenants", newName: "operation_id"); @@ -165,6 +130,13 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "bytea", nullable: true); + migrationBuilder.AddColumn( + name: "operation_id", + schema: "dim", + table: "technical_users", + type: "uuid", + nullable: true); + migrationBuilder.UpdateData( schema: "dim", table: "process_step_types", @@ -212,11 +184,102 @@ protected override void Up(MigrationBuilder migrationBuilder) keyValue: 6, column: "label", value: "SEND_CALLBACK"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 7, + column: "label", + value: "RETRIGGER_CREATE_WALLET"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 8, + column: "label", + value: "RETRIGGER_CHECK_OPERATION"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 9, + column: "label", + value: "RETRIGGER_GET_COMPANY"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 10, + column: "label", + value: "RETRIGGER_GET_DID_DOCUMENT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 11, + column: "label", + value: "RETRIGGER_CREATE_STATUS_LIST"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 12, + column: "label", + value: "RETRIGGER_SEND_CALLBACK"); + + migrationBuilder.InsertData( + schema: "dim", + table: "process_step_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 103, "RETRIGGER_CREATE_TECHNICAL_USER" }, + { 104, "RETRIGGER_GET_TECHNICAL_USER_DATA" }, + { 105, "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" }, + { 202, "RETRIGGER_DELETE_TECHNICAL_USER" }, + { 203, "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" } + }); } /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 103); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 104); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 105); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 202); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 203); + migrationBuilder.DropColumn( name: "client_secret", schema: "dim", @@ -232,6 +295,17 @@ protected override void Down(MigrationBuilder migrationBuilder) schema: "dim", table: "tenants"); + migrationBuilder.DropColumn( + name: "operation_id", + schema: "dim", + table: "technical_users"); + + migrationBuilder.RenameColumn( + name: "wallet_id", + schema: "dim", + table: "tenants", + newName: "sub_account_id"); + migrationBuilder.RenameColumn( name: "token_address", schema: "dim", @@ -242,7 +316,7 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "operation_id", schema: "dim", table: "tenants", - newName: "sub_account_id"); + newName: "space_id"); migrationBuilder.RenameColumn( name: "client_id", @@ -270,13 +344,6 @@ protected override void Down(MigrationBuilder migrationBuilder) type: "uuid", nullable: true); - migrationBuilder.AddColumn( - name: "space_id", - schema: "dim", - table: "tenants", - type: "uuid", - nullable: true); - migrationBuilder.UpdateData( schema: "dim", table: "process_step_types", @@ -325,18 +392,60 @@ protected override void Down(MigrationBuilder migrationBuilder) column: "label", value: "SUBSCRIBE_APPLICATION"); + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 7, + column: "label", + value: "CREATE_CLOUD_FOUNDRY_ENVIRONMENT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 8, + column: "label", + value: "CREATE_CLOUD_FOUNDRY_SPACE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 9, + column: "label", + value: "ADD_SPACE_MANAGER_ROLE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 10, + column: "label", + value: "ADD_SPACE_DEVELOPER_ROLE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 11, + column: "label", + value: "CREATE_DIM_SERVICE_INSTANCE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 12, + column: "label", + value: "CREATE_SERVICE_INSTANCE_BINDING"); + migrationBuilder.InsertData( schema: "dim", table: "process_step_types", columns: new[] { "id", "label" }, values: new object[,] { - { 7, "CREATE_CLOUD_FOUNDRY_ENVIRONMENT" }, - { 8, "CREATE_CLOUD_FOUNDRY_SPACE" }, - { 9, "ADD_SPACE_MANAGER_ROLE" }, - { 10, "ADD_SPACE_DEVELOPER_ROLE" }, - { 11, "CREATE_DIM_SERVICE_INSTANCE" }, - { 12, "CREATE_SERVICE_INSTANCE_BINDING" }, { 13, "GET_DIM_DETAILS" }, { 14, "CREATE_APPLICATION" }, { 15, "CREATE_COMPANY_IDENTITY" }, diff --git a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs index f86b696..b0d500a 100644 --- a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs +++ b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -/******************************************************************************** +/******************************************************************************** * Copyright (c) 2024 BMW Group AG * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. * @@ -19,7 +19,6 @@ ********************************************************************************/ // - using Dim.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -209,6 +208,36 @@ protected override void BuildModel(ModelBuilder modelBuilder) Label = "SEND_CALLBACK" }, new + { + Id = 7, + Label = "RETRIGGER_CREATE_WALLET" + }, + new + { + Id = 8, + Label = "RETRIGGER_CHECK_OPERATION" + }, + new + { + Id = 9, + Label = "RETRIGGER_GET_COMPANY" + }, + new + { + Id = 10, + Label = "RETRIGGER_GET_DID_DOCUMENT" + }, + new + { + Id = 11, + Label = "RETRIGGER_CREATE_STATUS_LIST" + }, + new + { + Id = 12, + Label = "RETRIGGER_SEND_CALLBACK" + }, + new { Id = 100, Label = "CREATE_TECHNICAL_USER" @@ -224,6 +253,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" }, new + { + Id = 103, + Label = "RETRIGGER_CREATE_TECHNICAL_USER" + }, + new + { + Id = 104, + Label = "RETRIGGER_GET_TECHNICAL_USER_DATA" + }, + new + { + Id = 105, + Label = "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new { Id = 200, Label = "DELETE_TECHNICAL_USER" @@ -232,6 +276,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 201, Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" + }, + new + { + Id = 202, + Label = "RETRIGGER_DELETE_TECHNICAL_USER" + }, + new + { + Id = 203, + Label = "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" }); }); @@ -292,6 +346,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bytea") .HasColumnName("initialization_vector"); + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + b.Property("ProcessId") .HasColumnType("uuid") .HasColumnName("process_id"); @@ -395,6 +453,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("token_address"); + b.Property("WalletId") + .HasColumnType("uuid") + .HasColumnName("wallet_id"); + b.HasKey("Id") .HasName("pk_tenants"); diff --git a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs index 6875779..792e3ef 100644 --- a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs @@ -21,6 +21,7 @@ using Dim.DbAccess; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; using DimProcess.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Processes.Worker.Library; @@ -50,7 +51,7 @@ public class DimProcessTypeExecutor( public async ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds) { - var (exists, tenantId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForProcessId(processId).ConfigureAwait(false); + var (exists, tenantId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForProcessId(processId).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw new NotFoundException($"process {processId} does not exist or is not associated with an tenant"); @@ -78,35 +79,35 @@ public class DimProcessTypeExecutor( (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { ProcessStepTypeId.CREATE_WALLET => await dimProcessHandler.CreateWallet(_tenantId, _tenantName, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.CHECK_OPERATION => await dimProcessHandler.CheckOperation(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.GET_COMPANY => await dimProcessHandler.GetCompany(_tenantId, _tenantName, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.GET_DID_DOCUMENT => await dimProcessHandler.GetDidDocument(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.CREATE_STATUS_LIST => await dimProcessHandler.CreateStatusList(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_CALLBACK => await dimProcessHandler.SendCallback(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), _ => (null, ProcessStepStatusId.TODO, false, null) }; } catch (Exception ex) when (ex is not SystemException) { - (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex); + (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex, processStepTypeId); modified = true; } return new IProcessTypeExecutor.StepExecutionResult(modified, stepStatusId, nextStepTypeIds, null, processMessage); } - private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex) + private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex, ProcessStepTypeId processStepTypeId) { return ex switch { ServiceException { IsRecoverable: true } => (ProcessStepStatusId.TODO, ex.Message, null), - _ => (ProcessStepStatusId.FAILED, ex.Message, null) + _ => (ProcessStepStatusId.FAILED, ex.Message, Enumerable.Repeat(processStepTypeId.GetRetriggerStep(ProcessTypeId.SETUP_DIM), 1)) }; } } diff --git a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs index 2903069..59faa97 100644 --- a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs @@ -21,6 +21,7 @@ using Dim.DbAccess; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; using DimProcess.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Processes.Worker.Library; @@ -50,7 +51,7 @@ public class TechnicalUserProcessTypeExecutor( public async ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds) { - var (exists, technicalUserId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForTechnicalUserProcessId(processId).ConfigureAwait(false); + var (exists, technicalUserId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForTechnicalUserProcessId(processId).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw new NotFoundException($"process {processId} does not exist or is not associated with an technical user"); @@ -78,33 +79,33 @@ public class TechnicalUserProcessTypeExecutor( (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { ProcessStepTypeId.CREATE_TECHNICAL_USER => await technicalUserProcessHandler.CreateServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserProcessHandler.GetTechnicalUserData(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK => await technicalUserProcessHandler.SendCreateCallback(_technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.DELETE_TECHNICAL_USER => await technicalUserProcessHandler.DeleteServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK => await technicalUserProcessHandler.SendDeleteCallback(_technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), _ => (null, ProcessStepStatusId.TODO, false, null) }; } catch (Exception ex) when (ex is not SystemException) { - (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex); + (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex, processStepTypeId); modified = true; } return new IProcessTypeExecutor.StepExecutionResult(modified, stepStatusId, nextStepTypeIds, null, processMessage); } - private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex) + private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex, ProcessStepTypeId processStepTypeId) { return ex switch { ServiceException { IsRecoverable: true } => (ProcessStepStatusId.TODO, ex.Message, null), - _ => (ProcessStepStatusId.FAILED, ex.Message, null) + _ => (ProcessStepStatusId.FAILED, ex.Message, Enumerable.Repeat(processStepTypeId.GetRetriggerStep(ProcessTypeId.TECHNICAL_USER), 1)) }; } } diff --git a/src/processes/DimProcess.Library/Callback/CallbackService.cs b/src/processes/DimProcess.Library/Callback/CallbackService.cs index 304ae99..70cc776 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -37,7 +37,7 @@ public class CallbackService(ITokenService tokenService, IOptions(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var data = new CallbackDataModel( did, didDocument, @@ -51,7 +51,7 @@ await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken) { var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var data = new AuthenticationDetail( tokenAddress, clientId, @@ -64,7 +64,7 @@ await httpClient.PostAsJsonAsync($"/api/administration/serviceAccount/callback/{ public async Task SendTechnicalUserDeletionCallback(Guid externalId, CancellationToken cancellationToken) { var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); await httpClient.PostAsync($"/api/administration/serviceAccount/callback/{externalId}/delete", null, cancellationToken) .CatchingIntoServiceExceptionFor("send-technical-user-deletion-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) .ConfigureAwait(false); diff --git a/src/processes/DimProcess.Library/DimProcessHandler.cs b/src/processes/DimProcess.Library/DimProcessHandler.cs index 855feb7..1c21721 100644 --- a/src/processes/DimProcess.Library/DimProcessHandler.cs +++ b/src/processes/DimProcess.Library/DimProcessHandler.cs @@ -101,9 +101,11 @@ public class DimProcessHandler( t.BaseUrl = null; t.ClientId = null; t.ClientSecret = null; + t.WalletId = null; }, t => { + t.WalletId = response.Data.CustomerWalletId; t.TokenAddress = serviceKey.Uaa.Url; t.BaseUrl = serviceKey.Url; t.ClientId = serviceKey.Uaa.ClientId; diff --git a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs index 5f1ff45..5ec7237 100644 --- a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs +++ b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; using Dim.DbAccess; using Dim.DbAccess.Extensions; using Dim.DbAccess.Repositories; @@ -25,44 +27,101 @@ using DimProcess.Library.Callback; using DimProcess.Library.DependencyInjection; using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; namespace DimProcess.Library; public class TechnicalUserProcessHandler( IDimRepositories dimRepositories, + IProvisioningClient provisioningClient, ICallbackService callbackService, IOptions options) : ITechnicalUserProcessHandler { private readonly TechnicalUserSettings _settings = options.Value; - public Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) { - return Task.FromResult(new ValueTuple?, ProcessStepStatusId, bool, string?>( - null, - ProcessStepStatusId.FAILED, + var tenantRepository = dimRepositories.GetInstance(); + var (walletId, technicalUserName) = await tenantRepository.GetTechnicalUserNameAndWalletId(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); + if (walletId == null) + { + throw new ConflictException("WalletId must not be null"); + } + + var operationId = await provisioningClient.CreateServiceKey(technicalUserName, walletId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + tenantRepository.AttachAndModifyTechnicalUser(technicalUserId, t => t.OperationId = null, t => t.OperationId = operationId); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.GET_TECHNICAL_USER_DATA, 1), + ProcessStepStatusId.DONE, false, - "Technical User Creation is currently not supported")); + null); } - public Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) { - return Task.FromResult(new ValueTuple?, ProcessStepStatusId, bool, string?>( - null, - ProcessStepStatusId.FAILED, + var tenantRepository = dimRepositories.GetInstance(); + var operationId = await tenantRepository.GetOperationIdForTechnicalUser(technicalUserId) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (operationId is null) + { + throw new ConflictException("OperationId must not be null"); + } + + var response = await provisioningClient.GetOperation(operationId.Value, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response.Status == OperationResponseStatus.pending) + { + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.TODO, + true, + null); + } + + if (response is { Status: OperationResponseStatus.completed, Data: null }) + { + throw new UnexpectedConditionException($"Data should never be null when in status {OperationResponseStatus.completed}"); + } + + var serviceKey = response.Data!.ServiceKey; + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + var (secret, initializationVector) = cryptoHelper.Encrypt(serviceKey.Uaa.ClientSecret); + + tenantRepository.AttachAndModifyTechnicalUser(technicalUserId, + t => + { + t.TokenAddress = null; + t.ClientId = null; + t.ClientSecret = null; + }, + t => + { + t.TokenAddress = serviceKey.Uaa.Url; + t.ClientId = serviceKey.Uaa.ClientId; + t.ClientSecret = secret; + t.InitializationVector = initializationVector; + t.EncryptionMode = _settings.EncryptionConfigIndex; + }); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, 1), + ProcessStepStatusId.DONE, false, - "Technical User Creation is currently not supported")); + null); } public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCreateCallback(Guid technicalUserId, CancellationToken cancellationToken) { - var (externalId, walletData) = await dimRepositories.GetInstance().GetTechnicalUserCallbackData(technicalUserId).ConfigureAwait(false); + var (externalId, walletData) = await dimRepositories.GetInstance().GetTechnicalUserCallbackData(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); - await callbackService.SendTechnicalUserCallback(externalId, tokenAddress, clientId, secret, cancellationToken).ConfigureAwait(false); + await callbackService.SendTechnicalUserCallback(externalId, tokenAddress, clientId, secret, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, @@ -84,9 +143,9 @@ public class TechnicalUserProcessHandler( { var tenantRepository = dimRepositories.GetInstance(); - var externalId = await tenantRepository.GetExternalIdForTechnicalUser(technicalUserId).ConfigureAwait(false); + var externalId = await tenantRepository.GetExternalIdForTechnicalUser(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); tenantRepository.RemoveTechnicalUser(technicalUserId); - await callbackService.SendTechnicalUserDeletionCallback(externalId, cancellationToken).ConfigureAwait(false); + await callbackService.SendTechnicalUserDeletionCallback(externalId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, diff --git a/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs index 9820578..f6f8522 100644 --- a/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs +++ b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs @@ -19,6 +19,7 @@ ********************************************************************************/ using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionService.cs b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs index 9b5de35..6f0e449 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutionService.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs @@ -109,7 +109,7 @@ bool UpdateVersion() return true; } - await foreach (var executionResult in processExecutor.ExecuteProcess(process.Id, process.ProcessTypeId, stoppingToken).WithCancellation(stoppingToken).ConfigureAwait(false)) + await foreach (var executionResult in processExecutor.ExecuteProcess(process.Id, process.ProcessTypeId, stoppingToken).ConfigureAwait(false)) { if (executionResult switch { @@ -118,14 +118,14 @@ bool UpdateVersion() _ => false }) { - await executorRepositories.SaveAsync().ConfigureAwait(false); + await executorRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } executorRepositories.Clear(); } if (process.ReleaseLock()) { - await executorRepositories.SaveAsync().ConfigureAwait(false); + await executorRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); executorRepositories.Clear(); } _logger.LogInformation("finished processing process {processId}", process.Id); diff --git a/src/processes/Processes.Worker.Library/ProcessExecutor.cs b/src/processes/Processes.Worker.Library/ProcessExecutor.cs index 367a392..2c31944 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutor.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutor.cs @@ -78,6 +78,7 @@ public ProcessExecutor(IEnumerable executors, IDimReposito { yield return IProcessExecutor.ProcessExecutionResult.LockRequested; } + ProcessStepStatusId resultStepStatusId; IEnumerable? scheduleStepTypeIds; IEnumerable? skipStepTypeIds; diff --git a/src/processes/Processes.Worker/Program.cs b/src/processes/Processes.Worker/Program.cs index 0820d06..8420912 100644 --- a/src/processes/Processes.Worker/Program.cs +++ b/src/processes/Processes.Worker/Program.cs @@ -56,7 +56,7 @@ Log.Information("Start processing"); var workerInstance = host.Services.GetRequiredService(); - await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(false); + await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(ConfigureAwaitOptions.None); Log.Information("Execution finished shutting down"); } catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) diff --git a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs index e7d3676..175fa2c 100644 --- a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs @@ -22,8 +22,11 @@ using Dim.Clients.Token; using Dim.DbAccess; using Dim.DbAccess.Extensions; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; +using Dim.Processes.Library; using Dim.Web.ErrorHandling; using Dim.Web.Models; using Microsoft.Extensions.Options; @@ -40,6 +43,7 @@ public class DimBusinessLogic( : IDimBusinessLogic { private static readonly Regex TenantName = new(@"(?<=[^\w-])|(?<=[^-])[\W_]+|(?<=[^-])$", RegexOptions.Compiled, new TimeSpan(0, 0, 0, 1)); + private static readonly Regex TechnicalUserName = new("[^a-zA-Z0-9]+", RegexOptions.Compiled, new TimeSpan(0, 0, 0, 1)); private readonly DimSettings _settings = options.Value; public async Task StartSetupDim(string companyName, string bpn, string didDocumentLocation, bool isIssuer) @@ -56,12 +60,12 @@ public async Task StartSetupDim(string companyName, string bpn, string didDocume dimRepositories.GetInstance().CreateTenant(tenant, bpn, didDocumentLocation, isIssuer, processId, _settings.OperatorId); - await dimRepositories.SaveAsync().ConfigureAwait(false); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } public async Task GetStatusList(string bpn, CancellationToken cancellationToken) { - var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(false); + var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); if (!exists) { @@ -87,12 +91,12 @@ public async Task GetStatusList(string bpn, CancellationToken cancellati ClientId = clientId, ClientSecret = secret }; - return await dimClient.GetStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + return await dimClient.GetStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } public async Task CreateStatusList(string bpn, CancellationToken cancellationToken) { - var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(false); + var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); if (!exists) { @@ -118,12 +122,12 @@ public async Task CreateStatusList(string bpn, CancellationToken cancell ClientId = clientId, ClientSecret = secret }; - return await dimClient.CreateStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + return await dimClient.CreateStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData) { - var (exists, tenantId) = await dimRepositories.GetInstance().GetTenantForBpn(bpn).ConfigureAwait(false); + var (exists, tenantId) = await dimRepositories.GetInstance().GetTenantForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { @@ -134,14 +138,16 @@ public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUse var processId = processStepRepository.CreateProcess(ProcessTypeId.TECHNICAL_USER).Id; processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessStepStatusId.TODO, processId); - dimRepositories.GetInstance().CreateTenantTechnicalUser(tenantId, technicalUserData.Name, technicalUserData.ExternalId, processId); + var technicalUserName = TechnicalUserName.Replace(technicalUserData.Name, string.Empty).ToLower(); + dimRepositories.GetInstance().CreateTenantTechnicalUser(tenantId, technicalUserName, technicalUserData.ExternalId, processId); - await dimRepositories.SaveAsync().ConfigureAwait(false); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } public async Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData) { - var (exists, technicalUserId, processId) = await dimRepositories.GetInstance().GetTechnicalUserForBpn(bpn, technicalUserData.Name).ConfigureAwait(false); + var technicalUserName = TechnicalUserName.Replace(technicalUserData.Name, string.Empty).ToLower(); + var (exists, technicalUserId, processId) = await dimRepositories.GetInstance().GetTechnicalUserForBpn(bpn, technicalUserName).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw NotFoundException.Create(DimErrors.NO_TECHNICAL_USER_FOUND, new ErrorParameter[] { new("bpn", bpn) }); @@ -162,6 +168,47 @@ public async Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUse t.ProcessId = processId; }); - await dimRepositories.SaveAsync().ConfigureAwait(false); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + } + + public async Task GetSetupProcess(string bpn, string companyName) + { + var processData = await dimRepositories.GetInstance().GetWalletProcessForTenant(bpn, companyName) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (processData == null) + { + throw new NotFoundException($"No process data found for BPN {bpn} and company name {companyName}"); + } + + return processData; + } + + public async Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName) + { + var processData = await dimRepositories.GetInstance().GetTechnicalUserProcess(bpn, companyName, technicalUserName) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (processData == null) + { + throw new NotFoundException($"No process data found for technical user {technicalUserName}"); + } + + return processData; + } + + public async Task RetriggerProcess(ProcessTypeId processTypeId, Guid processId, ProcessStepTypeId processStepTypeId) + { + var stepToTrigger = processStepTypeId.GetStepForRetrigger(processTypeId); + + var (validProcessId, processData) = await dimRepositories.GetInstance().IsValidProcess(processId, processTypeId, Enumerable.Repeat(processStepTypeId, 1)).ConfigureAwait(ConfigureAwaitOptions.None); + if (!validProcessId) + { + throw new NotFoundException($"process {processId} does not exist"); + } + + var context = processData.CreateManualProcessData(stepToTrigger, dimRepositories, () => $"processId {processId}"); + + context.ScheduleProcessSteps(Enumerable.Repeat(stepToTrigger, 1)); + context.FinalizeProcessStep(); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } } diff --git a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs index 603bb0e..ee75f04 100644 --- a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; +using Dim.Entities.Enums; using Dim.Web.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.DependencyInjection; @@ -30,4 +32,7 @@ public interface IDimBusinessLogic : ITransient Task CreateStatusList(string bpn, CancellationToken cancellationToken); Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData); Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData); + Task GetSetupProcess(string bpn, string companyName); + Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName); + Task RetriggerProcess(ProcessTypeId processTypeId, Guid processId, ProcessStepTypeId processStepTypeId); } diff --git a/src/web/Dim.Web/Controllers/DimController.cs b/src/web/Dim.Web/Controllers/DimController.cs index 486ecdb..642a32a 100644 --- a/src/web/Dim.Web/Controllers/DimController.cs +++ b/src/web/Dim.Web/Controllers/DimController.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Entities.Enums; using Dim.Web.BusinessLogic; using Dim.Web.Extensions; using Dim.Web.Models; @@ -80,6 +81,59 @@ public static RouteGroupBuilder MapDimApi(this RouteGroupBuilder group) .RequireAuthorization(r => r.RequireRole("delete_technical_user")) .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + dim.MapGet("process/setup", ( + [FromQuery] string bpn, + [FromQuery] string companyName, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.GetSetupProcess(bpn, companyName) + ) + .WithSwaggerDescription("Gets the wallet creation process id for the given bpn and companyName", + "Example: Post: api/dim/process/setup?bpn={bpn}&companyName={companyName}", + "bpn of the company", + "name of the company") + .RequireAuthorization(r => r.RequireRole("get_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/technical-user", ( + [FromQuery] string bpn, + [FromQuery] string companyName, + [FromQuery] string technicalUserName, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.GetTechnicalUserProcess(bpn, companyName, technicalUserName) + ) + .WithSwaggerDescription("Gets the technical user creation process id for the given technicalUserName", + "Example: Post: api/dim/process/technical-user?bpn={bpn}&companyName={companyName}&technicalUserName={technicalUserName}", + "bpn of the company", + "name of the company", + "name of the techincal user to get the process for") + .RequireAuthorization(r => r.RequireRole("get_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/wallet/{processId}/retrigger", ( + [FromRoute] Guid processId, + [FromQuery] ProcessStepTypeId processStepTypeId, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.RetriggerProcess(ProcessTypeId.SETUP_DIM, processId, processStepTypeId) + ) + .WithSwaggerDescription("Retriggers the given process step of the wallet creation process", + "Example: Post: api/dim/process/wallet/{processId}/retrigger?processStepTypeId={processStepTypeId}", + "Id of the process", + "The process step that should be retriggered") + .RequireAuthorization(r => r.RequireRole("retrigger_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/technicalUser/{processId}/retrigger", ( + [FromRoute] Guid processId, + [FromQuery] ProcessStepTypeId processStepTypeId, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.RetriggerProcess(ProcessTypeId.TECHNICAL_USER, processId, processStepTypeId) + ) + .WithSwaggerDescription("Retriggers the given process step of a technical user process", + "Example: Post: api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId={processStepTypeId}", + "Id of the process", + "The process step that should be retriggered") + .RequireAuthorization(r => r.RequireRole("retrigger_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); return group; } } diff --git a/src/web/Dim.Web/Dim.Web.csproj b/src/web/Dim.Web/Dim.Web.csproj index 886a45f..4a29c3d 100644 --- a/src/web/Dim.Web/Dim.Web.csproj +++ b/src/web/Dim.Web/Dim.Web.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs b/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs index 162d096..76efa4b 100644 --- a/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs +++ b/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs @@ -112,7 +112,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti .Returns(new ValueTuple(false, Guid.Empty, string.Empty, string.Empty)); // Act - async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -127,7 +127,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti public async Task ExecuteProcessStep_WithoutRegistrationId_ThrowsUnexpectedConditionException() { // Act - async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.SEND_CALLBACK, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.SEND_CALLBACK, Enumerable.Empty(), CancellationToken.None); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -225,7 +225,8 @@ public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetrig // Assert result.Modified.Should().BeTrue(); - result.ScheduleStepTypeIds.Should().BeNull(); + result.ScheduleStepTypeIds.Should().ContainSingle() + .And.Satisfy(x => x == ProcessStepTypeId.RETRIGGER_CREATE_WALLET); result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.FAILED); result.ProcessMessage.Should().Be("this is a test"); result.SkipStepTypeIds.Should().BeNull(); diff --git a/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs b/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs index 6737a74..9682b18 100644 --- a/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs +++ b/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs @@ -112,7 +112,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti .Returns(new ValueTuple(false, Guid.Empty, string.Empty, string.Empty)); // Act - async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -127,7 +127,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti public async Task ExecuteProcessStep_WithoutInitialization_ThrowsUnexpectedConditionException() { // Act - async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, Enumerable.Empty(), CancellationToken.None); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -225,7 +225,8 @@ public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetrig // Assert result.Modified.Should().BeTrue(); - result.ScheduleStepTypeIds.Should().BeNull(); + result.ScheduleStepTypeIds.Should().ContainSingle() + .And.Satisfy(x => x == ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER); result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.FAILED); result.ProcessMessage.Should().Be("this is a test"); result.SkipStepTypeIds.Should().BeNull(); diff --git a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs index dc9bd8c..7f8e240 100644 --- a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs @@ -105,7 +105,7 @@ public async Task CreateOperation_WithDidLocationNull_ThrowsUnexpectedConditionE { // Arrange A.CallTo(() => _tenantRepositories.GetHostingUrlAndIsIssuer(_tenantId)) - .Returns((true, (string?)null)); + .Returns((true, null)); Task Act() => _sut.CreateWallet(_tenantId, TenantName, CancellationToken.None); // Act @@ -203,8 +203,9 @@ public async Task CheckOperation_WithValid_UpdatesTenant() { // Arrange var operationId = Guid.NewGuid(); + var customerWalletId = Guid.NewGuid(); var responseData = new OperationResponseData( - Guid.NewGuid(), + customerWalletId, Guid.NewGuid().ToString(), "test name", new ServiceKey( @@ -231,6 +232,7 @@ public async Task CheckOperation_WithValid_UpdatesTenant() result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.GET_COMPANY); + tenant.WalletId.Should().Be(customerWalletId); tenant.BaseUrl.Should().Be(responseData.ServiceKey.Url); tenant.TokenAddress.Should().Be(responseData.ServiceKey.Uaa.Url); tenant.ClientId.Should().Be(responseData.ServiceKey.Uaa.ClientId); @@ -245,7 +247,7 @@ public async Task GetCompany_WithDidLocationNull_ThrowsUnexpectedConditionExcept { // Arrange A.CallTo(() => _tenantRepositories.GetCompanyRequestData(_tenantId)) - .Returns(((string?)null, GetWalletData())); + .Returns((null, GetWalletData())); Task Act() => _sut.GetCompany(_tenantId, TenantName, CancellationToken.None); // Act @@ -295,7 +297,7 @@ public async Task GetDidDocument_WithDownloadUrlNull_ThrowsUnexpectedConditionEx { // Arrange A.CallTo(() => _tenantRepositories.GetDownloadUrlAndIsIssuer(_tenantId)) - .Returns(((string?)null, false)); + .Returns((null, false)); Task Act() => _sut.GetDidDocument(_tenantId, CancellationToken.None); // Act @@ -314,7 +316,7 @@ public async Task GetDidDocument_WithValidData_ReturnsExpected(bool isIssuer) var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); const string DownloadUrl = "https://example.org/download"; A.CallTo(() => _tenantRepositories.GetDownloadUrlAndIsIssuer(_tenantId)) - .Returns((DownloadUrl, IsIssuer: isIssuer)); + .Returns((DownloadUrl, isIssuer)); A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => { @@ -342,7 +344,7 @@ public async Task CreateStatusList_WithCompanyIdNull_ThrowsUnexpectedConditionEx { // Arrange A.CallTo(() => _tenantRepositories.GetStatusListCreationData(_tenantId)) - .Returns(((Guid?)null, (string?)null, GetWalletData())); + .Returns((null, null, GetWalletData())); Task Act() => _sut.CreateStatusList(_tenantId, CancellationToken.None); // Act @@ -400,7 +402,7 @@ public async Task SendCallback_WithoutBaseUrl_ReturnsExpected() // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) .Returns(("bpn123", null, _fixture.Create(), null, null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act var ex = await Assert.ThrowsAsync(Act); @@ -415,7 +417,7 @@ public async Task SendCallback_WithoutDid_ReturnsExpected() // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) .Returns(("bpn123", "https://example.org/base", _fixture.Create(), null, null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act var ex = await Assert.ThrowsAsync(Act); @@ -430,7 +432,7 @@ public async Task SendCallback_WithoutDownloadUrl_ReturnsExpected() // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) .Returns(("bpn123", "https://example.org/base", _fixture.Create(), "did:web:example:org:base", null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act var ex = await Assert.ThrowsAsync(Act); diff --git a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs index 52f4130..929775f 100644 --- a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; using Dim.DbAccess; using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; @@ -38,6 +40,7 @@ public class TechnicalUserProcessHandlerTests private readonly ICallbackService _callbackService; private readonly TechnicalUserProcessHandler _sut; private readonly TechnicalUserSettings _settings; + private readonly IProvisioningClient _provisioningClient; public TechnicalUserProcessHandlerTests() { @@ -51,6 +54,7 @@ public TechnicalUserProcessHandlerTests() A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepositories); + _provisioningClient = A.Fake(); _callbackService = A.Fake(); _settings = new TechnicalUserSettings { @@ -68,25 +72,51 @@ public TechnicalUserProcessHandlerTests() }; var options = Options.Create(_settings); - _sut = new TechnicalUserProcessHandler(repositories, _callbackService, options); + _sut = new TechnicalUserProcessHandler(repositories, _provisioningClient, _callbackService, options); } #region CreateServiceInstanceBindings [Fact] - public async Task CreateServiceInstanceBindings_WithValidData_ReturnsExpected() + public async Task CreateServiceInstanceBindings_WithValid_SavesOperationId() { // Arrange - var technicalUserId = Guid.NewGuid(); + var operationId = Guid.NewGuid(); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "saTest", Guid.NewGuid()); + A.CallTo(() => _tenantRepositories.GetTechnicalUserNameAndWalletId(technicalUser.Id)) + .Returns((Guid.NewGuid(), technicalUser.TechnicalUserName)); + A.CallTo(() => _tenantRepositories.AttachAndModifyTechnicalUser(technicalUser.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize!.Invoke(technicalUser); + modify(technicalUser); + }); + A.CallTo(() => _provisioningClient.CreateServiceKey(technicalUser.TechnicalUserName, A._, A._)) + .Returns(operationId); // Act - var result = await _sut.CreateServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + var result = await _sut.CreateServiceInstanceBindings("test", technicalUser.Id, CancellationToken.None); // Assert + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.processMessage.Should().BeNull(); result.modified.Should().BeFalse(); - result.processMessage.Should().Be("Technical User Creation is currently not supported"); - result.stepStatusId.Should().Be(ProcessStepStatusId.FAILED); - result.nextStepTypeIds.Should().BeNull(); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.GET_TECHNICAL_USER_DATA); + technicalUser.OperationId.Should().Be(operationId); + } + + [Fact] + public async Task CreateServiceInstanceBindings_WithWalletIdNotSet_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + Task Act() => _sut.CreateServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("WalletId must not be null"); } #endregion @@ -94,20 +124,97 @@ public async Task CreateServiceInstanceBindings_WithValidData_ReturnsExpected() #region GetTechnicalUserData [Fact] - public async Task GetTechnicalUserData_WithValidData_ReturnsExpected() + public async Task GetTechnicalUserData_WithValid_SavesData() + { + // Arrange + var operationId = Guid.NewGuid(); + var responseData = new OperationResponseData( + Guid.NewGuid(), + Guid.NewGuid().ToString(), + "test name", + new ServiceKey( + new ServiceUaa("https://example.org/api", "https://example.org", "cl1", "test123"), + "https://example.org/test", + "test")); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "saTest", Guid.NewGuid()); + A.CallTo(() => _tenantRepositories.GetOperationIdForTechnicalUser(technicalUser.Id)) + .Returns(operationId); + A.CallTo(() => _tenantRepositories.AttachAndModifyTechnicalUser(technicalUser.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize!.Invoke(technicalUser); + modify(technicalUser); + }); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, responseData)); + + // Act + var result = await _sut.GetTechnicalUserData("test", technicalUser.Id, CancellationToken.None); + + // Assert + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.processMessage.Should().BeNull(); + result.modified.Should().BeFalse(); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK); + + technicalUser.TokenAddress.Should().Be(responseData.ServiceKey.Uaa.Url); + technicalUser.ClientId.Should().Be(responseData.ServiceKey.Uaa.ClientId); + } + + [Fact] + public async Task GetTechnicalUserData_WithPending_StaysInTodo() { // Arrange + var operationId = Guid.NewGuid(); var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetOperationIdForTechnicalUser(technicalUserId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.pending, null, null, null)); + // Act var result = await _sut.GetTechnicalUserData("test", technicalUserId, CancellationToken.None); // Assert - result.modified.Should().BeFalse(); - result.processMessage.Should().Be("Technical User Creation is currently not supported"); - result.stepStatusId.Should().Be(ProcessStepStatusId.FAILED); + result.stepStatusId.Should().Be(ProcessStepStatusId.TODO); + result.processMessage.Should().BeNull(); + result.modified.Should().BeTrue(); result.nextStepTypeIds.Should().BeNull(); } + [Fact] + public async Task GetTechnicalUserData_WithCompletedAndNoData_ThrowsUnexpectedConditionException() + { + // Arrange + var operationId = Guid.NewGuid(); + var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetOperationIdForTechnicalUser(technicalUserId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, null)); + Task Act() => _sut.GetTechnicalUserData("test", technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("Data should never be null when in status completed"); + } + + [Fact] + public async Task GetTechnicalUserData_WithOperationIdNotSet_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + Task Act() => _sut.GetTechnicalUserData("test", technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("OperationId must not be null"); + } + #endregion #region SendCreateCallback diff --git a/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs b/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs index 45d5ac8..f0c213e 100644 --- a/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs +++ b/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs @@ -19,6 +19,7 @@ ********************************************************************************/ using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; diff --git a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs index 48ebdd9..81c0f94 100644 --- a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs +++ b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs @@ -81,7 +81,7 @@ public void GetRegisteredProcessTypeIds_ReturnsExpected() public async Task ExecuteProcess_WithInvalidProcessTypeId_Throws() { // Arrange - var Act = async () => await _sut.ExecuteProcess(Guid.NewGuid(), (ProcessTypeId)default, CancellationToken.None).ToListAsync().ConfigureAwait(false); + var Act = async () => await _sut.ExecuteProcess(Guid.NewGuid(), default, CancellationToken.None).ToListAsync(); // Act var result = await Assert.ThrowsAsync(Act); @@ -841,7 +841,7 @@ public async Task ExecuteProcess_ProcessThrowsSystemException_Throws(bool isLock var Act = async () => { - await foreach (var stepResult in _sut.ExecuteProcess(processId, ProcessTypeId.SETUP_DIM, CancellationToken.None).ConfigureAwait(false)) + await foreach (var stepResult in _sut.ExecuteProcess(processId, ProcessTypeId.SETUP_DIM, CancellationToken.None)) { stepResults.Add(stepResult); }