mirror of
https://github.com/bitwarden/server
synced 2025-12-26 21:23:39 +00: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:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user