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:
parent
9368004994
commit
1661d28a03
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user