mirror of
https://github.com/bitwarden/server.git
synced 2025-07-03 09:02:48 -05:00
[PM-7004] Org Admin Initiate Delete (#3905)
* org delete * move org id to URL path * tweaks * lint fixes * Update src/Core/Services/Implementations/HandlebarsMailService.cs Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * Update src/Core/Services/Implementations/HandlebarsMailService.cs Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * PR feedback * fix id * [PM-7004] Move OrgDeleteTokenable to AdminConsole ownership * [PM-7004] Add consolidated billing logic into organization delete request acceptance endpoint * [PM-7004] Delete unused IOrganizationService.DeleteAsync(Organization organization, string token) method * [PM-7004] Fix unit tests * [PM-7004] Update delete organization request email templates * Add success message when initiating organization deletion * Refactor OrganizationsController request delete initiation action to handle exceptions --------- Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Rui Tome <rtome@bitwarden.com>
This commit is contained in:
@ -0,0 +1,32 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
|
||||
public class OrgDeleteTokenable : Tokens.ExpiringTokenable
|
||||
{
|
||||
public const string ClearTextPrefix = "";
|
||||
public const string DataProtectorPurpose = "OrgDeleteDataProtector";
|
||||
public const string TokenIdentifier = "OrgDelete";
|
||||
public string Identifier { get; set; } = TokenIdentifier;
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonConstructor]
|
||||
public OrgDeleteTokenable(DateTime expirationDate)
|
||||
{
|
||||
ExpirationDate = expirationDate;
|
||||
}
|
||||
|
||||
public OrgDeleteTokenable(Organization organization, int hoursTillExpiration)
|
||||
{
|
||||
Id = organization.Id;
|
||||
ExpirationDate = DateTime.UtcNow.AddHours(hoursTillExpiration);
|
||||
}
|
||||
|
||||
public bool IsValid(Organization organization)
|
||||
{
|
||||
return Id == organization.Id;
|
||||
}
|
||||
|
||||
protected override bool TokenIsValid() => Identifier == TokenIdentifier && Id != default;
|
||||
}
|
@ -34,6 +34,7 @@ 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);
|
||||
|
@ -4,6 +4,7 @@ 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;
|
||||
@ -60,6 +61,7 @@ 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;
|
||||
@ -94,6 +96,7 @@ public class OrganizationService : IOrganizationService
|
||||
IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory,
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IProviderRepository providerRepository,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
@ -123,6 +126,7 @@ public class OrganizationService : IOrganizationService
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery;
|
||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
_providerRepository = providerRepository;
|
||||
_orgUserInviteTokenableFactory = orgUserInviteTokenableFactory;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
@ -811,6 +815,23 @@ 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);
|
||||
|
Reference in New Issue
Block a user