1
0
mirror of https://github.com/bitwarden/server.git synced 2025-03-14 13:49:30 +01:00

add bulk delete to IUserService

This commit is contained in:
Brandon 2024-11-20 14:07:35 -05:00
parent 9368004994
commit 1661d28a03
No known key found for this signature in database
GPG Key ID: A0E0EF0B207BA40D
5 changed files with 89 additions and 48 deletions

View File

@ -89,7 +89,6 @@ public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganiz
throw new NotFoundException("Member not found.");
}
await _userService.DeleteAsync(user);
results.Add((orgUserId, string.Empty));
}
catch (Exception ex)
@ -98,6 +97,7 @@ public class DeleteManagedOrganizationUserAccountCommand : IDeleteManagedOrganiz
}
}
await _userService.DeleteManyAsync(users);
await LogDeletedOrganizationUsersAsync(orgUsers, results);
return results;

View File

@ -8,6 +8,8 @@ using Bit.Core.Models.Business;
using Fido2NetLib;
using Microsoft.AspNetCore.Identity;
#nullable enable
namespace Bit.Core.Services;
public interface IUserService
@ -45,6 +47,7 @@ public interface IUserService
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
Task<IdentityResult> DeleteAsync(User user);
Task<IdentityResult> DeleteAsync(User user, string token);
Task<IEnumerable<(Guid UserId, string? ErrorMessage)>> DeleteManyAsync(IEnumerable<User> users);
Task SendDeleteConfirmationAsync(string email);
Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,

View File

@ -32,6 +32,8 @@ using Microsoft.Extensions.Options;
using File = System.IO.File;
using JsonSerializer = System.Text.Json.JsonSerializer;
#nullable enable
namespace Bit.Core.Services;
public class UserService : UserManager<User>, IUserService, IDisposable
@ -223,59 +225,39 @@ public class UserService : UserManager<User>, IUserService, IDisposable
public override async Task<IdentityResult> DeleteAsync(User user)
{
// Check if user is the only owner of any organizations.
var onlyOwnerCount = await _organizationUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerCount > 0)
var userList = await ValidateDeleteUsersAsync(new List<User> { user });
var results = userList.ToList()[0].ErrorMessage;
if (results == string.Empty || results == null)
{
var deletedOrg = false;
var orgs = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
if (orgs.Count == 1)
{
var org = await _organizationRepository.GetByIdAsync(orgs.First().OrganizationId);
if (org != null && (!org.Enabled || string.IsNullOrWhiteSpace(org.GatewaySubscriptionId)))
{
var orgCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(org.Id);
if (orgCount <= 1)
{
await _organizationRepository.DeleteAsync(org);
deletedOrg = true;
}
}
}
if (!deletedOrg)
{
return IdentityResult.Failed(new IdentityError
{
Description = "Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user.",
});
}
await _userRepository.DeleteAsync(user);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.DeleteAccount, user, _currentContext));
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
}
var onlyOwnerProviderCount = await _providerUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerProviderCount > 0)
else
{
return IdentityResult.Failed(new IdentityError
{
Description = "Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user.",
Description = results
});
}
}
if (!string.IsNullOrWhiteSpace(user.GatewaySubscriptionId))
public async Task<IEnumerable<(Guid UserId, string? ErrorMessage)>> DeleteManyAsync(IEnumerable<User> users)
{
var results = await ValidateDeleteUsersAsync(users.ToList());
await _userRepository.DeleteManyAsync(users);
foreach (var user in users)
{
try
{
await CancelPremiumAsync(user);
}
catch (GatewayException) { }
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.DeleteAccount, user, _currentContext));
await _pushService.PushLogOutAsync(user.Id);
}
await _userRepository.DeleteAsync(user);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.DeleteAccount, user, _currentContext));
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
return results;
}
public async Task<IdentityResult> DeleteAsync(User user, string token)
@ -288,6 +270,61 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return await DeleteAsync(user);
}
private async Task<IEnumerable<(Guid UserId, string? ErrorMessage)>> ValidateDeleteUsersAsync(List<User> users)
{
var userResult = new List<(Guid UserId, string? ErrorMessage)>();
foreach (var user in users)
{
// Check if user is the only owner of any organizations.
var onlyOwnerCount = await _organizationUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerCount > 0)
{
var deletedOrg = false;
var orgs = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
if (orgs.Count == 1)
{
var org = await _organizationRepository.GetByIdAsync(orgs.First().OrganizationId);
if (org != null && (!org.Enabled || string.IsNullOrWhiteSpace(org.GatewaySubscriptionId)))
{
var orgCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(org.Id);
if (orgCount <= 1)
{
await _organizationRepository.DeleteAsync(org);
deletedOrg = true;
}
}
}
if (!deletedOrg)
{
userResult.Add((user.Id, "Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user."));
continue;
}
}
var onlyOwnerProviderCount = await _providerUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerProviderCount > 0)
{
userResult.Add((user.Id, "Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user."));
continue;
}
if (!string.IsNullOrWhiteSpace(user.GatewaySubscriptionId))
{
try
{
await CancelPremiumAsync(user);
}
catch (GatewayException) { }
}
userResult.Add((user.Id, string.Empty));
}
return userResult;
}
public async Task SendDeleteConfirmationAsync(string email)
{
var user = await _userRepository.GetByEmailAsync(email);

View File

@ -167,7 +167,7 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
{
await connection.ExecuteAsync(
$"[{Schema}].[{Table}_DeleteById]",
new { Ids = new List<Guid> { user.Id }.ToGuidIdArrayTVP() },
new { Id = user.Id },
commandType: CommandType.StoredProcedure,
commandTimeout: 180);
}

View File

@ -258,14 +258,15 @@ public class DeleteManagedOrganizationUserAccountCommandTests
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, true }, { orgUser2.Id, true } });
// Act
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, new[] { orgUser1.Id, orgUser2.Id }, null);
var userIds = new[] { orgUser1.Id, orgUser2.Id };
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, userIds, null);
// Assert
Assert.Equal(2, results.Count());
Assert.All(results, r => Assert.Empty(r.Item2));
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user2);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyAsync(userIds);
await sutProvider.GetDependency<IUserService>().Received(1).DeleteManyAsync(Arg.Any<IEnumerable<User>>());
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1
@ -484,7 +485,7 @@ public class DeleteManagedOrganizationUserAccountCommandTests
Assert.Equal("You cannot delete a member with Invited status.", results.First(r => r.Item1 == orgUser2.Id).Item2);
Assert.Equal("Member is not managed by the organization.", results.First(r => r.Item1 == orgUser3.Id).Item2);
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
await sutProvider.GetDependency<IUserService>().Received(1).DeleteManyAsync(Arg.Any<IEnumerable<User>>());
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1));