diff --git a/charts/dim/templates/cronjob-processes.yaml b/charts/dim/templates/cronjob-processes.yaml index 53e04af..64f2588 100644 --- a/charts/dim/templates/cronjob-processes.yaml +++ b/charts/dim/templates/cronjob-processes.yaml @@ -116,6 +116,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 996d04b..3fdece7 100644 --- a/charts/dim/templates/secret.yaml +++ b/charts/dim/templates/secret.yaml @@ -38,11 +38,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 b1b82ed..bec58bf 100644 --- a/charts/dim/values.yaml +++ b/charts/dim/values.yaml @@ -107,6 +107,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 acfb6c9..56b8b40 100644 --- a/consortia/environments/values-dev.yaml +++ b/consortia/environments/values-dev.yaml @@ -86,7 +86,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 bd667bb..12f0278 100644 --- a/consortia/environments/values-int.yaml +++ b/consortia/environments/values-int.yaml @@ -77,7 +77,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 c77dc3a..789f984 100644 --- a/src/clients/Dim.Clients/Api/Cf/CfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/CfClient.cs @@ -209,13 +209,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 f11a4bf..2a08224 100644 --- a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs +++ b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs @@ -27,7 +27,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 2f6ea81..41e0918 100644 --- a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs @@ -36,4 +36,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 82abb60..6572ed7 100644 --- a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs @@ -106,4 +106,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 b480859..61df8bb 100644 --- a/src/database/Dim.Entities/DimDbContext.cs +++ b/src/database/Dim.Entities/DimDbContext.cs @@ -41,6 +41,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) { @@ -85,10 +86,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 b76dc64..048c4f3 100644 --- a/src/database/Dim.Entities/Entities/Process.cs +++ b/src/database/Dim.Entities/Entities/Process.cs @@ -30,6 +30,7 @@ private Process() { ProcessSteps = new HashSet(); Tenants = new HashSet(); + TechnicalUsers = new HashSet(); } public Process(Guid id, ProcessTypeId processTypeId, Guid version) : this() @@ -52,4 +53,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..5b64a6e --- /dev/null +++ b/src/database/Dim.Entities/Entities/TechnicalUser.cs @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +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 e26f96c..03053ec 100644 --- a/src/database/Dim.Entities/Entities/Tenant.cs +++ b/src/database/Dim.Entities/Entities/Tenant.cs @@ -20,28 +20,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; } @@ -57,6 +53,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 918e0c7..918ce57 100644 --- a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs @@ -40,5 +40,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 094f435..9a35238 100644 --- a/src/database/Dim.Entities/Enums/ProcessTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessTypeId.cs @@ -22,5 +22,6 @@ namespace Dim.Entities.Enums; public enum ProcessTypeId { - SETUP_DIM = 1 + 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..bfed16d --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.Designer.cs @@ -0,0 +1,565 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +// +using System; +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..12a002a --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20240409140733_1.0.1.cs @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using 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 b38f7fc..d803800 100644 --- a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs +++ b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -/******************************************************************************** +/******************************************************************************** * Copyright (c) 2024 BMW Group AG * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. * @@ -35,7 +35,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); @@ -267,6 +267,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" }); }); @@ -292,9 +307,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") @@ -418,6 +494,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") @@ -433,6 +528,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("ProcessSteps"); + b.Navigation("TechnicalUsers"); + b.Navigation("Tenants"); }); @@ -450,6 +547,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 ffc5171..78be5c0 100644 --- a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs +++ b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs @@ -31,4 +31,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 df7c612..b970ed6 100644 --- a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs @@ -28,11 +28,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, @@ -56,14 +56,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; @@ -71,7 +63,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"); @@ -98,41 +90,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..2f2b455 --- /dev/null +++ b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Dim.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 e1dff18..94314c9 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -28,20 +28,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, @@ -51,6 +45,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 48ac77c..f0a57d3 100644 --- a/src/processes/DimProcess.Library/Callback/ICallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/ICallbackService.cs @@ -26,4 +26,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 bb67cba..09ca0de 100644 --- a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs +++ b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs @@ -54,4 +54,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..2de34fc --- /dev/null +++ b/src/processes/DimProcess.Library/DependencyInjection/TechnicalUserSettings.cs @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using 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 f0d4e8b..63bae3f 100644 --- a/src/processes/DimProcess.Library/DimProcess.Library.csproj +++ b/src/processes/DimProcess.Library/DimProcess.Library.csproj @@ -32,6 +32,7 @@ + diff --git a/src/processes/DimProcess.Library/DimProcessHandler.cs b/src/processes/DimProcess.Library/DimProcessHandler.cs index 7453305..26c4286 100644 --- a/src/processes/DimProcess.Library/DimProcessHandler.cs +++ b/src/processes/DimProcess.Library/DimProcessHandler.cs @@ -36,42 +36,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) { @@ -84,8 +62,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; }, @@ -109,14 +87,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), @@ -133,13 +111,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), @@ -156,16 +134,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; }, @@ -188,7 +166,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."); @@ -199,10 +177,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; }, @@ -225,7 +203,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."); @@ -236,9 +214,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), @@ -256,7 +234,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."); @@ -267,9 +245,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?>( @@ -281,9 +259,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; }, @@ -301,13 +279,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), @@ -319,13 +297,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), @@ -336,9 +314,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), @@ -349,13 +327,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), @@ -366,15 +344,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; }, @@ -391,13 +369,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 { @@ -406,8 +384,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; }, @@ -424,13 +402,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 { @@ -439,9 +417,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; @@ -462,7 +440,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"); @@ -478,7 +456,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", @@ -486,10 +464,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; }, @@ -506,7 +484,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"); @@ -517,7 +495,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", @@ -525,7 +503,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), @@ -536,7 +514,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."); @@ -552,10 +530,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..3d8bc10 --- /dev/null +++ b/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Dim.Entities.Enums; + +namespace 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..3e71b33 --- /dev/null +++ b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs @@ -0,0 +1,141 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Dim.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 fade945..9e846ea 100644 --- a/src/processes/Processes.Worker/Program.cs +++ b/src/processes/Processes.Worker/Program.cs @@ -39,7 +39,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 5f5fa5c..055781a 100644 --- a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs @@ -25,6 +25,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; @@ -113,4 +114,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 6a5117f..9263959 100644 --- a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Web.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.DependencyInjection; namespace Dim.Web.BusinessLogic; @@ -27,4 +28,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 cda78fc..de3fdc8 100644 --- a/src/web/Dim.Web/Controllers/DimController.cs +++ b/src/web/Dim.Web/Controllers/DimController.cs @@ -66,6 +66,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..2aaceed --- /dev/null +++ b/src/web/Dim.Web/Models/TechnicalUserData.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +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 f6e03b7..c2ebb03 100644 --- a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs @@ -631,7 +631,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();