mirror of
https://github.com/bitwarden/server.git
synced 2025-01-30 23:11:22 +01:00
[PM-12489] Extract OrganizationService.DeleteAsync and OrganizationService.InitiateDeleteAsync into commands (#5279)
* Create organization deletion command with logic extracted from OrganizationService * Add unit tests for OrganizationDeleteCommand * Register OrganizationDeleteCommand for dependency injection * Refactor organization deletion logic to use IOrganizationDeleteCommand and remove legacy IOrganizationService.DeleteAsync method * Add organization deletion initiation command and refactor service usage * Enhance organization deletion commands with detailed XML documentation * Refactor organization command registration to include sign-up and deletion methods
This commit is contained in:
parent
f2182c2aae
commit
3908edd08f
@ -5,6 +5,7 @@ using Bit.Admin.Services;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@ -56,6 +57,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationService organizationService,
|
||||
@ -82,7 +84,8 @@ public class OrganizationsController : Controller
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand,
|
||||
IProviderBillingService providerBillingService,
|
||||
IFeatureService featureService)
|
||||
IFeatureService featureService,
|
||||
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -109,6 +112,7 @@ public class OrganizationsController : Controller
|
||||
_removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand;
|
||||
_providerBillingService = providerBillingService;
|
||||
_featureService = featureService;
|
||||
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Org_List_View)]
|
||||
@ -319,7 +323,7 @@ public class OrganizationsController : Controller
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization != null)
|
||||
{
|
||||
await _organizationService.InitiateDeleteAsync(organization, model.AdminEmail);
|
||||
await _organizationInitiateDeleteCommand.InitiateDeleteAsync(organization, model.AdminEmail);
|
||||
TempData["Success"] = "The request to initiate deletion of the organization has been sent.";
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -58,6 +59,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -78,7 +80,8 @@ public class OrganizationsController : Controller
|
||||
IProviderBillingService providerBillingService,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand)
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||
IOrganizationDeleteCommand organizationDeleteCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -99,6 +102,7 @@ public class OrganizationsController : Controller
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
||||
_organizationDeleteCommand = organizationDeleteCommand;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -303,7 +307,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await _organizationService.DeleteAsync(organization);
|
||||
await _organizationDeleteCommand.DeleteAsync(organization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/delete-recover-token")]
|
||||
@ -333,7 +337,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await _organizationService.DeleteAsync(organization);
|
||||
await _organizationDeleteCommand.DeleteAsync(organization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/api-key")]
|
||||
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
|
||||
public interface IOrganizationDeleteCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Permanently deletes an organization and performs necessary cleanup.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization to delete.</param>
|
||||
/// <exception cref="BadRequestException">Thrown when the organization cannot be deleted due to configuration constraints.</exception>
|
||||
Task DeleteAsync(Organization organization);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
|
||||
public interface IOrganizationInitiateDeleteCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Initiates a secure deletion process for an organization by requesting confirmation from an organization admin.
|
||||
/// </summary>
|
||||
/// <param name="organization">The organization to be deleted.</param>
|
||||
/// <param name="orgAdminEmail">The email address of the organization admin who will confirm the deletion.</param>
|
||||
/// <exception cref="BadRequestException">Thrown when the specified admin email is invalid or lacks sufficient permissions.</exception>
|
||||
Task InitiateDeleteAsync(Organization organization, string orgAdminEmail);
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
public class OrganizationDeleteCommand : IOrganizationDeleteCommand
|
||||
{
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
|
||||
public OrganizationDeleteCommand(
|
||||
IApplicationCacheService applicationCacheService,
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IReferenceEventService referenceEventService,
|
||||
ISsoConfigRepository ssoConfigRepository)
|
||||
{
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_currentContext = currentContext;
|
||||
_organizationRepository = organizationRepository;
|
||||
_paymentService = paymentService;
|
||||
_referenceEventService = referenceEventService;
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Organization organization)
|
||||
{
|
||||
await ValidateDeleteOrganizationAsync(organization);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var eop = !organization.ExpirationDate.HasValue ||
|
||||
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext));
|
||||
}
|
||||
catch (GatewayException) { }
|
||||
}
|
||||
|
||||
await _organizationRepository.DeleteAsync(organization);
|
||||
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
private async Task ValidateDeleteOrganizationAsync(Organization organization)
|
||||
{
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector)
|
||||
{
|
||||
throw new BadRequestException("You cannot delete an Organization that is using Key Connector.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
public class OrganizationInitiateDeleteCommand : IOrganizationInitiateDeleteCommand
|
||||
{
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IMailService _mailService;
|
||||
|
||||
public const string OrganizationAdminNotFoundErrorMessage = "Org admin not found.";
|
||||
|
||||
public OrganizationInitiateDeleteCommand(
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IUserRepository userRepository,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IMailService mailService)
|
||||
{
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_userRepository = userRepository;
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
_mailService = mailService;
|
||||
}
|
||||
|
||||
public async Task InitiateDeleteAsync(Organization organization, string orgAdminEmail)
|
||||
{
|
||||
var orgAdmin = await _userRepository.GetByEmailAsync(orgAdminEmail);
|
||||
if (orgAdmin == null)
|
||||
{
|
||||
throw new BadRequestException(OrganizationAdminNotFoundErrorMessage);
|
||||
}
|
||||
var orgAdminOrgUser = await _organizationUserRepository.GetDetailsByUserAsync(orgAdmin.Id, organization.Id);
|
||||
if (orgAdminOrgUser == null || orgAdminOrgUser.Status is not OrganizationUserStatusType.Confirmed ||
|
||||
(orgAdminOrgUser.Type is not OrganizationUserType.Admin and not OrganizationUserType.Owner))
|
||||
{
|
||||
throw new BadRequestException(OrganizationAdminNotFoundErrorMessage);
|
||||
}
|
||||
var token = _orgDeleteTokenDataFactory.Protect(new OrgDeleteTokenable(organization, 1));
|
||||
await _mailService.SendInitiateDeleteOrganzationEmailAsync(orgAdminEmail, organization, token);
|
||||
}
|
||||
}
|
@ -28,8 +28,6 @@ public interface IOrganizationService
|
||||
/// </summary>
|
||||
Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(OrganizationLicense license, User owner,
|
||||
string ownerKey, string collectionName, string publicKey, string privateKey);
|
||||
Task InitiateDeleteAsync(Organization organization, string orgAdminEmail);
|
||||
Task DeleteAsync(Organization organization);
|
||||
Task EnableAsync(Guid organizationId, DateTime? expirationDate);
|
||||
Task DisableAsync(Guid organizationId, DateTime? expirationDate);
|
||||
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
||||
|
@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
@ -68,7 +67,6 @@ public class OrganizationService : IOrganizationService
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery;
|
||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
@ -106,7 +104,6 @@ public class OrganizationService : IOrganizationService
|
||||
IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory,
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IProviderRepository providerRepository,
|
||||
IFeatureService featureService,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
@ -139,7 +136,6 @@ public class OrganizationService : IOrganizationService
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
_providerRepository = providerRepository;
|
||||
_orgUserInviteTokenableFactory = orgUserInviteTokenableFactory;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
@ -690,44 +686,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InitiateDeleteAsync(Organization organization, string orgAdminEmail)
|
||||
{
|
||||
var orgAdmin = await _userRepository.GetByEmailAsync(orgAdminEmail);
|
||||
if (orgAdmin == null)
|
||||
{
|
||||
throw new BadRequestException("Org admin not found.");
|
||||
}
|
||||
var orgAdminOrgUser = await _organizationUserRepository.GetDetailsByUserAsync(orgAdmin.Id, organization.Id);
|
||||
if (orgAdminOrgUser == null || orgAdminOrgUser.Status != OrganizationUserStatusType.Confirmed ||
|
||||
(orgAdminOrgUser.Type != OrganizationUserType.Admin && orgAdminOrgUser.Type != OrganizationUserType.Owner))
|
||||
{
|
||||
throw new BadRequestException("Org admin not found.");
|
||||
}
|
||||
var token = _orgDeleteTokenDataFactory.Protect(new OrgDeleteTokenable(organization, 1));
|
||||
await _mailService.SendInitiateDeleteOrganzationEmailAsync(orgAdminEmail, organization, token);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Organization organization)
|
||||
{
|
||||
await ValidateDeleteOrganizationAsync(organization);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var eop = !organization.ExpirationDate.HasValue ||
|
||||
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext));
|
||||
}
|
||||
catch (GatewayException) { }
|
||||
}
|
||||
|
||||
await _organizationRepository.DeleteAsync(organization);
|
||||
await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
public async Task EnableAsync(Guid organizationId, DateTime? expirationDate)
|
||||
{
|
||||
var org = await GetOrgById(organizationId);
|
||||
@ -1978,15 +1936,6 @@ public class OrganizationService : IOrganizationService
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ValidateDeleteOrganizationAsync(Organization organization)
|
||||
{
|
||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector)
|
||||
{
|
||||
throw new BadRequestException("You cannot delete an Organization that is using Key Connector.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId)
|
||||
{
|
||||
if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value)
|
||||
|
@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfa
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
@ -52,6 +53,7 @@ public static class OrganizationServiceCollectionExtensions
|
||||
services.AddOrganizationLicenseCommandsQueries();
|
||||
services.AddOrganizationDomainCommandsQueries();
|
||||
services.AddOrganizationSignUpCommands();
|
||||
services.AddOrganizationDeleteCommands();
|
||||
services.AddOrganizationAuthCommands();
|
||||
services.AddOrganizationUserCommands();
|
||||
services.AddOrganizationUserCommandsQueries();
|
||||
@ -61,6 +63,12 @@ public static class OrganizationServiceCollectionExtensions
|
||||
private static IServiceCollection AddOrganizationSignUpCommands(this IServiceCollection services) =>
|
||||
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
||||
|
||||
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IOrganizationDeleteCommand, OrganizationDeleteCommand>();
|
||||
services.AddScoped<IOrganizationInitiateDeleteCommand, OrganizationInitiateDeleteCommand>();
|
||||
}
|
||||
|
||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICreateOrganizationConnectionCommand, CreateOrganizationConnectionCommand>();
|
||||
|
@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
@ -52,6 +53,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
private readonly OrganizationsController _sut;
|
||||
|
||||
public OrganizationsControllerTests()
|
||||
@ -75,6 +77,7 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||
_cloudOrganizationSignUpCommand = Substitute.For<ICloudOrganizationSignUpCommand>();
|
||||
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
|
||||
|
||||
_sut = new OrganizationsController(
|
||||
_organizationRepository,
|
||||
@ -95,7 +98,8 @@ public class OrganizationsControllerTests : IDisposable
|
||||
_providerBillingService,
|
||||
_orgDeleteTokenDataFactory,
|
||||
_removeOrganizationUserCommand,
|
||||
_cloudOrganizationSignUpCommand);
|
||||
_cloudOrganizationSignUpCommand,
|
||||
_organizationDeleteCommand);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -226,6 +230,6 @@ public class OrganizationsControllerTests : IDisposable
|
||||
await _providerBillingService.Received(1)
|
||||
.ScaleSeats(provider, organization.PlanType, -organization.Seats.Value);
|
||||
|
||||
await _organizationService.Received(1).DeleteAsync(organization);
|
||||
await _organizationDeleteCommand.Received(1).DeleteAsync(organization);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationDeleteCommandTests
|
||||
{
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task Delete_Success(Organization organization, SutProvider<OrganizationDeleteCommand> sutProvider)
|
||||
{
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organization);
|
||||
|
||||
await organizationRepository.Received().DeleteAsync(organization);
|
||||
await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider<OrganizationDeleteCommand> sutProvider,
|
||||
SsoConfig ssoConfig)
|
||||
{
|
||||
ssoConfig.Enabled = true;
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||
|
||||
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organization));
|
||||
|
||||
Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message);
|
||||
|
||||
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
|
||||
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationInitiateDeleteCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task InitiateDeleteAsync_ValidAdminUser_Success(OrganizationUserType organizationUserType,
|
||||
Organization organization, User orgAdmin, OrganizationUserOrganizationDetails orgAdminUser,
|
||||
string token, SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
|
||||
{
|
||||
orgAdminUser.Type = organizationUserType;
|
||||
orgAdminUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(orgAdmin.Email)
|
||||
.Returns(orgAdmin);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetDetailsByUserAsync(orgAdmin.Id, organization.Id)
|
||||
.Returns(orgAdminUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgDeleteTokenable>>()
|
||||
.Protect(Arg.Any<OrgDeleteTokenable>())
|
||||
.Returns(token);
|
||||
|
||||
await sutProvider.Sut.InitiateDeleteAsync(organization, orgAdmin.Email);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received(1)
|
||||
.SendInitiateDeleteOrganzationEmailAsync(orgAdmin.Email, organization, token);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task InitiateDeleteAsync_UserNotFound_ThrowsBadRequest(
|
||||
Organization organization, string email, SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns((User)null);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InitiateDeleteAsync(organization, email));
|
||||
|
||||
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task InitiateDeleteAsync_UserNotOrgAdmin_ThrowsBadRequest(OrganizationUserType organizationUserType,
|
||||
Organization organization, User user, OrganizationUserOrganizationDetails orgUser,
|
||||
SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
|
||||
{
|
||||
orgUser.Type = organizationUserType;
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(user.Email)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetDetailsByUserAsync(user.Id, organization.Id)
|
||||
.Returns(orgUser);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email));
|
||||
|
||||
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Invited)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
[BitAutoData(OrganizationUserStatusType.Accepted)]
|
||||
public async Task InitiateDeleteAsync_UserNotConfirmed_ThrowsBadRequest(
|
||||
OrganizationUserStatusType organizationUserStatusType,
|
||||
Organization organization, User user, OrganizationUserOrganizationDetails orgUser,
|
||||
SutProvider<OrganizationInitiateDeleteCommand> sutProvider)
|
||||
{
|
||||
orgUser.Type = OrganizationUserType.Admin;
|
||||
orgUser.Status = organizationUserStatusType;
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(user.Email)
|
||||
.Returns(user);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetDetailsByUserAsync(user.Id, organization.Id)
|
||||
.Returns(orgUser);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email));
|
||||
|
||||
Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message);
|
||||
}
|
||||
}
|
@ -6,9 +6,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@ -1427,39 +1425,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
Assert.Contains("Seat limit has been reached. Contact your provider to purchase additional seats.", failureMessage);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task Delete_Success(Organization organization, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organization);
|
||||
|
||||
await organizationRepository.Received().DeleteAsync(organization);
|
||||
await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id);
|
||||
}
|
||||
|
||||
[Theory, PaidOrganizationCustomize, BitAutoData]
|
||||
public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider<OrganizationService> sutProvider,
|
||||
SsoConfig ssoConfig)
|
||||
{
|
||||
ssoConfig.Enabled = true;
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
var ssoConfigRepository = sutProvider.GetDependency<ISsoConfigRepository>();
|
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||
|
||||
ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organization));
|
||||
|
||||
Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message);
|
||||
|
||||
await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default);
|
||||
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
|
||||
}
|
||||
|
||||
private void RestoreRevokeUser_Setup(
|
||||
Organization organization,
|
||||
OrganizationUser? requestingOrganizationUser,
|
||||
|
Loading…
Reference in New Issue
Block a user