diff --git a/charts/dim/templates/cronjob-processes.yaml b/charts/dim/templates/cronjob-processes.yaml index 6ab4ae6..bb01dd7 100644 --- a/charts/dim/templates/cronjob-processes.yaml +++ b/charts/dim/templates/cronjob-processes.yaml @@ -115,6 +115,19 @@ spec: value: "{{ .Values.processesworker.callback.tokenAddress }}" - name: "CALLBACK__BASEADDRESS" value: "{{ .Values.processesworker.callback.baseAddress }}" + - name: "TECHNICALUSERCREATION__ENCRYPTIONCONFIGINDEX" + value: "{{ .Values.processesworker.technicalUserCreation.encryptionConfigIndex }}" + - name: "TECHNICALUSERCREATION__ENCRYPTIONCONFIGS__0__INDEX" + value: "{{ .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.index }}" + - name: "TECHNICALUSERCREATION__ENCRYPTIONCONFIGS__0__ENCRYPTIONKEY" + valueFrom: + secretKeyRef: + name: "{{ template "dim.secretName" . }}" + key: "technicalusercreation-encryption-key0" + - name: "TECHNICALUSERCREATION__ENCRYPTIONCONFIGS__0__CIPHERMODE" + value: "{{ .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.cipherMode }}" + - name: "TECHNICALUSERCREATION__ENCRYPTIONCONFIGS__0__PADDINGMODE" + value: "{{ .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.paddingMode }}" ports: - name: http containerPort: {{ .Values.portContainer }} diff --git a/charts/dim/templates/secret.yaml b/charts/dim/templates/secret.yaml index d7591f9..6b51653 100644 --- a/charts/dim/templates/secret.yaml +++ b/charts/dim/templates/secret.yaml @@ -37,11 +37,13 @@ data: client-secret-cis-central: {{ coalesce ( .Values.processesworker.dim.clientSecretCisCentral | b64enc ) ( index $secret.data "client-secret-cis-central" ) | default ( randAlphaNum 32 ) | quote }} client-secret-cf: {{ coalesce ( .Values.processesworker.cf.clientSecret | b64enc ) ( index $secret.data "client-secret-cf" ) | default ( randAlphaNum 32 ) | quote }} client-secret-callback: {{ coalesce ( .Values.processesworker.callback.clientSecret | b64enc ) ( index $secret.data "client-secret-callback" ) | default ( randAlphaNum 32 ) | quote }} + technicalusercreation-encryption-key0: {{ coalesce ( .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.encryptionKey | b64enc ) ( index $secret.data "technicalusercreation-encryption-key0" ) | default ( randAlphaNum 32 ) | quote }} {{ else -}} stringData: # if secret doesn't exist, use provided value from values file or generate a random one client-secret-cis-central: {{ .Values.processesworker.dim.clientSecretCisCentral | default ( randAlphaNum 32 ) | quote }} client-secret-cf: {{ .Values.processesworker.cf.clientSecret | default ( randAlphaNum 32 ) | quote }} client-secret-callback: {{ .Values.processesworker.callback.clientSecret | default ( randAlphaNum 32 ) | quote }} + technicalusercreation-encryption-key0: {{ .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.encryptionKey | default ( randAlphaNum 32 ) | quote }} {{ end }} {{- end -}} diff --git a/charts/dim/values.yaml b/charts/dim/values.yaml index ac3d68e..b085b63 100644 --- a/charts/dim/values.yaml +++ b/charts/dim/values.yaml @@ -106,6 +106,16 @@ processesworker: tokenAddress: "" # -- Url to the cf service api baseAddress: "" + technicalUserCreation: + encryptionConfigIndex: 0 + encryptionConfigs: + index0: + index: 0 + cipherMode: "CBC" + paddingMode: "PKCS7" + # -- EncryptionKey to encrypt the technical user client-secret. Secret-key 'technicalusercreation-encryption-key0'. + # Expected format is 256 bit (64 digits) hex. + encryptionKey: "" # -- Secret containing "client-secret-cis-central", "client-secret-cf" and "client-secret-callback" existingSecret: "" diff --git a/consortia/environments/values-dev.yaml b/consortia/environments/values-dev.yaml index 9f91c12..e314eb2 100644 --- a/consortia/environments/values-dev.yaml +++ b/consortia/environments/values-dev.yaml @@ -85,7 +85,11 @@ processesworker: clientSecret: "" tokenAddress: "http://centralidp.dev.demo.catena-x.net/auth/realms/CX-Central/protocol/openid-connect/token" # -- Url to the cf service api - baseAddress: "https://portal-backend.dev.demo.catena-x.net/api/administration/registration/dim/" + baseAddress: "https://portal-backend.dev.demo.catena-x.net" + technicalUserCreation: + encryptionConfigs: + index0: + encryptionKey: "<" idp: address: "https://centralidp.dev.demo.catena-x.net" diff --git a/consortia/environments/values-int.yaml b/consortia/environments/values-int.yaml index 522b8d5..0c7249b 100644 --- a/consortia/environments/values-int.yaml +++ b/consortia/environments/values-int.yaml @@ -76,7 +76,11 @@ processesworker: clientSecret: "" tokenAddress: "http://centralidp.int.demo.catena-x.net/auth/realms/CX-Central/protocol/openid-connect/token" # -- Url to the cf service api - baseAddress: "https://portal-backend.dev.demo.catena-x.net/api/administration/registration/dim/" + baseAddress: "https://portal-backend.dev.demo.catena-x.net" + technicalUserCreation: + encryptionConfigs: + index0: + encryptionKey: "<" idp: address: "https://centralidp.int.demo.catena-x.net" diff --git a/src/clients/Dim.Clients/Api/Cf/CfClient.cs b/src/clients/Dim.Clients/Api/Cf/CfClient.cs index 6f052c9..0659cbb 100644 --- a/src/clients/Dim.Clients/Api/Cf/CfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/CfClient.cs @@ -208,13 +208,13 @@ private async Task GetServiceInstances(string tenantName, Guid? spaceId, C } } - public async Task CreateServiceInstanceBindings(string tenantName, Guid spaceId, CancellationToken cancellationToken) + public async Task CreateServiceInstanceBindings(string tenantName, string? keyName, Guid spaceId, CancellationToken cancellationToken) { var serviceInstanceId = await GetServiceInstances(tenantName, spaceId, cancellationToken).ConfigureAwait(false); var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); var data = new CreateServiceCredentialBindingRequest( "key", - $"{tenantName}-dim-key01", + $"{keyName ?? tenantName}-dim-key01", new ServiceCredentialRelationships( new DimServiceInstance(new DimData(serviceInstanceId))) ); diff --git a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs index 7ac7421..f3315fc 100644 --- a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs @@ -26,7 +26,7 @@ public interface ICfClient Task GetServicePlan(string servicePlanName, string servicePlanType, CancellationToken cancellationToken); Task GetSpace(string tenantName, CancellationToken cancellationToken); Task CreateDimServiceInstance(string tenantName, Guid spaceId, Guid servicePlanId, CancellationToken cancellationToken); - Task CreateServiceInstanceBindings(string tenantName, Guid spaceId, CancellationToken cancellationToken); + 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); } diff --git a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs index 74d69c4..69140f7 100644 --- a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs @@ -35,4 +35,10 @@ public interface ITenantRepository Task<(Guid? DimInstanceId, string HostingUrl, bool IsIssuer)> GetDimInstanceIdAndHostingUrl(Guid tenantId); Task<(string? ApplicationId, Guid? CompanyId, Guid? DimInstanceId, bool IsIssuer)> GetApplicationAndCompanyId(Guid tenantId); Task<(bool Exists, Guid? CompanyId, Guid? InstanceId)> GetCompanyAndInstanceIdForBpn(string bpn); + Task<(bool Exists, Guid TenantId)> GetTenantForBpn(string bpn); + void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId); + void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify); + Task<(bool Exists, Guid TechnicalUserId, string CompanyName, string Bpn)> GetTenantDataForTechnicalUserProcessId(Guid processId); + Task<(Guid? spaceId, string technicalUserName)> GetSpaceIdAndTechnicalUserName(Guid technicalUserId); + Task<(Guid ExternalId, string? TokenAddress, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetTechnicalUserCallbackData(Guid technicalUserId); } diff --git a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs index 0c37946..1b0ca1d 100644 --- a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs @@ -105,4 +105,44 @@ public void AttachAndModifyTenant(Guid tenantId, Action? initialize, Act _context.Tenants.Where(x => x.Bpn == bpn) .Select(x => new ValueTuple(true, x.CompanyId, x.DimInstanceId)) .SingleOrDefaultAsync(); + + public void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId) => + _context.TechnicalUsers.Add(new TechnicalUser(Guid.NewGuid(), tenantId, externalId, technicalUserName, processId)); + + public void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify) + { + var technicalUser = new TechnicalUser(technicalUserId, Guid.Empty, Guid.Empty, null!, Guid.Empty); + initialize?.Invoke(technicalUser); + _context.TechnicalUsers.Attach(technicalUser); + modify(technicalUser); + } + + public Task<(bool Exists, Guid TenantId)> GetTenantForBpn(string bpn) => + _context.Tenants.Where(x => x.Bpn == bpn) + .Select(x => new ValueTuple(true, x.Id)) + .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid TechnicalUserId, string CompanyName, string Bpn)> GetTenantDataForTechnicalUserProcessId(Guid processId) => + _context.TechnicalUsers + .Where(x => x.ProcessId == processId) + .Select(x => new ValueTuple(true, x.Id, x.Tenant!.CompanyName, x.Tenant.Bpn)) + .SingleOrDefaultAsync(); + + public Task<(Guid? spaceId, string technicalUserName)> GetSpaceIdAndTechnicalUserName(Guid technicalUserId) => + _context.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple(x.Tenant!.SpaceId, x.TechnicalUserName)) + .SingleOrDefaultAsync(); + + public Task<(Guid ExternalId, string? TokenAddress, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetTechnicalUserCallbackData(Guid technicalUserId) => + _context.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple( + x.ExternalId, + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode)) + .SingleOrDefaultAsync(); } diff --git a/src/database/Dim.Entities/DimDbContext.cs b/src/database/Dim.Entities/DimDbContext.cs index 64bc708..8897ba4 100644 --- a/src/database/Dim.Entities/DimDbContext.cs +++ b/src/database/Dim.Entities/DimDbContext.cs @@ -40,6 +40,7 @@ public DimDbContext(DbContextOptions options) public virtual DbSet ProcessStepTypes { get; set; } = default!; public virtual DbSet ProcessTypes { get; set; } = default!; public virtual DbSet Tenants { get; set; } = default!; + public virtual DbSet TechnicalUsers { get; set; } = default!; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -84,10 +85,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Select(e => new ProcessStepType(e)) ); - modelBuilder.Entity() - .HasOne(d => d.Process) - .WithMany(p => p.Tenants) - .HasForeignKey(d => d.ProcessId) - .OnDelete(DeleteBehavior.ClientSetNull); + modelBuilder.Entity(t => + { + t.HasOne(d => d.Process) + .WithMany(p => p.Tenants) + .HasForeignKey(d => d.ProcessId) + .OnDelete(DeleteBehavior.ClientSetNull); + }); + + modelBuilder.Entity(tu => + { + tu.HasOne(d => d.Process) + .WithMany(p => p.TechnicalUsers) + .HasForeignKey(d => d.ProcessId) + .OnDelete(DeleteBehavior.ClientSetNull); + + tu.HasOne(t => t.Tenant) + .WithMany(t => t.TechnicalUsers) + .HasForeignKey(t => t.TenantId) + .OnDelete(DeleteBehavior.ClientSetNull); + }); } } diff --git a/src/database/Dim.Entities/Entities/Process.cs b/src/database/Dim.Entities/Entities/Process.cs index 96c3253..4f03eeb 100644 --- a/src/database/Dim.Entities/Entities/Process.cs +++ b/src/database/Dim.Entities/Entities/Process.cs @@ -29,6 +29,7 @@ private Process() { ProcessSteps = new HashSet(); Tenants = new HashSet(); + TechnicalUsers = new HashSet(); } public Process(Guid id, ProcessTypeId processTypeId, Guid version) : this() @@ -51,4 +52,5 @@ public Process(Guid id, ProcessTypeId processTypeId, Guid version) : this() public virtual ProcessType? ProcessType { get; set; } public virtual ICollection ProcessSteps { get; private set; } public virtual ICollection Tenants { get; private set; } + public virtual ICollection TechnicalUsers { get; private set; } } diff --git a/src/database/Dim.Entities/Entities/TechnicalUser.cs b/src/database/Dim.Entities/Entities/TechnicalUser.cs new file mode 100644 index 0000000..8b3d235 --- /dev/null +++ b/src/database/Dim.Entities/Entities/TechnicalUser.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +namespace Dim.Entities.Entities; + +public class TechnicalUser( + Guid id, + Guid tenantId, + Guid externalId, + string technicalUserName, + Guid processId) +{ + public Guid Id { get; set; } = id; + public Guid TenantId { get; set; } = tenantId; + public Guid ExternalId { get; set; } = externalId; + public string TechnicalUserName { get; set; } = technicalUserName; + public string? TokenAddress { get; set; } + public string? ClientId { get; set; } + public byte[]? ClientSecret { get; set; } + public byte[]? InitializationVector { get; set; } + public int? EncryptionMode { get; set; } + public Guid ProcessId { get; set; } = processId; + public virtual Tenant? Tenant { get; set; } + public virtual Process? Process { get; set; } +} diff --git a/src/database/Dim.Entities/Entities/Tenant.cs b/src/database/Dim.Entities/Entities/Tenant.cs index 095d292..e789c38 100644 --- a/src/database/Dim.Entities/Entities/Tenant.cs +++ b/src/database/Dim.Entities/Entities/Tenant.cs @@ -19,28 +19,24 @@ namespace Dim.Entities.Entities; -public class Tenant +public class Tenant( + Guid id, + string companyName, + string bpn, + string didDocumentLocation, + bool isIssuer, + Guid processId, + Guid operatorId) { - public Tenant(Guid id, string companyName, string bpn, string didDocumentLocation, bool isIssuer, Guid processId, Guid operatorId) - { - Id = id; - CompanyName = companyName; - Bpn = bpn; - DidDocumentLocation = didDocumentLocation; - IsIssuer = isIssuer; - ProcessId = processId; - OperatorId = operatorId; - } + public Guid Id { get; set; } = id; + public string CompanyName { get; set; } = companyName; + public string Bpn { get; set; } = bpn; - public Guid Id { get; set; } - public string CompanyName { get; set; } - public string Bpn { get; set; } + public string DidDocumentLocation { get; set; } = didDocumentLocation; - public string DidDocumentLocation { get; set; } + public bool IsIssuer { get; set; } = isIssuer; - public bool IsIssuer { get; set; } - - public Guid ProcessId { get; set; } + public Guid ProcessId { get; set; } = processId; public Guid? SubAccountId { get; set; } @@ -56,6 +52,7 @@ public Tenant(Guid id, string companyName, string bpn, string didDocumentLocatio public string? ApplicationId { get; set; } public Guid? CompanyId { get; set; } public string? ApplicationKey { get; set; } - public Guid OperatorId { get; set; } + public Guid OperatorId { get; set; } = operatorId; public virtual Process? Process { get; set; } + public virtual ICollection TechnicalUsers { get; private set; } = new HashSet(); } diff --git a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs index 59b36e6..91f2ee6 100644 --- a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs @@ -39,5 +39,10 @@ public enum ProcessStepTypeId CREATE_COMPANY_IDENTITY = 15, ASSIGN_COMPANY_APPLICATION = 16, CREATE_STATUS_LIST = 17, - SEND_CALLBACK = 18 + SEND_CALLBACK = 18, + + // Create Technical User + CREATE_TECHNICAL_USER = 100, + GET_TECHNICAL_USER_DATA = 101, + SEND_TECHNICAL_USER_CALLBACK = 102, } diff --git a/src/database/Dim.Entities/Enums/ProcessTypeId.cs b/src/database/Dim.Entities/Enums/ProcessTypeId.cs index a0b9a3d..19f966d 100644 --- a/src/database/Dim.Entities/Enums/ProcessTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessTypeId.cs @@ -21,5 +21,6 @@ namespace Dim.Entities.Enums; public enum ProcessTypeId { - SETUP_DIM = 1 + SETUP_DIM = 1, + CREATE_TECHNICAL_USER = 2 } diff --git a/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.Designer.cs b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.Designer.cs new file mode 100644 index 0000000..0ad55bb --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.Designer.cs @@ -0,0 +1,564 @@ +/******************************************************************************** + * 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; +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("20240409140733_1.0.1")] + partial class _101 + { + /// + 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_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" + }); + }); + + 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/20240409140733_1.0.1.cs b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.cs new file mode 100644 index 0000000..b041606 --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.cs @@ -0,0 +1,130 @@ +/******************************************************************************** + * 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 Microsoft.EntityFrameworkCore.Migrations; +using System; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Dim.Migrations.Migrations +{ + /// + public partial class _101 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "technical_users", + schema: "dim", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + tenant_id = table.Column(type: "uuid", nullable: false), + external_id = table.Column(type: "uuid", nullable: false), + technical_user_name = table.Column(type: "text", nullable: false), + token_address = table.Column(type: "text", nullable: true), + client_id = table.Column(type: "text", nullable: true), + client_secret = table.Column(type: "bytea", nullable: true), + initialization_vector = table.Column(type: "bytea", nullable: true), + encryption_mode = table.Column(type: "integer", nullable: true), + process_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_technical_users", x => x.id); + table.ForeignKey( + name: "fk_technical_users_processes_process_id", + column: x => x.process_id, + principalSchema: "dim", + principalTable: "processes", + principalColumn: "id"); + table.ForeignKey( + name: "fk_technical_users_tenants_tenant_id", + column: x => x.tenant_id, + principalSchema: "dim", + principalTable: "tenants", + principalColumn: "id"); + }); + + migrationBuilder.InsertData( + schema: "dim", + table: "process_step_types", + columns: new[] { "id", "label" }, + values: new object[,] + { + { 100, "CREATE_TECHNICAL_USER" }, + { 101, "GET_TECHNICAL_USER_DATA" }, + { 102, "SEND_TECHNICAL_USER_CALLBACK" } + }); + + migrationBuilder.InsertData( + schema: "dim", + table: "process_types", + columns: new[] { "id", "label" }, + values: new object[] { 2, "CREATE_TECHNICAL_USER" }); + + migrationBuilder.CreateIndex( + name: "ix_technical_users_process_id", + schema: "dim", + table: "technical_users", + column: "process_id"); + + migrationBuilder.CreateIndex( + name: "ix_technical_users_tenant_id", + schema: "dim", + table: "technical_users", + column: "tenant_id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "technical_users", + schema: "dim"); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 100); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 101); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_step_types", + keyColumn: "id", + keyValue: 102); + + migrationBuilder.DeleteData( + schema: "dim", + table: "process_types", + keyColumn: "id", + keyValue: 2); + } + } +} diff --git a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs index bc06ef2..014a052 100644 --- a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs +++ b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs @@ -34,7 +34,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder .HasDefaultSchema("dim") .UseCollation("en_US.utf8") - .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("ProductVersion", "8.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -266,6 +266,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) { 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_CALLBACK" }); }); @@ -291,9 +306,70 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 1, Label = "SETUP_DIM" + }, + new + { + Id = 2, + Label = "CREATE_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") @@ -417,6 +493,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -432,6 +527,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("ProcessSteps"); + b.Navigation("TechnicalUsers"); + b.Navigation("Tenants"); }); @@ -449,6 +546,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Processes"); }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.Navigation("TechnicalUsers"); + }); #pragma warning restore 612, 618 } } diff --git a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs index b0ada0c..3130889 100644 --- a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs +++ b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs @@ -30,4 +30,9 @@ public static IServiceCollection AddDimProcessExecutor(this IServiceCollection s services .AddTransient() .AddDimProcessHandler(config); + + public static IServiceCollection AddTechnicalUserProcessExecutor(this IServiceCollection services, IConfiguration config) => + services + .AddTransient() + .AddTechnicalUserProcessHandler(config); } diff --git a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs index ee08220..61f27d5 100644 --- a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs @@ -27,11 +27,11 @@ namespace DimProcess.Executor; -public class DimProcessTypeExecutor : IProcessTypeExecutor +public class DimProcessTypeExecutor( + IDimRepositories dimRepositories, + IDimProcessHandler dimProcessHandler) + : IProcessTypeExecutor { - private readonly IDimRepositories _dimRepositories; - private readonly IDimProcessHandler _dimProcessHandler; - private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( ProcessStepTypeId.CREATE_SUBACCOUNT, ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS, @@ -55,14 +55,6 @@ public class DimProcessTypeExecutor : IProcessTypeExecutor private Guid _tenantId; private string? _tenantName; - public DimProcessTypeExecutor( - IDimRepositories dimRepositories, - IDimProcessHandler dimProcessHandler) - { - _dimRepositories = dimRepositories; - _dimProcessHandler = dimProcessHandler; - } - public ProcessTypeId GetProcessTypeId() => ProcessTypeId.SETUP_DIM; public bool IsExecutableStepTypeId(ProcessStepTypeId processStepTypeId) => _executableProcessSteps.Contains(processStepTypeId); public IEnumerable GetExecutableStepTypeIds() => _executableProcessSteps; @@ -70,7 +62,7 @@ public 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(false); if (!exists) { throw new NotFoundException($"process {processId} does not exist or is not associated with an tenant"); @@ -97,41 +89,41 @@ public DimProcessTypeExecutor( { (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { - ProcessStepTypeId.CREATE_SUBACCOUNT => await _dimProcessHandler.CreateSubaccount(_tenantId, _tenantName, cancellationToken) + ProcessStepTypeId.CREATE_SUBACCOUNT => await dimProcessHandler.CreateSubaccount(_tenantId, _tenantName, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS => await _dimProcessHandler.CreateServiceManagerBindings(_tenantId, cancellationToken) + ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS => await dimProcessHandler.CreateServiceManagerBindings(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.ASSIGN_ENTITLEMENTS => await _dimProcessHandler.AssignEntitlements(_tenantId, cancellationToken) + ProcessStepTypeId.ASSIGN_ENTITLEMENTS => await dimProcessHandler.AssignEntitlements(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_INSTANCE => await _dimProcessHandler.CreateServiceInstance(_tenantId, cancellationToken) + ProcessStepTypeId.CREATE_SERVICE_INSTANCE => await dimProcessHandler.CreateServiceInstance(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_BINDING => await _dimProcessHandler.CreateServiceBindings(_tenantId, cancellationToken) + ProcessStepTypeId.CREATE_SERVICE_BINDING => await dimProcessHandler.CreateServiceBindings(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.SUBSCRIBE_APPLICATION => await _dimProcessHandler.SubscribeApplication(_tenantId, cancellationToken) + ProcessStepTypeId.SUBSCRIBE_APPLICATION => await dimProcessHandler.SubscribeApplication(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT => await _dimProcessHandler.CreateCloudFoundryEnvironment(_tenantId, _tenantName, cancellationToken) + ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT => await dimProcessHandler.CreateCloudFoundryEnvironment(_tenantId, _tenantName, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE => await _dimProcessHandler.CreateCloudFoundrySpace(_tenantId, _tenantName, cancellationToken) + ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE => await dimProcessHandler.CreateCloudFoundrySpace(_tenantId, _tenantName, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE => await _dimProcessHandler.AddSpaceManagerRole(_tenantId, cancellationToken) + ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE => await dimProcessHandler.AddSpaceManagerRole(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE => await _dimProcessHandler.AddSpaceDeveloperRole(_tenantId, cancellationToken) + ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE => await dimProcessHandler.AddSpaceDeveloperRole(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE => await _dimProcessHandler.CreateDimServiceInstance(_tenantName, _tenantId, cancellationToken) + ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE => await dimProcessHandler.CreateDimServiceInstance(_tenantName, _tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING => await _dimProcessHandler.CreateServiceInstanceBindings(_tenantName, _tenantId, cancellationToken) + ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING => await dimProcessHandler.CreateServiceInstanceBindings(_tenantName, _tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.GET_DIM_DETAILS => await _dimProcessHandler.GetDimDetails(_tenantName, _tenantId, cancellationToken) + ProcessStepTypeId.GET_DIM_DETAILS => await dimProcessHandler.GetDimDetails(_tenantName, _tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_APPLICATION => await _dimProcessHandler.CreateApplication(_tenantName, _tenantId, cancellationToken) + ProcessStepTypeId.CREATE_APPLICATION => await dimProcessHandler.CreateApplication(_tenantName, _tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_COMPANY_IDENTITY => await _dimProcessHandler.CreateCompanyIdentity(_tenantId, _tenantName, cancellationToken) + ProcessStepTypeId.CREATE_COMPANY_IDENTITY => await dimProcessHandler.CreateCompanyIdentity(_tenantId, _tenantName, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION => await _dimProcessHandler.AssignCompanyApplication(_tenantId, cancellationToken) + ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION => await dimProcessHandler.AssignCompanyApplication(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.CREATE_STATUS_LIST => await _dimProcessHandler.CreateStatusList(_tenantId, cancellationToken) + ProcessStepTypeId.CREATE_STATUS_LIST => await dimProcessHandler.CreateStatusList(_tenantId, cancellationToken) .ConfigureAwait(false), - ProcessStepTypeId.SEND_CALLBACK => await _dimProcessHandler.SendCallback(_tenantId, cancellationToken) + ProcessStepTypeId.SEND_CALLBACK => await dimProcessHandler.SendCallback(_tenantId, cancellationToken) .ConfigureAwait(false), _ => (null, ProcessStepStatusId.TODO, false, null) }; diff --git a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs new file mode 100644 index 0000000..ce881c3 --- /dev/null +++ b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs @@ -0,0 +1,103 @@ +/******************************************************************************** + * 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.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 TechnicalUserProcessTypeExecutor( + IDimRepositories dimRepositories, + ITechnicalUserProcessHandler technicalUserProcessHandler) + : IProcessTypeExecutor +{ + private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( + ProcessStepTypeId.CREATE_TECHNICAL_USER, + ProcessStepTypeId.GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK); + + private Guid _technicalUserId; + private string? _tenantName; + + public ProcessTypeId GetProcessTypeId() => ProcessTypeId.CREATE_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.CREATE_TECHNICAL_USER => await technicalUserProcessHandler.CreateServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserProcessHandler.GetTechnicalUserData(_tenantName, _technicalUserId, cancellationToken) + .ConfigureAwait(false), + ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK => await technicalUserProcessHandler.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 7f0fe78..187bf62 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -29,20 +29,14 @@ namespace DimProcess.Library.Callback; -public class CallbackService : ICallbackService +public class CallbackService(ITokenService tokenService, IOptions options) + : ICallbackService { - private readonly ITokenService _tokenService; - private readonly CallbackSettings _settings; - - public CallbackService(ITokenService tokenService, IOptions options) - { - _tokenService = tokenService; - _settings = options.Value; - } + private readonly CallbackSettings _settings = options.Value; public async Task SendCallback(string bpn, ServiceCredentialBindingDetailResponse dimDetails, JsonDocument didDocument, string did, CancellationToken cancellationToken) { - var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken) + var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) .ConfigureAwait(false); var data = new CallbackDataModel( did, @@ -52,6 +46,17 @@ public async Task SendCallback(string bpn, ServiceCredentialBindingDetailRespons dimDetails.Credentials.Uaa.ClientId, dimDetails.Credentials.Uaa.ClientSecret) ); - await httpClient.PostAsJsonAsync($"{bpn}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); + await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); + } + + public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken) + { + var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(false); + var data = new AuthenticationDetail( + tokenAddress, + clientId, + clientSecret); + await httpClient.PostAsJsonAsync($"/api/adminstration/serviceAccount/callback/{externalId}", data, JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); } } diff --git a/src/processes/DimProcess.Library/Callback/ICallbackService.cs b/src/processes/DimProcess.Library/Callback/ICallbackService.cs index 25ea90f..cb94405 100644 --- a/src/processes/DimProcess.Library/Callback/ICallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/ICallbackService.cs @@ -25,4 +25,6 @@ namespace DimProcess.Library.Callback; 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); } diff --git a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs index 749c653..87b9a2a 100644 --- a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs +++ b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs @@ -53,4 +53,19 @@ public static IServiceCollection AddDimProcessHandler(this IServiceCollection se return services; } + + public static IServiceCollection AddTechnicalUserProcessHandler(this IServiceCollection services, IConfiguration config) + { + services.AddOptions() + .Bind(config.GetSection("TechnicalUserCreation")) + .ValidateOnStart(); + + services + .AddTransient() + .AddTransient() + .AddCfClient(config.GetSection("Cf")) + .AddCallbackClient(config.GetSection("Callback")); + + return services; + } } diff --git a/src/processes/DimProcess.Library/DependencyInjection/TechnicalUserSettings.cs b/src/processes/DimProcess.Library/DependencyInjection/TechnicalUserSettings.cs new file mode 100644 index 0000000..b0f0b06 --- /dev/null +++ b/src/processes/DimProcess.Library/DependencyInjection/TechnicalUserSettings.cs @@ -0,0 +1,15 @@ +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; + +namespace DimProcess.Library.DependencyInjection; + +public class TechnicalUserSettings +{ + [Required] + public int EncryptionConfigIndex { get; set; } + + [Required] + [DistinctValues("x => x.Index")] + public IEnumerable EncryptionConfigs { get; set; } = null!; +} diff --git a/src/processes/DimProcess.Library/DimProcess.Library.csproj b/src/processes/DimProcess.Library/DimProcess.Library.csproj index 1b4c4ea..884f3b6 100644 --- a/src/processes/DimProcess.Library/DimProcess.Library.csproj +++ b/src/processes/DimProcess.Library/DimProcess.Library.csproj @@ -31,6 +31,7 @@ + diff --git a/src/processes/DimProcess.Library/DimProcessHandler.cs b/src/processes/DimProcess.Library/DimProcessHandler.cs index 13e508b..7a43678 100644 --- a/src/processes/DimProcess.Library/DimProcessHandler.cs +++ b/src/processes/DimProcess.Library/DimProcessHandler.cs @@ -35,42 +35,20 @@ namespace DimProcess.Library; -public class DimProcessHandler : IDimProcessHandler +public class DimProcessHandler( + IDimRepositories dimRepositories, + ISubAccountClient subAccountClient, + IServiceClient serviceClient, + ISubscriptionClient subscriptionClient, + IEntitlementClient entitlementClient, + IProvisioningClient provisioningClient, + ICfClient cfClient, + IDimClient dimClient, + ICallbackService callbackService, + IOptions options) + : IDimProcessHandler { - private readonly IDimRepositories _dimRepositories; - private readonly ISubAccountClient _subAccountClient; - private readonly IServiceClient _serviceClient; - private readonly IEntitlementClient _entitlementClient; - private readonly ISubscriptionClient _subscriptionClient; - private readonly IProvisioningClient _provisioningClient; - private readonly ICfClient _cfClient; - private readonly IDimClient _dimClient; - private readonly ICallbackService _callbackService; - private readonly DimHandlerSettings _settings; - - public DimProcessHandler( - IDimRepositories dimRepositories, - ISubAccountClient subAccountClient, - IServiceClient serviceClient, - ISubscriptionClient subscriptionClient, - IEntitlementClient entitlementClient, - IProvisioningClient provisioningClient, - ICfClient cfClient, - IDimClient dimClient, - ICallbackService callbackService, - IOptions options) - { - _dimRepositories = dimRepositories; - _subAccountClient = subAccountClient; - _serviceClient = serviceClient; - _entitlementClient = entitlementClient; - _subscriptionClient = subscriptionClient; - _provisioningClient = provisioningClient; - _cfClient = cfClient; - _dimClient = dimClient; - _callbackService = callbackService; - _settings = options.Value; - } + private readonly DimHandlerSettings _settings = options.Value; public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSubaccount(Guid tenantId, string tenantName, CancellationToken cancellationToken) { @@ -83,8 +61,8 @@ public DimProcessHandler( ClientSecret = _settings.ClientsecretCisCentral }; - var subAccountId = await _subAccountClient.CreateSubaccount(subAccountAuth, adminMail, tenantName, parentDirectoryId, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + var subAccountId = await subAccountClient.CreateSubaccount(subAccountAuth, adminMail, tenantName, parentDirectoryId, cancellationToken).ConfigureAwait(false); + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.SubAccountId = null; }, @@ -108,14 +86,14 @@ public DimProcessHandler( ClientSecret = _settings.ClientsecretCisCentral }; - var tenantRepository = _dimRepositories.GetInstance(); + var tenantRepository = dimRepositories.GetInstance(); var subAccountId = await tenantRepository.GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); } - await _subAccountClient.CreateServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + await subAccountClient.CreateServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.ASSIGN_ENTITLEMENTS, 1), @@ -132,13 +110,13 @@ public DimProcessHandler( ClientId = _settings.ClientidCisCentral, ClientSecret = _settings.ClientsecretCisCentral }; - var subAccountId = await _dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); + var subAccountId = await dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); } - await _entitlementClient.AssignEntitlements(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + await entitlementClient.AssignEntitlements(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICE_INSTANCE, 1), @@ -155,16 +133,16 @@ public DimProcessHandler( ClientId = _settings.ClientidCisCentral, ClientSecret = _settings.ClientsecretCisCentral }; - var subAccountId = await _dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); + var subAccountId = await dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); } - var saBinding = await _subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var serviceInstance = await _serviceClient.CreateServiceInstance(saBinding, cancellationToken).ConfigureAwait(false); + var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + var serviceInstance = await serviceClient.CreateServiceInstance(saBinding, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.ServiceInstanceId = null; }, @@ -187,7 +165,7 @@ public DimProcessHandler( ClientId = _settings.ClientidCisCentral, ClientSecret = _settings.ClientsecretCisCentral }; - var (subAccountId, serviceInstanceId) = await _dimRepositories.GetInstance().GetSubAccountAndServiceInstanceIdsByTenantId(tenantId).ConfigureAwait(false); + var (subAccountId, serviceInstanceId) = await dimRepositories.GetInstance().GetSubAccountAndServiceInstanceIdsByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); @@ -198,10 +176,10 @@ public DimProcessHandler( throw new ConflictException("ServiceInstanceId must not be null."); } - var saBinding = await _subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var serviceBinding = await _serviceClient.CreateServiceBinding(saBinding, serviceInstanceId, cancellationToken).ConfigureAwait(false); + var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + var serviceBinding = await serviceClient.CreateServiceBinding(saBinding, serviceInstanceId, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.ServiceBindingName = null; }, @@ -224,7 +202,7 @@ public DimProcessHandler( ClientId = _settings.ClientidCisCentral, ClientSecret = _settings.ClientsecretCisCentral }; - var (subAccountId, serviceBindingName) = await _dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); + var (subAccountId, serviceBindingName) = await dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); @@ -235,9 +213,9 @@ public DimProcessHandler( throw new ConflictException("ServiceBindingName must not be null."); } - var saBinding = await _subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var bindingResponse = await _serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); - await _subscriptionClient.SubscribeApplication(saBinding.Url, bindingResponse, "decentralized-identity-management-app", "standard", cancellationToken).ConfigureAwait(false); + var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + var bindingResponse = await serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); + await subscriptionClient.SubscribeApplication(saBinding.Url, bindingResponse, "decentralized-identity-management-app", "standard", cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT, 1), @@ -255,7 +233,7 @@ public DimProcessHandler( ClientId = _settings.ClientidCisCentral, ClientSecret = _settings.ClientsecretCisCentral }; - var (subAccountId, serviceBindingName) = await _dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); + var (subAccountId, serviceBindingName) = await dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); if (subAccountId == null) { throw new ConflictException("SubAccountId must not be null."); @@ -266,9 +244,9 @@ public DimProcessHandler( throw new ConflictException("ServiceBindingName must not be null."); } - var saBinding = await _subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var bindingResponse = await _serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); - await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingResponse, tenantName, adminMail, cancellationToken) + var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); + var bindingResponse = await serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); + await provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingResponse, tenantName, adminMail, cancellationToken) .ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( @@ -280,9 +258,9 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCloudFoundrySpace(Guid tenantId, string tenantName, CancellationToken cancellationToken) { - var spaceId = await _cfClient.CreateCloudFoundrySpace(tenantName, cancellationToken).ConfigureAwait(false); + var spaceId = await cfClient.CreateCloudFoundrySpace(tenantName, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.SpaceId = null; }, @@ -300,13 +278,13 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceManagerRole(Guid tenantId, CancellationToken cancellationToken) { var adminMail = _settings.AdminMail; - var spaceId = await _dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); + var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); if (spaceId == null) { throw new ConflictException("SpaceId must not be null."); } - await _cfClient.AddSpaceRoleToUser("space_manager", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); + await cfClient.AddSpaceRoleToUser("space_manager", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE, 1), @@ -318,13 +296,13 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceDeveloperRole(Guid tenantId, CancellationToken cancellationToken) { var adminMail = _settings.AdminMail; - var spaceId = await _dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); + var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); if (spaceId == null) { throw new ConflictException("SpaceId must not be null."); } - await _cfClient.AddSpaceRoleToUser("space_developer", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); + await cfClient.AddSpaceRoleToUser("space_developer", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE, 1), @@ -335,9 +313,9 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateDimServiceInstance(string tenantName, Guid tenantId, CancellationToken cancellationToken) { - var servicePlanId = await _cfClient.GetServicePlan("decentralized-identity-management", "standard", cancellationToken).ConfigureAwait(false); - var spaceId = await _cfClient.GetSpace(tenantName, cancellationToken).ConfigureAwait(false); - await _cfClient.CreateDimServiceInstance(tenantName, spaceId, servicePlanId, cancellationToken).ConfigureAwait(false); + var servicePlanId = await cfClient.GetServicePlan("decentralized-identity-management", "standard", cancellationToken).ConfigureAwait(false); + var spaceId = await cfClient.GetSpace(tenantName, cancellationToken).ConfigureAwait(false); + await cfClient.CreateDimServiceInstance(tenantName, spaceId, servicePlanId, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING, 1), @@ -348,13 +326,13 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid tenantId, CancellationToken cancellationToken) { - var spaceId = await _dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); + var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); if (spaceId == null) { throw new ConflictException("SpaceId must not be null."); } - await _cfClient.CreateServiceInstanceBindings(tenantName, spaceId.Value, cancellationToken).ConfigureAwait(false); + await cfClient.CreateServiceInstanceBindings(tenantName, null, spaceId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.GET_DIM_DETAILS, 1), @@ -365,15 +343,15 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetDimDetails(string tenantName, Guid tenantId, CancellationToken cancellationToken) { - var spaceId = await _dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); + var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); if (spaceId == null) { throw new ConflictException("SpaceId must not be null."); } - var dimInstanceId = await _cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{tenantName}-dim-key01", cancellationToken).ConfigureAwait(false); + var dimInstanceId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{tenantName}-dim-key01", cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.DimInstanceId = null; }, @@ -390,13 +368,13 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateApplication(string tenantName, Guid tenantId, CancellationToken cancellationToken) { - var (dimInstanceId, _, _) = await _dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); + var (dimInstanceId, _, _) = await dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); if (dimInstanceId == null) { throw new ConflictException("DimInstanceId must not be null."); } - var dimDetails = await _cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); var dimAuth = new BasicAuthSettings { @@ -405,8 +383,8 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe ClientSecret = dimDetails.Credentials.Uaa.ClientSecret }; var dimBaseUrl = dimDetails.Credentials.Url; - var applicationId = await _dimClient.CreateApplication(dimAuth, dimBaseUrl, tenantName, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + var applicationId = await dimClient.CreateApplication(dimAuth, dimBaseUrl, tenantName, cancellationToken).ConfigureAwait(false); + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.ApplicationId = null; }, @@ -423,13 +401,13 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCompanyIdentity(Guid tenantId, string tenantName, CancellationToken cancellationToken) { - var (dimInstanceId, hostingUrl, isIssuer) = await _dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); + var (dimInstanceId, hostingUrl, isIssuer) = await dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); if (dimInstanceId == null) { throw new ConflictException("DimInstanceId must not be null."); } - var dimDetails = await _cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); var dimAuth = new BasicAuthSettings { @@ -438,9 +416,9 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe ClientSecret = dimDetails.Credentials.Uaa.ClientSecret }; var dimBaseUrl = dimDetails.Credentials.Url; - var result = await _dimClient.CreateCompanyIdentity(dimAuth, hostingUrl, dimBaseUrl, tenantName, isIssuer, cancellationToken).ConfigureAwait(false); + var result = await dimClient.CreateCompanyIdentity(dimAuth, hostingUrl, dimBaseUrl, tenantName, isIssuer, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.DidDownloadUrl = null; tenant.Did = null; @@ -461,7 +439,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AssignCompanyApplication(Guid tenantId, CancellationToken cancellationToken) { - var (applicationId, companyId, dimInstanceId, isIssuer) = await _dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); + var (applicationId, companyId, dimInstanceId, isIssuer) = await dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); if (applicationId == null) { throw new ConflictException("ApplicationId must always be set here"); @@ -477,7 +455,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe throw new ConflictException("DimInstanceId must not be null."); } - var dimDetails = await _cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); var dimAuth = new BasicAuthSettings { TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", @@ -485,10 +463,10 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe ClientSecret = dimDetails.Credentials.Uaa.ClientSecret }; var dimBaseUrl = dimDetails.Credentials.Url; - var applicationKey = await _dimClient.GetApplication(dimAuth, dimBaseUrl, applicationId, cancellationToken); - await _dimClient.AssignApplicationToCompany(dimAuth, dimBaseUrl, applicationKey, companyId.Value, cancellationToken).ConfigureAwait(false); + var applicationKey = await dimClient.GetApplication(dimAuth, dimBaseUrl, applicationId, cancellationToken); + await dimClient.AssignApplicationToCompany(dimAuth, dimBaseUrl, applicationKey, companyId.Value, cancellationToken).ConfigureAwait(false); - _dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => { tenant.ApplicationKey = null; }, @@ -505,7 +483,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateStatusList(Guid tenantId, CancellationToken cancellationToken) { - var (_, companyId, dimInstanceId, _) = await _dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); + var (_, companyId, dimInstanceId, _) = await dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); if (companyId == null) { throw new ConflictException("CompanyId must always be set here"); @@ -516,7 +494,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe throw new ConflictException("DimInstanceId must not be null."); } - var dimDetails = await _cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); var dimAuth = new BasicAuthSettings { TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", @@ -524,7 +502,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe ClientSecret = dimDetails.Credentials.Uaa.ClientSecret }; var dimBaseUrl = dimDetails.Credentials.Url; - await _dimClient.CreateStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + await dimClient.CreateStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.SEND_CALLBACK, 1), @@ -535,7 +513,7 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid tenantId, CancellationToken cancellationToken) { - var (bpn, downloadUrl, did, dimInstanceId) = await _dimRepositories.GetInstance().GetCallbackData(tenantId).ConfigureAwait(false); + var (bpn, downloadUrl, did, dimInstanceId) = await dimRepositories.GetInstance().GetCallbackData(tenantId).ConfigureAwait(false); if (downloadUrl == null) { throw new ConflictException("DownloadUrl must not be null."); @@ -551,10 +529,10 @@ await _provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRe throw new ConflictException("DimInstanceId must not be null."); } - var dimDetails = await _cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); - var didDocument = await _dimClient.GetDidDocument(downloadUrl, cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var didDocument = await dimClient.GetDidDocument(downloadUrl, cancellationToken).ConfigureAwait(false); - await _callbackService.SendCallback(bpn, dimDetails, didDocument, did, cancellationToken).ConfigureAwait(false); + await callbackService.SendCallback(bpn, dimDetails, didDocument, did, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, diff --git a/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs new file mode 100644 index 0000000..6b7a694 --- /dev/null +++ b/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs @@ -0,0 +1,29 @@ +/******************************************************************************** + * 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 DimProcess.Library; + +public interface ITechnicalUserProcessHandler +{ + 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); + 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/TechnicalUserProcessHandler.cs new file mode 100644 index 0000000..258f600 --- /dev/null +++ b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs @@ -0,0 +1,140 @@ +/******************************************************************************** + * 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.Clients.Api.Cf; +using Dim.DbAccess; +using Dim.DbAccess.Repositories; +using Dim.Entities.Enums; +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.Encryption; + +namespace DimProcess.Library; + +public class TechnicalUserProcessHandler( + IDimRepositories dimRepositories, + ICfClient cfClient, + ICallbackService callbackService, + IOptions options) : ITechnicalUserProcessHandler +{ + private readonly TechnicalUserSettings _settings = options.Value; + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(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."); + } + + await cfClient.CreateServiceInstanceBindings(tenantName, technicalUserName, spaceId.Value, cancellationToken).ConfigureAwait(false); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.GET_TECHNICAL_USER_DATA, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(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 dimInstanceId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{technicalUserName}-dim-key01", cancellationToken).ConfigureAwait(false); + var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId, cancellationToken).ConfigureAwait(false); + var (secret, initializationVector, encryptionMode) = Encrypt(dimDetails.Credentials.Uaa.ClientSecret); + + dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, technicalUser => + { + technicalUser.TokenAddress = null; + technicalUser.ClientId = null; + technicalUser.ClientSecret = null; + technicalUser.InitializationVector = null; + technicalUser.EncryptionMode = null; + }, + technicalUser => + { + technicalUser.TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token"; + technicalUser.ClientId = dimDetails.Credentials.Uaa.ClientId; + technicalUser.ClientSecret = secret; + technicalUser.InitializationVector = initializationVector; + technicalUser.EncryptionMode = encryptionMode; + }); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CALLBACK, 1), + ProcessStepStatusId.DONE, + false, + null); + } + + private (byte[] Secret, byte[] InitializationVector, int EncryptionMode) Encrypt(string clientSecret) + { + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == _settings.EncryptionConfigIndex) ?? throw new ConfigurationException($"EncryptionModeIndex {_settings.EncryptionConfigIndex} is not configured"); + var (secret, initializationVector) = CryptoHelper.Encrypt(clientSecret, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + return (secret, initializationVector, _settings.EncryptionConfigIndex); + } + + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid technicalUserId, CancellationToken cancellationToken) + { + var (externalId, tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = await dimRepositories.GetInstance().GetTechnicalUserCallbackData(technicalUserId).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(clientId)) + { + throw new ConflictException("ClientId must not be null"); + } + + if (string.IsNullOrWhiteSpace(tokenAddress)) + { + throw new ConflictException("TokenAddress must not be null"); + } + + var secret = Decrypt(clientSecret, initializationVector, encryptionMode); + + await callbackService.SendTechnicalUserCallback(externalId, tokenAddress, clientId, secret, cancellationToken).ConfigureAwait(false); + + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.DONE, + false, + null); + } + + private string Decrypt(byte[]? clientSecret, byte[]? initializationVector, int? encryptionMode) + { + if (clientSecret == null) + { + throw new ConflictException("ClientSecret must not be null"); + } + + if (encryptionMode == null) + { + throw new ConflictException("EncryptionMode must not be null"); + } + + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == encryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {encryptionMode} is not configured"); + + return CryptoHelper.Decrypt(clientSecret, initializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + } +} diff --git a/src/processes/Processes.Worker/Program.cs b/src/processes/Processes.Worker/Program.cs index e6d7cab..55014bd 100644 --- a/src/processes/Processes.Worker/Program.cs +++ b/src/processes/Processes.Worker/Program.cs @@ -40,7 +40,8 @@ .AddTransient() .AddDatabase(hostContext.Configuration) .AddProcessExecutionService(hostContext.Configuration.GetSection("Processes")) - .AddDimProcessExecutor(hostContext.Configuration); + .AddDimProcessExecutor(hostContext.Configuration) + .AddTechnicalUserProcessExecutor(hostContext.Configuration); }) .AddLogging() .Build(); diff --git a/src/processes/Processes.Worker/appsettings.json b/src/processes/Processes.Worker/appsettings.json index 5850051..1e79cbc 100644 --- a/src/processes/Processes.Worker/appsettings.json +++ b/src/processes/Processes.Worker/appsettings.json @@ -21,7 +21,7 @@ } }, "ConnectionStrings": { - "DimDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;", + "DimDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" }, "Dim": { "AdminMail": "", @@ -52,5 +52,16 @@ "Scope": "", "TokenAddress": "", "BaseAddress": "" + }, + "TechnicalUserCreation": { + "EncryptionConfigIndex": 0, + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] } } diff --git a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs index 76ee48f..9bedd05 100644 --- a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs @@ -24,6 +24,7 @@ using Dim.DbAccess.Repositories; using Dim.Entities.Enums; using Dim.Web.ErrorHandling; +using Dim.Web.Models; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; @@ -112,4 +113,22 @@ public async Task CreateStatusList(string bpn, CancellationToken cancell var dimBaseUrl = dimDetails.Credentials.Url; return await _dimClient.CreateStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); } + + public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData, CancellationToken cancellationToken) + { + var (exists, tenantId) = await _dimRepositories.GetInstance().GetTenantForBpn(bpn).ConfigureAwait(false); + + if (!exists) + { + throw NotFoundException.Create(DimErrors.NO_COMPANY_FOR_BPN, new ErrorParameter[] { new("bpn", bpn) }); + } + + var processStepRepository = _dimRepositories.GetInstance(); + var processId = processStepRepository.CreateProcess(ProcessTypeId.CREATE_TECHNICAL_USER).Id; + processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessStepStatusId.TODO, processId); + + _dimRepositories.GetInstance().CreateTenantTechnicalUser(tenantId, technicalUserData.Name, technicalUserData.ExternalId, 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 3a07227..d503b65 100644 --- a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs @@ -17,6 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Web.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.DependencyInjection; namespace Dim.Web.BusinessLogic; @@ -26,4 +27,5 @@ public interface IDimBusinessLogic : ITransient Task StartSetupDim(string companyName, string bpn, string didDocumentLocation, bool isIssuer); Task GetStatusList(string bpn, CancellationToken cancellationToken); Task CreateStatusList(string bpn, CancellationToken cancellationToken); + Task CreateTechnicalUser(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 cd54d16..cf0117e 100644 --- a/src/web/Dim.Web/Controllers/DimController.cs +++ b/src/web/Dim.Web/Controllers/DimController.cs @@ -65,6 +65,13 @@ public static RouteGroupBuilder MapDimApi(this RouteGroupBuilder group) .RequireAuthorization(r => r.RequireRole("create_status_list")) .Produces(StatusCodes.Status200OK, responseType: typeof(string), contentType: Constants.JsonContentType); + policyHub.MapPost("technical-user/{bpn}", ([FromRoute] string bpn, [FromBody] TechnicalUserData technicalUserData, CancellationToken cancellationToken, [FromServices] IDimBusinessLogic dimBusinessLogic) => dimBusinessLogic.CreateTechnicalUser(bpn, technicalUserData, cancellationToken)) + .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")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + return group; } } diff --git a/src/web/Dim.Web/Models/TechnicalUserData.cs b/src/web/Dim.Web/Models/TechnicalUserData.cs new file mode 100644 index 0000000..aa58199 --- /dev/null +++ b/src/web/Dim.Web/Models/TechnicalUserData.cs @@ -0,0 +1,6 @@ +namespace Dim.Web.Models; + +public record TechnicalUserData( + Guid ExternalId, + string Name +); diff --git a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs index e2982cf..0119cf7 100644 --- a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs @@ -630,7 +630,7 @@ public async Task CreateServiceInstanceBindings_WithValidData_ReturnsExpected() var result = await _sut.CreateServiceInstanceBindings(_tenantName, _tenantId, CancellationToken.None); // Assert - A.CallTo(() => _cfClient.CreateServiceInstanceBindings(_tenantName, spaceId, A._)) + A.CallTo(() => _cfClient.CreateServiceInstanceBindings(_tenantName, null, spaceId, A._)) .MustHaveHappenedOnceExactly(); result.modified.Should().BeFalse();