From fb5b9a3d59744576d0332d98984d9038023d8c04 Mon Sep 17 00:00:00 2001 From: Phil Schneider Date: Fri, 15 Dec 2023 16:42:44 +0100 Subject: [PATCH] fix(registration): delete idp on application decline (#371) * remove both idp-related entities in portal-db and corresponding entries in keycloak: * shared idps: remove both shared realm and corresponding idp plus companyAssignedIdentityProvider-entry * own idp: remove idp and companyAssignedIdentityProvider-entry * managed idp: only remove companyAssignedIdentityProvider-entry * support deletion of multiple idps --------- Refs: TEST-1642 Co-authored-by: Norbert Truchsess Reviewed-by: Norbert Truchsess --- .../RegistrationBusinessLogic.cs | 33 ++++- .../Repositories/ApplicationRepository.cs | 9 +- .../Repositories/IApplicationRepository.cs | 2 +- .../IIdentityProviderRepository.cs | 3 + .../Repositories/IUserRepository.cs | 2 +- .../IdentityProviderRepository.cs | 9 ++ .../Repositories/UserRepository.cs | 13 +- .../RegistrationBusinessLogicTest.cs | 117 +++++++++++++++--- 8 files changed, 163 insertions(+), 25 deletions(-) diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs index 458b784907..2beab67df1 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs @@ -31,8 +31,10 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Extensions; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Library; +using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library; using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.Models; using System.Text.RegularExpressions; @@ -49,6 +51,7 @@ public sealed class RegistrationBusinessLogic : IRegistrationBusinessLogic private readonly IApplicationChecklistService _checklistService; private readonly IClearinghouseBusinessLogic _clearinghouseBusinessLogic; private readonly ISdFactoryBusinessLogic _sdFactoryBusinessLogic; + private readonly IProvisioningManager _provisioningManager; private readonly ILogger _logger; public RegistrationBusinessLogic( @@ -58,6 +61,7 @@ public RegistrationBusinessLogic( IApplicationChecklistService checklistService, IClearinghouseBusinessLogic clearinghouseBusinessLogic, ISdFactoryBusinessLogic sdFactoryBusinessLogic, + IProvisioningManager provisioningManager, ILogger logger) { _portalRepositories = portalRepositories; @@ -66,6 +70,7 @@ public RegistrationBusinessLogic( _checklistService = checklistService; _clearinghouseBusinessLogic = clearinghouseBusinessLogic; _sdFactoryBusinessLogic = sdFactoryBusinessLogic; + _provisioningManager = provisioningManager; _logger = logger; } @@ -419,7 +424,7 @@ public async Task DeclineRegistrationVerification(Guid applicationId, string com throw new ArgumentException($"CompanyApplication {applicationId} is not in status SUBMITTED", nameof(applicationId)); } - var (companyId, companyName, processId) = result; + var (companyId, companyName, processId, idps, companyUserIds) = result; var context = await _checklistService .VerifyChecklistEntryAndProcessSteps( @@ -443,6 +448,22 @@ public async Task DeclineRegistrationVerification(Guid applicationId, string com }, null); + var identityProviderRepository = _portalRepositories.GetInstance(); + foreach (var (idpId, idpAlias, idpType) in idps) + { + if (idpType == IdentityProviderTypeId.SHARED) + { + await _provisioningManager.DeleteSharedIdpRealmAsync(idpAlias).ConfigureAwait(false); + } + identityProviderRepository.DeleteCompanyIdentityProvider(companyId, idpId); + if (idpType == IdentityProviderTypeId.OWN || idpType == IdentityProviderTypeId.SHARED) + { + await _provisioningManager.DeleteCentralIdentityProviderAsync(idpAlias).ConfigureAwait(false); + identityProviderRepository.DeleteIamIdentityProvider(idpAlias); + identityProviderRepository.DeleteIdentityProvider(idpId); + } + } + _portalRepositories.GetInstance().AttachAndModifyCompanyApplication(applicationId, application => { application.ApplicationStatusId = CompanyApplicationStatusId.DECLINED; @@ -453,6 +474,16 @@ public async Task DeclineRegistrationVerification(Guid applicationId, string com company.CompanyStatusId = CompanyStatusId.REJECTED; }); + foreach (var userId in companyUserIds) + { + var iamUserId = await _provisioningManager.GetUserByUserName(userId.ToString()).ConfigureAwait(false); + if (iamUserId != null) + { + await _provisioningManager.DeleteCentralRealmUserAsync(iamUserId).ConfigureAwait(false); + } + } + _portalRepositories.GetInstance().AttachAndModifyIdentities(companyUserIds.Select(userId => new ValueTuple>(userId, identity => { identity.UserStatusId = UserStatusId.DELETED; }))); + if (processId != null) { _portalRepositories.GetInstance().CreateProcessStepRange(Enumerable.Repeat(new ValueTuple(ProcessStepTypeId.TRIGGER_CALLBACK_OSP_DECLINED, ProcessStepStatusId.TODO, processId.Value), 1)); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs index 02a7a5aaed..f059d0c9d4 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ApplicationRepository.cs @@ -424,13 +424,16 @@ public IAsyncEnumerable GetSubmittedApplicationIdsByBpn(string bpn) => /// /// Id of the application /// Returns the company id - public Task<(Guid CompanyId, string CompanyName, Guid? NetworkRegistrationProcessId)> GetCompanyIdNameForSubmittedApplication(Guid applicationId) => + public Task<(Guid CompanyId, string CompanyName, Guid? NetworkRegistrationProcessId, IEnumerable<(Guid IdentityProviderId, string IamAlias, IdentityProviderTypeId TypeId)> Idps, IEnumerable CompanyUserIds)> GetCompanyIdNameForSubmittedApplication(Guid applicationId) => _dbContext.CompanyApplications + .AsSplitQuery() .Where(x => x.Id == applicationId && x.ApplicationStatusId == CompanyApplicationStatusId.SUBMITTED) - .Select(x => new ValueTuple( + .Select(x => new ValueTuple, IEnumerable>( x.CompanyId, x.Company!.Name, - x.Company!.NetworkRegistration!.ProcessId)) + x.Company.NetworkRegistration!.ProcessId, + x.Company.IdentityProviders.Select(idp => new ValueTuple(idp.Id, idp.IamIdentityProvider!.IamIdpAlias, idp.IdentityProviderTypeId)), + x.Company.Identities.Where(i => i.IdentityTypeId == IdentityTypeId.COMPANY_USER && i.UserStatusId != UserStatusId.DELETED).Select(i => i.Id))) .SingleOrDefaultAsync(); public Task IsValidApplicationForCompany(Guid applicationId, Guid companyId) => diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs index 6e2d755512..7de555ca10 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IApplicationRepository.cs @@ -96,7 +96,7 @@ public interface IApplicationRepository /// /// Id of the application /// The id of the company for the given application - Task<(Guid CompanyId, string CompanyName, Guid? NetworkRegistrationProcessId)> GetCompanyIdNameForSubmittedApplication(Guid applicationId); + Task<(Guid CompanyId, string CompanyName, Guid? NetworkRegistrationProcessId, IEnumerable<(Guid IdentityProviderId, string IamAlias, IdentityProviderTypeId TypeId)> Idps, IEnumerable CompanyUserIds)> GetCompanyIdNameForSubmittedApplication(Guid applicationId); Task IsValidApplicationForCompany(Guid applicationId, Guid companyId); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs index 9fbf402395..39d230fcd9 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IIdentityProviderRepository.cs @@ -29,8 +29,11 @@ namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositorie public interface IIdentityProviderRepository { IdentityProvider CreateIdentityProvider(IdentityProviderCategoryId identityProviderCategory, IdentityProviderTypeId identityProviderTypeId, Guid owner, Action? setOptionalFields); + void DeleteIdentityProvider(Guid identityProviderId); IamIdentityProvider CreateIamIdentityProvider(Guid identityProviderId, string idpAlias); + void DeleteIamIdentityProvider(string idpAlias); CompanyIdentityProvider CreateCompanyIdentityProvider(Guid companyId, Guid identityProviderId); + void DeleteCompanyIdentityProvider(Guid companyId, Guid identityProviderId); void CreateCompanyIdentityProviders(IEnumerable<(Guid CompanyId, Guid IdentityProviderId)> companyIdIdentityProviderIds); Task GetSharedIdentityProviderIamAliasDataUntrackedAsync(Guid companyId); Task<(string? Alias, bool IsValidUser)> GetIdpCategoryIdByUserIdAsync(Guid companyUserId, Guid userCompanyId); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs index 39bd2b3f56..00daabde15 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs @@ -21,7 +21,6 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; -using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; @@ -123,4 +122,5 @@ public interface IUserRepository Identity AttachAndModifyIdentity(Guid identityId, Action? initialize, Action modify); CompanyUserAssignedIdentityProvider AddCompanyUserAssignedIdentityProvider(Guid companyUserId, Guid identityProviderId, string providerId, string userName); IAsyncEnumerable GetUserAssignedIdentityProviderForNetworkRegistration(Guid networkRegistrationId); + void AttachAndModifyIdentities(IEnumerable<(Guid IdentityId, Action Modify)> identityData); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs index e6d03f36e4..98be8c920c 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IdentityProviderRepository.cs @@ -53,6 +53,9 @@ public IdentityProvider CreateIdentityProvider(IdentityProviderCategoryId identi .Add(idp).Entity; } + public void DeleteIdentityProvider(Guid identityProviderId) => + _context.IdentityProviders.Remove(new IdentityProvider(identityProviderId, default, default, Guid.Empty, default)); + public CompanyIdentityProvider CreateCompanyIdentityProvider(Guid companyId, Guid identityProviderId) => _context.CompanyIdentityProviders .Add(new CompanyIdentityProvider( @@ -60,6 +63,9 @@ public CompanyIdentityProvider CreateCompanyIdentityProvider(Guid companyId, Gui identityProviderId )).Entity; + public void DeleteCompanyIdentityProvider(Guid companyId, Guid identityProviderId) => + _context.Remove(new CompanyIdentityProvider(companyId, identityProviderId)); + public void CreateCompanyIdentityProviders(IEnumerable<(Guid CompanyId, Guid IdentityProviderId)> companyIdIdentityProviderIds) => _context.CompanyIdentityProviders .AddRange(companyIdIdentityProviderIds.Select(x => new CompanyIdentityProvider( @@ -74,6 +80,9 @@ public IamIdentityProvider CreateIamIdentityProvider(Guid identityProviderId, st idpAlias, identityProviderId)).Entity; + public void DeleteIamIdentityProvider(string idpAlias) => + _context.IamIdentityProviders.Remove(new IamIdentityProvider(idpAlias, Guid.Empty)); + public Task GetSharedIdentityProviderIamAliasDataUntrackedAsync(Guid companyId) => _context.IdentityProviders .AsNoTracking() diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs index 589713e04a..db05337950 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs @@ -24,7 +24,6 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; -using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; @@ -446,4 +445,16 @@ public IAsyncEnumerable GetUserAssignedI cu.CompanyUserAssignedIdentityProviders.Select(assigned => new ProviderLinkData(assigned.UserName, assigned.IdentityProvider!.IamIdentityProvider!.IamIdpAlias, assigned.ProviderId)) )) .ToAsyncEnumerable(); + + public void AttachAndModifyIdentities(IEnumerable<(Guid IdentityId, Action Modify)> identityData) + { + var initial = identityData.Select(x => + { + var identity = new Identity(x.IdentityId, default, Guid.Empty, default, default); + return (Identity: identity, x.Modify); + } + ).ToList(); + _dbContext.AttachRange(initial.Select(x => x.Identity)); + initial.ForEach(x => x.Modify(x.Identity)); + } } diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs index 8d3c875bd0..7ee6ac3633 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs @@ -33,6 +33,7 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Library; +using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library; using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared; @@ -46,6 +47,8 @@ public class RegistrationBusinessLogicTest private const string AlreadyTakenBpn = "BPNL123698762666"; private const string ValidBpn = "BPNL123698762345"; private const string CompanyName = "TestCompany"; + private const string IamAliasId = "idp1"; + private static readonly Guid IdpId = Guid.NewGuid(); private static readonly Guid IdWithBpn = new("c244f79a-7faf-4c59-bb85-fbfdf72ce46f"); private static readonly Guid NotExistingApplicationId = new("9f0cfd0d-c512-438e-a07e-3198bce873bf"); private static readonly Guid ActiveApplicationCompanyId = new("045abf01-7762-468b-98fb-84a30c39b7c7"); @@ -53,10 +56,11 @@ public class RegistrationBusinessLogicTest private static readonly Guid ExistingExternalId = Guid.NewGuid(); private static readonly Guid IdWithoutBpn = new("d90995fe-1241-4b8d-9f5c-f3909acc6399"); private static readonly Guid ApplicationId = new("6084d6e0-0e01-413c-850d-9f944a6c494c"); + private static readonly Guid UserId = Guid.NewGuid(); private readonly IPortalRepositories _portalRepositories; private readonly IApplicationRepository _applicationRepository; - private readonly IApplicationChecklistRepository _applicationChecklistRepository; + private readonly IIdentityProviderRepository _identityProviderRepository; private readonly IProcessStepRepository _processStepRepository; private readonly IUserRepository _userRepository; private readonly IFixture _fixture; @@ -67,6 +71,7 @@ public class RegistrationBusinessLogicTest private readonly ISdFactoryBusinessLogic _sdFactoryBusinessLogic; private readonly IMailingService _mailingService; private readonly IDocumentRepository _documentRepository; + private readonly IProvisioningManager _provisioningManager; public RegistrationBusinessLogicTest() { @@ -77,31 +82,33 @@ public RegistrationBusinessLogicTest() _portalRepositories = A.Fake(); _applicationRepository = A.Fake(); - _applicationChecklistRepository = A.Fake(); + _identityProviderRepository = A.Fake(); _documentRepository = A.Fake(); _processStepRepository = A.Fake(); _userRepository = A.Fake(); _companyRepository = A.Fake(); var options = A.Fake>(); + var settings = A.Fake(); + settings.ApplicationsMaxPageSize = 15; + A.CallTo(() => options.Value).Returns(settings); + _clearinghouseBusinessLogic = A.Fake(); _sdFactoryBusinessLogic = A.Fake(); _checklistService = A.Fake(); _mailingService = A.Fake(); - var settings = A.Fake(); - settings.ApplicationsMaxPageSize = 15; + _provisioningManager = A.Fake(); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_applicationRepository); - A.CallTo(() => _portalRepositories.GetInstance()).Returns(_applicationChecklistRepository); + A.CallTo(() => _portalRepositories.GetInstance()).Returns(_identityProviderRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_documentRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_processStepRepository); - A.CallTo(() => options.Value).Returns(settings); var logger = A.Fake>(); - _logic = new RegistrationBusinessLogic(_portalRepositories, options, _mailingService, _checklistService, _clearinghouseBusinessLogic, _sdFactoryBusinessLogic, logger); + _logic = new RegistrationBusinessLogic(_portalRepositories, options, _mailingService, _checklistService, _clearinghouseBusinessLogic, _sdFactoryBusinessLogic, _provisioningManager, logger); } #region GetCompanyApplicationDetailsAsync @@ -446,9 +453,11 @@ public async Task SetRegistrationVerification_WithBpnNotDone_CallsExpected() } [Theory] - [InlineData(ApplicationChecklistEntryStatusId.TO_DO)] - [InlineData(ApplicationChecklistEntryStatusId.DONE)] - public async Task DeclineRegistrationVerification_WithDecline_StateAndCommentSetCorrectly(ApplicationChecklistEntryStatusId checklistStatusId) + [InlineData(ApplicationChecklistEntryStatusId.TO_DO, IdentityProviderTypeId.SHARED)] + [InlineData(ApplicationChecklistEntryStatusId.DONE, IdentityProviderTypeId.SHARED)] + [InlineData(ApplicationChecklistEntryStatusId.TO_DO, IdentityProviderTypeId.OWN)] + [InlineData(ApplicationChecklistEntryStatusId.DONE, IdentityProviderTypeId.OWN)] + public async Task DeclineRegistrationVerification_WithDecline_StateAndCommentSetCorrectly(ApplicationChecklistEntryStatusId checklistStatusId, IdentityProviderTypeId idpTypeId) { // Arrange const string comment = "application rejected because of reasons."; @@ -456,7 +465,7 @@ public async Task DeclineRegistrationVerification_WithDecline_StateAndCommentSet var entry = new ApplicationChecklistEntry(IdWithBpn, ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, checklistStatusId, DateTimeOffset.UtcNow); var company = new Company(CompanyId, null!, CompanyStatusId.PENDING, DateTimeOffset.UtcNow); var application = new CompanyApplication(ApplicationId, company.Id, CompanyApplicationStatusId.SUBMITTED, CompanyApplicationTypeId.INTERNAL, DateTimeOffset.UtcNow); - SetupForDeclineRegistrationVerification(entry, application, company, checklistStatusId); + SetupForDeclineRegistrationVerification(entry, application, company, checklistStatusId, idpTypeId); A.CallTo(() => _processStepRepository.CreateProcessStepRange(A>._)) .Invokes((IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> processStepData) => { @@ -479,6 +488,78 @@ public async Task DeclineRegistrationVerification_WithDecline_StateAndCommentSet processSteps.Should().ContainSingle().Which.Should().Match(x => x.ProcessStepStatusId == ProcessStepStatusId.TODO && x.ProcessStepTypeId == ProcessStepTypeId.TRIGGER_CALLBACK_OSP_DECLINED); + if (idpTypeId == IdentityProviderTypeId.SHARED) + { + A.CallTo(() => _provisioningManager.DeleteSharedIdpRealmAsync(IamAliasId)) + .MustHaveHappenedOnceExactly(); + } + A.CallTo(() => _provisioningManager.DeleteCentralIdentityProviderAsync(IamAliasId)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _provisioningManager.DeleteCentralRealmUserAsync("user123")) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task DeclineRegistrationVerification_WithApplicationNotFound_ThrowsArgumentException() + { + // Arrange + var applicationId = Guid.NewGuid(); + A.CallTo(() => _applicationRepository.GetCompanyIdNameForSubmittedApplication(applicationId)) + .Returns(default((Guid, string, Guid?, IEnumerable<(Guid, string, IdentityProviderTypeId)>, IEnumerable))); + async Task Act() => await _logic.DeclineRegistrationVerification(applicationId, "test", CancellationToken.None).ConfigureAwait(false); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be($"CompanyApplication {applicationId} is not in status SUBMITTED (Parameter 'applicationId')"); + ex.ParamName.Should().Be("applicationId"); + } + + [Fact] + public async Task DeclineRegistrationVerification_WithMultipleIdps_CallsExpected() + { + // Arrange + var applicationId = Guid.NewGuid(); + var companyId = Guid.NewGuid(); + var sharedIdpId = Guid.NewGuid(); + var managedIdpId = Guid.NewGuid(); + var ownIdpId = Guid.NewGuid(); + + A.CallTo(() => _applicationRepository.GetCompanyIdNameForSubmittedApplication(applicationId)) + .Returns(( + companyId, + "test", + null, + new[] + { + (sharedIdpId, "idp1", IdentityProviderTypeId.SHARED), + (managedIdpId, "idp2", IdentityProviderTypeId.MANAGED), + (ownIdpId, "idp3", IdentityProviderTypeId.OWN), + }, + Enumerable.Empty())); + + // Act + await _logic.DeclineRegistrationVerification(applicationId, "test", CancellationToken.None).ConfigureAwait(false); + + // Assert + A.CallTo(() => _identityProviderRepository.DeleteCompanyIdentityProvider(companyId, sharedIdpId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _identityProviderRepository.DeleteIamIdentityProvider("idp1")).MustHaveHappenedOnceExactly(); + A.CallTo(() => _identityProviderRepository.DeleteIdentityProvider(sharedIdpId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _provisioningManager.DeleteSharedIdpRealmAsync("idp1")).MustHaveHappenedOnceExactly(); + A.CallTo(() => _provisioningManager.DeleteCentralIdentityProviderAsync("idp1")).MustHaveHappenedOnceExactly(); + + A.CallTo(() => _identityProviderRepository.DeleteCompanyIdentityProvider(companyId, sharedIdpId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _identityProviderRepository.DeleteIamIdentityProvider("idp2")).MustNotHaveHappened(); + A.CallTo(() => _identityProviderRepository.DeleteIdentityProvider(managedIdpId)).MustNotHaveHappened(); + A.CallTo(() => _provisioningManager.DeleteSharedIdpRealmAsync("idp2")).MustNotHaveHappened(); + A.CallTo(() => _provisioningManager.DeleteCentralIdentityProviderAsync("idp2")).MustNotHaveHappened(); + + A.CallTo(() => _identityProviderRepository.DeleteCompanyIdentityProvider(companyId, ownIdpId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _identityProviderRepository.DeleteIamIdentityProvider("idp3")).MustHaveHappenedOnceExactly(); + A.CallTo(() => _identityProviderRepository.DeleteIdentityProvider(ownIdpId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _provisioningManager.DeleteSharedIdpRealmAsync("idp3")).MustNotHaveHappened(); + A.CallTo(() => _provisioningManager.DeleteCentralIdentityProviderAsync("idp3")).MustHaveHappenedOnceExactly(); } #endregion @@ -808,15 +889,12 @@ private void SetupForApproveRegistrationVerification(ApplicationChecklistEntry a { { ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, new ValueTuple(ApplicationChecklistEntryStatusId.DONE, null) } }.ToImmutableDictionary(), Enumerable.Empty())); - - A.CallTo(() => _applicationRepository.GetCompanyIdNameForSubmittedApplication(IdWithBpn)) - .Returns((CompanyId, CompanyName, ExistingExternalId)); } - private void SetupForDeclineRegistrationVerification(ApplicationChecklistEntry applicationChecklistEntry, CompanyApplication application, Company company, ApplicationChecklistEntryStatusId checklistStatusId) + private void SetupForDeclineRegistrationVerification(ApplicationChecklistEntry applicationChecklistEntry, CompanyApplication application, Company company, ApplicationChecklistEntryStatusId checklistStatusId, IdentityProviderTypeId idpTypeId) { A.CallTo(() => _checklistService.FinalizeChecklistEntryAndProcessSteps(A._, A>._, A>._, A?>._)) - .Invokes((IApplicationChecklistService.ManualChecklistProcessStepData _, Action? initail, Action action, IEnumerable? _) => + .Invokes((IApplicationChecklistService.ManualChecklistProcessStepData _, Action? _, Action action, IEnumerable? _) => { action.Invoke(applicationChecklistEntry); }); @@ -833,10 +911,13 @@ private void SetupForDeclineRegistrationVerification(ApplicationChecklistEntry a }.ToImmutableDictionary(), Enumerable.Empty())); A.CallTo(() => _applicationRepository.GetCompanyIdNameForSubmittedApplication(IdWithBpn)) - .Returns((CompanyId, CompanyName, ExistingExternalId)); + .Returns((CompanyId, CompanyName, ExistingExternalId, Enumerable.Repeat((IdpId, IamAliasId, idpTypeId), 1), Enumerable.Repeat(UserId, 1))); + + A.CallTo(() => _provisioningManager.GetUserByUserName(UserId.ToString())) + .Returns("user123"); A.CallTo(() => _checklistService.FinalizeChecklistEntryAndProcessSteps(A._, A>._, A>._, A?>._)) - .Invokes((IApplicationChecklistService.ManualChecklistProcessStepData _, Action initial, Action action, IEnumerable? _) => + .Invokes((IApplicationChecklistService.ManualChecklistProcessStepData _, Action _, Action action, IEnumerable? _) => { action.Invoke(applicationChecklistEntry); });