From 273c32d266c6309ab1b9e6e364797f9a9f0f52c8 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Wed, 19 Jun 2024 17:41:55 +0200 Subject: [PATCH] feat(technicalUser): add delete technical user --- src/clients/Dim.Clients/Api/Cf/CfClient.cs | 7 + src/clients/Dim.Clients/Api/Cf/ICfClient.cs | 1 + .../Repositories/ITenantRepository.cs | 3 + .../Repositories/TenantRepository.cs | 16 + .../Dim.Entities/Enums/ProcessStepTypeId.cs | 6 +- .../Dim.Entities/Enums/ProcessTypeId.cs | 3 +- ...9122220_AddDeleteTechnicalUser.Designer.cs | 560 ++++++++++++++++++ .../20240619122220_AddDeleteTechnicalUser.cs | 70 +++ .../Migrations/DimDbContextModelSnapshot.cs | 44 +- .../DimProcessCollectionExtensions.cs | 3 +- ...chnicalUserCreationProcessTypeExecutor.cs} | 12 +- ...echnicalUserDeletionProcessTypeExecutor.cs | 100 ++++ .../Callback/CallbackService.cs | 7 + .../Callback/ICallbackService.cs | 1 + .../DimHandlerExtensions.cs | 3 +- ...> ITechnicalUserCreationProcessHandler.cs} | 2 +- .../ITechnicalUserDeletionProcessHandler.cs | 28 + ...=> TechnicalUserCreationProcessHandler.cs} | 6 +- .../TechnicalUserDeletionProcessHandler.cs | 66 +++ .../Dim.Web/BusinessLogic/DimBusinessLogic.cs | 21 + .../BusinessLogic/IDimBusinessLogic.cs | 1 + src/web/Dim.Web/Controllers/DimController.cs | 9 +- .../ErrorHandling/DimErrorMessageContainer.cs | 6 +- ...chnicalUserCreationProcessHandlerTests.cs} | 31 +- ...echnicalUserDeletionProcessHandlerTests.cs | 121 ++++ 25 files changed, 1070 insertions(+), 57 deletions(-) create mode 100644 src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.Designer.cs create mode 100644 src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.cs rename src/processes/DimProcess.Executor/{TechnicalUserProcessTypeExecutor.cs => TechnicalUserCreationProcessTypeExecutor.cs} (88%) create mode 100644 src/processes/DimProcess.Executor/TechnicalUserDeletionProcessTypeExecutor.cs rename src/processes/DimProcess.Library/{ITechnicalUserProcessHandler.cs => ITechnicalUserCreationProcessHandler.cs} (96%) create mode 100644 src/processes/DimProcess.Library/ITechnicalUserDeletionProcessHandler.cs rename src/processes/DimProcess.Library/{TechnicalUserProcessHandler.cs => TechnicalUserCreationProcessHandler.cs} (97%) create mode 100644 src/processes/DimProcess.Library/TechnicalUserDeletionProcessHandler.cs rename tests/processes/DimProcess.Library.Tests/{TechnicalUserProcessHandlerTests.cs => TechnicalUserCreationProcessHandlerTests.cs} (72%) create mode 100644 tests/processes/DimProcess.Library.Tests/TechnicalUserDeletionProcessHandlerTests.cs diff --git a/src/clients/Dim.Clients/Api/Cf/CfClient.cs b/src/clients/Dim.Clients/Api/Cf/CfClient.cs index cc579b6..dedf544 100644 --- a/src/clients/Dim.Clients/Api/Cf/CfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/CfClient.cs @@ -276,4 +276,11 @@ public async Task GetServiceBindingDetai throw new ServiceException(je.Message); } } + + public async Task DeleteServiceInstanceBindings(Guid serviceBindingId, CancellationToken cancellationToken) + { + var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); + await client.DeleteAsync($"/v3/service_credential_bindings/{serviceBindingId}", cancellationToken) + .CatchingIntoServiceExceptionFor("delete-si-bindings", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS); + } } diff --git a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs index 0ec25c8..11991f1 100644 --- a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs @@ -29,4 +29,5 @@ public interface ICfClient Task CreateServiceInstanceBindings(string tenantName, string? keyName, Guid spaceId, CancellationToken cancellationToken); Task GetServiceBinding(string tenantName, Guid spaceId, string bindingName, CancellationToken cancellationToken); Task GetServiceBindingDetails(Guid id, CancellationToken cancellationToken); + Task DeleteServiceInstanceBindings(Guid serviceBindingId, CancellationToken cancellationToken); } diff --git a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs index c782e97..63d7ac3 100644 --- a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs @@ -42,4 +42,7 @@ public interface ITenantRepository Task<(Guid? spaceId, string technicalUserName)> GetSpaceIdAndTechnicalUserName(Guid technicalUserId); Task<(Guid ExternalId, string? TokenAddress, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetTechnicalUserCallbackData(Guid technicalUserId); Task<(Guid? DimInstanceId, Guid? CompanyId)> GetDimInstanceIdAndDid(Guid tenantId); + Task<(bool Exists, Guid TechnicalUserId)> GetTechnicalUserForBpn(string bpn, string technicalUserName); + Task GetExternalIdForTechnicalUser(Guid technicalUserId); + void RemoveTechnicalUser(Guid technicalUserId); } diff --git a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs index ce94eb3..6145715 100644 --- a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs @@ -144,4 +144,20 @@ public void AttachAndModifyTechnicalUser(Guid technicalUserId, Action x.Id == tenantId) .Select(x => new ValueTuple(x.DimInstanceId, x.CompanyId)) .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid TechnicalUserId)> GetTechnicalUserForBpn(string bpn, string technicalUserName) => + context.TechnicalUsers + .Where(x => x.TechnicalUserName == technicalUserName && x.Tenant!.Bpn == bpn) + .Select(x => new ValueTuple(true, x.Id)) + .SingleOrDefaultAsync(); + + public Task GetExternalIdForTechnicalUser(Guid technicalUserId) => + context.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => x.ExternalId) + .SingleOrDefaultAsync(); + + public void RemoveTechnicalUser(Guid technicalUserId) => + context.TechnicalUsers + .Remove(new TechnicalUser(technicalUserId, default, default, null!, default)); } diff --git a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs index 2d47426..e88530d 100644 --- a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs @@ -44,5 +44,9 @@ public enum ProcessStepTypeId // Create Technical User CREATE_TECHNICAL_USER = 100, GET_TECHNICAL_USER_DATA = 101, - SEND_TECHNICAL_USER_CALLBACK = 102, + SEND_TECHNICAL_USER_CREATION_CALLBACK = 102, + + // Delete Technical User + DELETE_TECHNICAL_USER = 200, + SEND_TECHNICAL_USER_DELETION_CALLBACK = 201 } diff --git a/src/database/Dim.Entities/Enums/ProcessTypeId.cs b/src/database/Dim.Entities/Enums/ProcessTypeId.cs index c11e32e..9154cac 100644 --- a/src/database/Dim.Entities/Enums/ProcessTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessTypeId.cs @@ -22,5 +22,6 @@ namespace Dim.Entities.Enums; public enum ProcessTypeId { SETUP_DIM = 1, - CREATE_TECHNICAL_USER = 2 + CREATE_TECHNICAL_USER = 2, + DELETE_TECHNICAL_USER = 3 } diff --git a/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.Designer.cs b/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.Designer.cs new file mode 100644 index 0000000..440cc8c --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.Designer.cs @@ -0,0 +1,560 @@ +// +using System; +using Dim.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Dim.Migrations.Migrations +{ + [DbContext(typeof(DimDbContext))] + [Migration("20240619122220_AddDeleteTechnicalUser")] + partial class AddDeleteTechnicalUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dim") + .UseCollation("en_US.utf8") + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Dim.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", "dim"); + }); + + modelBuilder.Entity("Dim.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", "dim"); + }); + + modelBuilder.Entity("Dim.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", "dim"); + + 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("Dim.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", "dim"); + + b.HasData( + new + { + Id = 1, + Label = "CREATE_SUBACCOUNT" + }, + new + { + Id = 2, + Label = "CREATE_SERVICEMANAGER_BINDINGS" + }, + new + { + Id = 3, + Label = "ASSIGN_ENTITLEMENTS" + }, + new + { + Id = 4, + Label = "CREATE_SERVICE_INSTANCE" + }, + new + { + Id = 5, + Label = "CREATE_SERVICE_BINDING" + }, + new + { + Id = 6, + Label = "SUBSCRIBE_APPLICATION" + }, + new + { + Id = 7, + Label = "CREATE_CLOUD_FOUNDRY_ENVIRONMENT" + }, + new + { + Id = 8, + Label = "CREATE_CLOUD_FOUNDRY_SPACE" + }, + new + { + Id = 9, + Label = "ADD_SPACE_MANAGER_ROLE" + }, + new + { + Id = 10, + Label = "ADD_SPACE_DEVELOPER_ROLE" + }, + new + { + Id = 11, + Label = "CREATE_DIM_SERVICE_INSTANCE" + }, + new + { + Id = 12, + Label = "CREATE_SERVICE_INSTANCE_BINDING" + }, + new + { + Id = 13, + Label = "GET_DIM_DETAILS" + }, + new + { + Id = 14, + Label = "CREATE_APPLICATION" + }, + new + { + Id = 15, + Label = "CREATE_COMPANY_IDENTITY" + }, + new + { + Id = 16, + Label = "ASSIGN_COMPANY_APPLICATION" + }, + new + { + Id = 17, + Label = "CREATE_STATUS_LIST" + }, + new + { + Id = 18, + Label = "SEND_CALLBACK" + }, + new + { + Id = 100, + Label = "CREATE_TECHNICAL_USER" + }, + new + { + Id = 101, + Label = "GET_TECHNICAL_USER_DATA" + }, + new + { + Id = 102, + Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new + { + Id = 200, + Label = "DELETE_TECHNICAL_USER" + }, + new + { + Id = 201, + Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" + }); + }); + + modelBuilder.Entity("Dim.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", "dim"); + + b.HasData( + new + { + Id = 1, + Label = "SETUP_DIM" + }, + new + { + Id = 2, + Label = "CREATE_TECHNICAL_USER" + }, + new + { + Id = 3, + Label = "DELETE_TECHNICAL_USER" + }); + }); + + modelBuilder.Entity("Dim.Entities.Entities.TechnicalUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClientId") + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .HasColumnType("bytea") + .HasColumnName("client_secret"); + + b.Property("EncryptionMode") + .HasColumnType("integer") + .HasColumnName("encryption_mode"); + + b.Property("ExternalId") + .HasColumnType("uuid") + .HasColumnName("external_id"); + + b.Property("InitializationVector") + .HasColumnType("bytea") + .HasColumnName("initialization_vector"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("TechnicalUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("technical_user_name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TokenAddress") + .HasColumnType("text") + .HasColumnName("token_address"); + + b.HasKey("Id") + .HasName("pk_technical_users"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_technical_users_process_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_technical_users_tenant_id"); + + b.ToTable("technical_users", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ApplicationId") + .HasColumnType("text") + .HasColumnName("application_id"); + + b.Property("ApplicationKey") + .HasColumnType("text") + .HasColumnName("application_key"); + + b.Property("Bpn") + .IsRequired() + .HasColumnType("text") + .HasColumnName("bpn"); + + b.Property("CompanyId") + .HasColumnType("uuid") + .HasColumnName("company_id"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("Did") + .HasColumnType("text") + .HasColumnName("did"); + + b.Property("DidDocumentLocation") + .IsRequired() + .HasColumnType("text") + .HasColumnName("did_document_location"); + + b.Property("DidDownloadUrl") + .HasColumnType("text") + .HasColumnName("did_download_url"); + + b.Property("DimInstanceId") + .HasColumnType("uuid") + .HasColumnName("dim_instance_id"); + + b.Property("IsIssuer") + .HasColumnType("boolean") + .HasColumnName("is_issuer"); + + b.Property("OperatorId") + .HasColumnType("uuid") + .HasColumnName("operator_id"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("ServiceBindingName") + .HasColumnType("text") + .HasColumnName("service_binding_name"); + + b.Property("ServiceInstanceId") + .HasColumnType("text") + .HasColumnName("service_instance_id"); + + b.Property("SpaceId") + .HasColumnType("uuid") + .HasColumnName("space_id"); + + b.Property("SubAccountId") + .HasColumnType("uuid") + .HasColumnName("sub_account_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_tenants_process_id"); + + b.ToTable("tenants", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Process", b => + { + b.HasOne("Dim.Entities.Entities.ProcessType", "ProcessType") + .WithMany("Processes") + .HasForeignKey("ProcessTypeId") + .IsRequired() + .HasConstraintName("fk_processes_process_types_process_type_id"); + + b.Navigation("ProcessType"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStep", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_process_steps_processes_process_id"); + + b.HasOne("Dim.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("Dim.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("Dim.Entities.Entities.TechnicalUser", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("TechnicalUsers") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_technical_users_processes_process_id"); + + b.HasOne("Dim.Entities.Entities.Tenant", "Tenant") + .WithMany("TechnicalUsers") + .HasForeignKey("TenantId") + .IsRequired() + .HasConstraintName("fk_technical_users_tenants_tenant_id"); + + b.Navigation("Process"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("Tenants") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_tenants_processes_process_id"); + + b.Navigation("Process"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Process", b => + { + b.Navigation("ProcessSteps"); + + b.Navigation("TechnicalUsers"); + + b.Navigation("Tenants"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepStatus", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepType", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessType", b => + { + b.Navigation("Processes"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.Navigation("TechnicalUsers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.cs b/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.cs new file mode 100644 index 0000000..5862b54 --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240619122220_AddDeleteTechnicalUser.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Dim.Migrations.Migrations +{ + /// + public partial class AddDeleteTechnicalUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 102, + column: "label", + value: "SEND_TECHNICAL_USER_CREATION_CALLBACK"); + + migrationBuilder.InsertData( + schema: "dim", + table: "process_step_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 200, "DELETE_TECHNICAL_USER" }, + { 201, "SEND_TECHNICAL_USER_DELETION_CALLBACK" } + }); + + migrationBuilder.InsertData( + schema: "dim", + table: "process_types", + columns: new[] { "id", "label" }, + values: new object[] { 3, "DELETE_TECHNICAL_USER" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 200); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 201); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_types", + keyColumn: "id", + keyValue: 3); + + migrationBuilder.UpdateData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 102, + column: "label", + value: "SEND_TECHNICAL_USER_CALLBACK"); + } + } +} diff --git a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs index b60b83a..997a2da 100644 --- a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs +++ b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs @@ -1,27 +1,12 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * - * 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 Dim.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable namespace Dim.Migrations.Migrations { @@ -280,7 +265,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 102, - Label = "SEND_TECHNICAL_USER_CALLBACK" + Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new + { + Id = 200, + Label = "DELETE_TECHNICAL_USER" + }, + new + { + Id = 201, + Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" }); }); @@ -311,6 +306,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 2, Label = "CREATE_TECHNICAL_USER" + }, + new + { + Id = 3, + Label = "DELETE_TECHNICAL_USER" }); }); diff --git a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs index 7a7cf91..8eab6f4 100644 --- a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs +++ b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs @@ -33,6 +33,7 @@ public static IServiceCollection AddDimProcessExecutor(this IServiceCollection s public static IServiceCollection AddTechnicalUserProcessExecutor(this IServiceCollection services, IConfiguration config) => services - .AddTransient() + .AddTransient() + .AddTransient() .AddTechnicalUserProcessHandler(config); } diff --git a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/TechnicalUserCreationProcessTypeExecutor.cs similarity index 88% rename from src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs rename to src/processes/DimProcess.Executor/TechnicalUserCreationProcessTypeExecutor.cs index 0ceee9a..292db37 100644 --- a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/TechnicalUserCreationProcessTypeExecutor.cs @@ -27,15 +27,15 @@ namespace DimProcess.Executor; -public class TechnicalUserProcessTypeExecutor( +public class TechnicalUserCreationProcessTypeExecutor( IDimRepositories dimRepositories, - ITechnicalUserProcessHandler technicalUserProcessHandler) + ITechnicalUserCreationProcessHandler technicalUserCreationProcessHandler) : IProcessTypeExecutor { private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessStepTypeId.GET_TECHNICAL_USER_DATA, - ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK); + ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK); private Guid _technicalUserId; private string? _tenantName; @@ -74,11 +74,11 @@ public class TechnicalUserProcessTypeExecutor( { (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { - ProcessStepTypeId.CREATE_TECHNICAL_USER => await technicalUserProcessHandler.CreateServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) + ProcessStepTypeId.CREATE_TECHNICAL_USER => await technicalUserCreationProcessHandler.CreateServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserProcessHandler.GetTechnicalUserData(_tenantName, _technicalUserId, cancellationToken) + ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserCreationProcessHandler.GetTechnicalUserData(_tenantName, _technicalUserId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK => await technicalUserProcessHandler.SendCallback(_technicalUserId, cancellationToken) + ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK => await technicalUserCreationProcessHandler.SendCallback(_technicalUserId, cancellationToken) .ConfigureAwait(false), _ => (null, ProcessStepStatusId.TODO, false, null) }; diff --git a/src/processes/DimProcess.Executor/TechnicalUserDeletionProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/TechnicalUserDeletionProcessTypeExecutor.cs new file mode 100644 index 0000000..c871ebe --- /dev/null +++ b/src/processes/DimProcess.Executor/TechnicalUserDeletionProcessTypeExecutor.cs @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * + * 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.DbAccess; +using Dim.DbAccess.Repositories; +using Dim.Entities.Enums; +using DimProcess.Library; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +using System.Collections.Immutable; + +namespace DimProcess.Executor; + +public class TechnicalUserDeletionProcessTypeExecutor( + IDimRepositories dimRepositories, + ITechnicalUserDeletionProcessHandler technicalUserDeletionProcessHandler) + : IProcessTypeExecutor +{ + private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( + ProcessStepTypeId.DELETE_TECHNICAL_USER, + ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); + + private Guid _technicalUserId; + private string? _tenantName; + + public ProcessTypeId GetProcessTypeId() => ProcessTypeId.DELETE_TECHNICAL_USER; + 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, technicalUserId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForTechnicalUserProcessId(processId).ConfigureAwait(false); + if (!exists) + { + throw new NotFoundException($"process {processId} does not exist or is not associated with an technical user"); + } + + _technicalUserId = technicalUserId; + _tenantName = $"{bpn}_{companyName}"; + return new IProcessTypeExecutor.InitializationResult(false, null); + } + + public async ValueTask ExecuteProcessStep(ProcessStepTypeId processStepTypeId, IEnumerable processStepTypeIds, CancellationToken cancellationToken) + { + if (_technicalUserId == Guid.Empty || _tenantName is null) + { + throw new UnexpectedConditionException("technicalUserId and tenantName should never be empty here"); + } + + IEnumerable? nextStepTypeIds; + ProcessStepStatusId stepStatusId; + bool modified; + string? processMessage; + + try + { + (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch + { + ProcessStepTypeId.DELETE_TECHNICAL_USER => await technicalUserDeletionProcessHandler.DeleteServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK => await technicalUserDeletionProcessHandler.SendCallback(_technicalUserId, 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/DimProcess.Library/Callback/CallbackService.cs b/src/processes/DimProcess.Library/Callback/CallbackService.cs index a92d49c..b90d0c1 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -59,4 +59,11 @@ public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress clientSecret); await httpClient.PostAsJsonAsync($"/api/administration/serviceAccount/callback/{externalId}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); } + + public async Task SendTechnicalUserDeletionCallback(Guid externalId, CancellationToken cancellationToken) + { + var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(false); + await httpClient.PostAsync($"/api/administration/serviceAccount/callback/{externalId}/delete", null, cancellationToken).ConfigureAwait(false); + } } diff --git a/src/processes/DimProcess.Library/Callback/ICallbackService.cs b/src/processes/DimProcess.Library/Callback/ICallbackService.cs index 8dca0dd..5f49c51 100644 --- a/src/processes/DimProcess.Library/Callback/ICallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/ICallbackService.cs @@ -27,4 +27,5 @@ public interface ICallbackService Task SendCallback(string bpn, ServiceCredentialBindingDetailResponse dimDetails, JsonDocument didDocument, string did, CancellationToken cancellationToken); Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken); + Task SendTechnicalUserDeletionCallback(Guid externalId, CancellationToken cancellationToken); } diff --git a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs index e2d2069..53ed4ae 100644 --- a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs +++ b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs @@ -62,7 +62,8 @@ public static IServiceCollection AddTechnicalUserProcessHandler(this IServiceCol services .AddTransient() - .AddTransient() + .AddTransient() + .AddTransient() .AddCfClient(config.GetSection("Cf")) .AddCallbackClient(config.GetSection("Callback")); diff --git a/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/ITechnicalUserCreationProcessHandler.cs similarity index 96% rename from src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs rename to src/processes/DimProcess.Library/ITechnicalUserCreationProcessHandler.cs index f84d75f..3e5d745 100644 --- a/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs +++ b/src/processes/DimProcess.Library/ITechnicalUserCreationProcessHandler.cs @@ -21,7 +21,7 @@ namespace DimProcess.Library; -public interface ITechnicalUserProcessHandler +public interface ITechnicalUserCreationProcessHandler { Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); diff --git a/src/processes/DimProcess.Library/ITechnicalUserDeletionProcessHandler.cs b/src/processes/DimProcess.Library/ITechnicalUserDeletionProcessHandler.cs new file mode 100644 index 0000000..6dff894 --- /dev/null +++ b/src/processes/DimProcess.Library/ITechnicalUserDeletionProcessHandler.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * + * 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 DimProcess.Library; + +public interface ITechnicalUserDeletionProcessHandler +{ + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid technicalUserId, CancellationToken cancellationToken); +} diff --git a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/TechnicalUserCreationProcessHandler.cs similarity index 97% rename from src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs rename to src/processes/DimProcess.Library/TechnicalUserCreationProcessHandler.cs index fd5b1ea..cb8d426 100644 --- a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs +++ b/src/processes/DimProcess.Library/TechnicalUserCreationProcessHandler.cs @@ -29,11 +29,11 @@ namespace DimProcess.Library; -public class TechnicalUserProcessHandler( +public class TechnicalUserCreationProcessHandler( IDimRepositories dimRepositories, ICfClient cfClient, ICallbackService callbackService, - IOptions options) : ITechnicalUserProcessHandler + IOptions options) : ITechnicalUserCreationProcessHandler { private readonly TechnicalUserSettings _settings = options.Value; @@ -85,7 +85,7 @@ public class TechnicalUserProcessHandler( technicalUser.EncryptionMode = _settings.EncryptionConfigIndex; }); return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK, 1), + Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, 1), ProcessStepStatusId.DONE, false, null); diff --git a/src/processes/DimProcess.Library/TechnicalUserDeletionProcessHandler.cs b/src/processes/DimProcess.Library/TechnicalUserDeletionProcessHandler.cs new file mode 100644 index 0000000..d578e9f --- /dev/null +++ b/src/processes/DimProcess.Library/TechnicalUserDeletionProcessHandler.cs @@ -0,0 +1,66 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * + * 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.Clients.Api.Cf; +using Dim.DbAccess; +using Dim.DbAccess.Repositories; +using Dim.Entities.Enums; +using DimProcess.Library.Callback; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; + +namespace DimProcess.Library; + +public class TechnicalUserDeletionProcessHandler( + IDimRepositories dimRepositories, + ICfClient cfClient, + ICallbackService callbackService) : ITechnicalUserDeletionProcessHandler +{ + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + { + var (spaceId, technicalUserName) = await dimRepositories.GetInstance().GetSpaceIdAndTechnicalUserName(technicalUserId).ConfigureAwait(false); + if (spaceId == null) + { + throw new ConflictException("SpaceId must not be null."); + } + + var serviceBindingId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{technicalUserName}-dim-key01", cancellationToken).ConfigureAwait(false); + await cfClient.DeleteServiceInstanceBindings(serviceBindingId, cancellationToken).ConfigureAwait(false); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid technicalUserId, CancellationToken cancellationToken) + { + var tenantRepository = dimRepositories.GetInstance(); + + var externalId = await tenantRepository.GetExternalIdForTechnicalUser(technicalUserId).ConfigureAwait(false); + tenantRepository.RemoveTechnicalUser(technicalUserId); + await callbackService.SendTechnicalUserDeletionCallback(externalId, cancellationToken).ConfigureAwait(false); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.DONE, + false, + null); + } +} diff --git a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs index 167e18c..dfda889 100644 --- a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs @@ -125,4 +125,25 @@ public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUse await dimRepositories.SaveAsync().ConfigureAwait(false); } + + public async Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData, CancellationToken cancellationToken) + { + var (exists, technicalUserId) = await dimRepositories.GetInstance().GetTechnicalUserForBpn(bpn, technicalUserData.Name).ConfigureAwait(false); + if (!exists) + { + throw NotFoundException.Create(DimErrors.NO_TECHNICAL_USER_FOUND, new ErrorParameter[] { new("bpn", bpn) }); + } + + var processStepRepository = dimRepositories.GetInstance(); + var processId = processStepRepository.CreateProcess(ProcessTypeId.DELETE_TECHNICAL_USER).Id; + processStepRepository.CreateProcessStep(ProcessStepTypeId.DELETE_TECHNICAL_USER, ProcessStepStatusId.TODO, processId); + + dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, null, t => + { + t.ExternalId = technicalUserData.ExternalId; + t.ProcessId = processId; + }); + + await dimRepositories.SaveAsync().ConfigureAwait(false); + } } diff --git a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs index b01feab..4515e6b 100644 --- a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs @@ -28,4 +28,5 @@ public interface IDimBusinessLogic : ITransient Task GetStatusList(string bpn, CancellationToken cancellationToken); Task CreateStatusList(string bpn, CancellationToken cancellationToken); Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData, CancellationToken cancellationToken); + Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData, CancellationToken cancellationToken); } diff --git a/src/web/Dim.Web/Controllers/DimController.cs b/src/web/Dim.Web/Controllers/DimController.cs index b492719..ab6d7c2 100644 --- a/src/web/Dim.Web/Controllers/DimController.cs +++ b/src/web/Dim.Web/Controllers/DimController.cs @@ -69,7 +69,14 @@ public static RouteGroupBuilder MapDimApi(this RouteGroupBuilder group) .WithSwaggerDescription("Creates a technical user for the dim of the given bpn", "Example: Post: api/dim/technical-user/{bpn}", "bpn of the company") - // .RequireAuthorization(r => r.RequireRole("create_technical_user")) + .RequireAuthorization(r => r.RequireRole("create_technical_user")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + policyHub.MapPost("technical-user/{bpn}/delete", ([FromRoute] string bpn, [FromBody] TechnicalUserData technicalUserData, CancellationToken cancellationToken, [FromServices] IDimBusinessLogic dimBusinessLogic) => dimBusinessLogic.DeleteTechnicalUser(bpn, technicalUserData, cancellationToken)) + .WithSwaggerDescription("Deletes a technical user with the given name of the given bpn", + "Example: Post: api/dim/technical-user/{bpn}/delete", + "bpn of the company") + .RequireAuthorization(r => r.RequireRole("delete_technical_user")) .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); return group; diff --git a/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs b/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs index ac1a7df..a84fbd5 100644 --- a/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs +++ b/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs @@ -29,7 +29,8 @@ public class DimErrorMessageContainer : IErrorMessageContainer private static readonly IReadOnlyDictionary _messageContainer = new Dictionary { { DimErrors.NO_COMPANY_FOR_BPN, "No Tenant found for Bpn {bpn}" }, { DimErrors.NO_COMPANY_ID_SET, "No Company Id set" }, - { DimErrors.NO_INSTANCE_ID_SET, "No Instnace Id set" }, + { DimErrors.NO_INSTANCE_ID_SET, "No Instance Id set" }, + { DimErrors.NO_TECHNICAL_USER_FOUND, "No Technical User found" }, }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); public Type Type { get => typeof(DimErrors); } @@ -40,5 +41,6 @@ public enum DimErrors { NO_COMPANY_FOR_BPN, NO_COMPANY_ID_SET, - NO_INSTANCE_ID_SET + NO_INSTANCE_ID_SET, + NO_TECHNICAL_USER_FOUND } diff --git a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/TechnicalUserCreationProcessHandlerTests.cs similarity index 72% rename from tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs rename to tests/processes/DimProcess.Library.Tests/TechnicalUserCreationProcessHandlerTests.cs index 2e5fd88..324e3cd 100644 --- a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/TechnicalUserCreationProcessHandlerTests.cs @@ -11,31 +11,27 @@ namespace DimProcess.Library.Tests; -public class TechnicalUserProcessHandlerTests +public class TechnicalUserCreationProcessHandlerTests { - private readonly ICallbackService _callbackService; - private readonly IFixture _fixture; - private readonly IDimRepositories _repositories; private readonly ITenantRepository _tenantRepositories; - private readonly IOptions _options; private readonly ICfClient _cfClient; - private readonly TechnicalUserProcessHandler _sut; + private readonly TechnicalUserCreationProcessHandler _sut; - public TechnicalUserProcessHandlerTests() + public TechnicalUserCreationProcessHandlerTests() { - _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); - _fixture.Behaviors.OfType().ToList() - .ForEach(b => _fixture.Behaviors.Remove(b)); - _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); - _repositories = A.Fake(); + var repositories = A.Fake(); _tenantRepositories = A.Fake(); - A.CallTo(() => _repositories.GetInstance()).Returns(_tenantRepositories); + A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepositories); _cfClient = A.Fake(); - _callbackService = A.Fake(); - _options = Options.Create(new TechnicalUserSettings + var callbackService = A.Fake(); + var options = Options.Create(new TechnicalUserSettings { EncryptionConfigIndex = 0, EncryptionConfigs = new[] @@ -50,7 +46,7 @@ public TechnicalUserProcessHandlerTests() } }); - _sut = new TechnicalUserProcessHandler(_repositories, _cfClient, _callbackService, _options); + _sut = new TechnicalUserCreationProcessHandler(repositories, _cfClient, callbackService, options); } #region CreateSubaccount @@ -82,11 +78,10 @@ public async Task CreateSubaccount_WithValidData_ReturnsExpected() result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK); technicalUser.EncryptionMode.Should().NotBeNull().And.Be(0); technicalUser.ClientId.Should().Be("cl1"); } #endregion - } diff --git a/tests/processes/DimProcess.Library.Tests/TechnicalUserDeletionProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/TechnicalUserDeletionProcessHandlerTests.cs new file mode 100644 index 0000000..42bd942 --- /dev/null +++ b/tests/processes/DimProcess.Library.Tests/TechnicalUserDeletionProcessHandlerTests.cs @@ -0,0 +1,121 @@ +using Dim.Clients.Api.Cf; +using Dim.DbAccess; +using Dim.DbAccess.Repositories; +using Dim.Entities.Entities; +using Dim.Entities.Enums; +using DimProcess.Library.Callback; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; + +namespace DimProcess.Library.Tests; + +public class TechnicalUserDeletionProcessHandlerTests +{ + private readonly ITenantRepository _tenantRepositories; + private readonly ICfClient _cfClient; + private readonly ICallbackService _callbackService; + private readonly TechnicalUserDeletionProcessHandler _sut; + + public TechnicalUserDeletionProcessHandlerTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + var repositories = A.Fake(); + _tenantRepositories = A.Fake(); + + A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepositories); + + _cfClient = A.Fake(); + _callbackService = A.Fake(); + + _sut = new TechnicalUserDeletionProcessHandler(repositories, _cfClient, _callbackService); + } + + #region DeleteServiceInstanceBindings + + [Fact] + public async Task DeleteServiceInstanceBindings_WithValidData_ReturnsExpected() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + var serviceBindingId = Guid.NewGuid(); + var spaceId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetSpaceIdAndTechnicalUserName(technicalUserId)) + .Returns(new ValueTuple(spaceId, "test")); + A.CallTo(() => _cfClient.GetServiceBinding("test", spaceId, A._, A._)) + .Returns(serviceBindingId); + + // Act + var result = await _sut.DeleteServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + + // Assert + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); + A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(A._, A._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(serviceBindingId, A._)) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task DeleteServiceInstanceBindings_WithoutSpaceId_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetSpaceIdAndTechnicalUserName(technicalUserId)) + .Returns(new ValueTuple(null, "test")); + async Task Act() => await _sut.DeleteServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("SpaceId must not be null."); + A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(A._, A._)) + .MustNotHaveHappened(); + } + + #endregion + + #region SendCallback + + [Fact] + public async Task SendCallback_WithValidData_ReturnsExpected() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + var externalId = Guid.NewGuid(); + var technicalUsers = new List + { + new(technicalUserId, Guid.NewGuid(), Guid.NewGuid(), "sa-t", Guid.NewGuid()) + }; + A.CallTo(() => _tenantRepositories.GetExternalIdForTechnicalUser(technicalUserId)) + .Returns(externalId); + A.CallTo(() => _tenantRepositories.RemoveTechnicalUser(A._)) + .Invokes((Guid tuId) => + { + var user = technicalUsers.Single(x => x.Id == tuId); + technicalUsers.Remove(user); + }); + + // Act + var result = await _sut.SendCallback(technicalUserId, CancellationToken.None); + + // Assert + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().BeNull(); + technicalUsers.Should().BeEmpty(); + A.CallTo(() => _callbackService.SendTechnicalUserDeletionCallback(A._, A._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _callbackService.SendTechnicalUserDeletionCallback(externalId, A._)) + .MustHaveHappenedOnceExactly(); + } + + #endregion +}