mirror of
https://github.com/bitwarden/server.git
synced 2025-02-02 23:41:21 +01:00
Add DeleteManyAsync method and stored procedure
This commit is contained in:
parent
702a81b161
commit
97b3be26f0
@ -32,4 +32,5 @@ public interface IUserRepository : IRepository<User, Guid>
|
||||
/// <param name="updateDataActions">Registered database calls to update re-encrypted data.</param>
|
||||
Task UpdateUserKeyAndEncryptedDataAsync(User user,
|
||||
IEnumerable<UpdateEncryptedDataForKeyRotation> updateDataActions);
|
||||
Task DeleteManyAsync(IEnumerable<User> users);
|
||||
}
|
||||
|
@ -172,6 +172,18 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
|
||||
commandTimeout: 180);
|
||||
}
|
||||
}
|
||||
public async Task DeleteManyAsync(IEnumerable<User> users)
|
||||
{
|
||||
var list = users.Select(user => user.Id);
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
$"[{Schema}].[{Table}_DeleteByIds]",
|
||||
new { Ids = list.ToGuidIdArrayTVP() },
|
||||
commandType: CommandType.StoredProcedure,
|
||||
commandTimeout: 180);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateStorageAsync(Guid id)
|
||||
{
|
||||
|
@ -261,6 +261,54 @@ public class UserRepository : Repository<Core.Entities.User, User, Guid>, IUserR
|
||||
var mappedUser = Mapper.Map<User>(user);
|
||||
dbContext.Users.Remove(mappedUser);
|
||||
|
||||
await transaction.CommitAsync();
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManyAsync(IEnumerable<Core.Entities.User> users)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var transaction = await dbContext.Database.BeginTransactionAsync();
|
||||
|
||||
dbContext.WebAuthnCredentials.RemoveRange(dbContext.WebAuthnCredentials.Where(w => users.Any(u => u.Id == w.UserId)));
|
||||
dbContext.Ciphers.RemoveRange(dbContext.Ciphers.Where(c => users.Any(u => u.Id == c.UserId)));
|
||||
dbContext.Folders.RemoveRange(dbContext.Folders.Where(f => users.Any(u => u.Id == f.UserId)));
|
||||
dbContext.AuthRequests.RemoveRange(dbContext.AuthRequests.Where(s => users.Any(u => u.Id == s.UserId)));
|
||||
dbContext.Devices.RemoveRange(dbContext.Devices.Where(d => users.Any(u => u.Id == d.UserId)));
|
||||
var collectionUsers = from cu in dbContext.CollectionUsers
|
||||
join ou in dbContext.OrganizationUsers on cu.OrganizationUserId equals ou.Id
|
||||
where users.Any(u => u.Id == ou.UserId)
|
||||
select cu;
|
||||
dbContext.CollectionUsers.RemoveRange(collectionUsers);
|
||||
var groupUsers = from gu in dbContext.GroupUsers
|
||||
join ou in dbContext.OrganizationUsers on gu.OrganizationUserId equals ou.Id
|
||||
where users.Any(u => u.Id == ou.UserId)
|
||||
select gu;
|
||||
dbContext.GroupUsers.RemoveRange(groupUsers);
|
||||
dbContext.UserProjectAccessPolicy.RemoveRange(
|
||||
dbContext.UserProjectAccessPolicy.Where(ap => users.Any(u => u.Id == ap.OrganizationUser.UserId)));
|
||||
dbContext.UserServiceAccountAccessPolicy.RemoveRange(
|
||||
dbContext.UserServiceAccountAccessPolicy.Where(ap => users.Any(u => u.Id == ap.OrganizationUser.UserId)));
|
||||
dbContext.OrganizationUsers.RemoveRange(dbContext.OrganizationUsers.Where(ou => users.Any(u => u.Id == ou.UserId)));
|
||||
dbContext.ProviderUsers.RemoveRange(dbContext.ProviderUsers.Where(pu => users.Any(u => u.Id == pu.UserId)));
|
||||
dbContext.SsoUsers.RemoveRange(dbContext.SsoUsers.Where(su => users.Any(u => u.Id == su.UserId)));
|
||||
dbContext.EmergencyAccesses.RemoveRange(
|
||||
dbContext.EmergencyAccesses.Where(ea => users.Any(u => u.Id == ea.GrantorId || u.Id == ea.GranteeId)));
|
||||
dbContext.Sends.RemoveRange(dbContext.Sends.Where(s => users.Any(u => u.Id == s.UserId)));
|
||||
dbContext.NotificationStatuses.RemoveRange(dbContext.NotificationStatuses.Where(ns => users.Any(u => u.Id == ns.UserId)));
|
||||
dbContext.Notifications.RemoveRange(dbContext.Notifications.Where(n => users.Any(u => u.Id == n.UserId)));
|
||||
|
||||
foreach (User u in users)
|
||||
{
|
||||
var mappedUser = Mapper.Map<User>(u);
|
||||
dbContext.Users.Remove(mappedUser);
|
||||
}
|
||||
|
||||
|
||||
await transaction.CommitAsync();
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
144
src/Sql/dbo/Stored Procedures/User_DeleteByIds.sql
Normal file
144
src/Sql/dbo/Stored Procedures/User_DeleteByIds.sql
Normal file
@ -0,0 +1,144 @@
|
||||
CREATE PROCEDURE [dbo].[User_DeleteByIds]
|
||||
@Ids [dbo].[GuidIdArray]
|
||||
WITH RECOMPILE
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
DECLARE @BatchSize INT = 100
|
||||
|
||||
-- Delete ciphers
|
||||
WHILE @BatchSize > 0
|
||||
BEGIN
|
||||
BEGIN TRANSACTION User_DeleteById_Ciphers
|
||||
|
||||
DELETE TOP(@BatchSize)
|
||||
FROM
|
||||
[dbo].[Cipher]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
SET @BatchSize = @@ROWCOUNT
|
||||
|
||||
COMMIT TRANSACTION User_DeleteById_Ciphers
|
||||
END
|
||||
|
||||
BEGIN TRANSACTION User_DeleteById
|
||||
|
||||
-- Delete WebAuthnCredentials
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[WebAuthnCredential]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete folders
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Folder]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete AuthRequest, must be before Device
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[AuthRequest]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete devices
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Device]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete collection users
|
||||
DELETE
|
||||
CU
|
||||
FROM
|
||||
[dbo].[CollectionUser] CU
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUser] OU ON OU.[Id] = CU.[OrganizationUserId]
|
||||
WHERE
|
||||
OU.[UserId] IN (@Ids)
|
||||
|
||||
-- Delete group users
|
||||
DELETE
|
||||
GU
|
||||
FROM
|
||||
[dbo].[GroupUser] GU
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUser] OU ON OU.[Id] = GU.[OrganizationUserId]
|
||||
WHERE
|
||||
OU.[UserId] IN (@Ids)
|
||||
|
||||
-- Delete AccessPolicy
|
||||
DELETE
|
||||
AP
|
||||
FROM
|
||||
[dbo].[AccessPolicy] AP
|
||||
INNER JOIN
|
||||
[dbo].[OrganizationUser] OU ON OU.[Id] = AP.[OrganizationUserId]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete organization users
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[OrganizationUser]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete provider users
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[ProviderUser]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete SSO Users
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[SsoUser]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete Emergency Accesses
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[EmergencyAccess]
|
||||
WHERE
|
||||
[GrantorId] = @Id
|
||||
OR
|
||||
[GranteeId] = @Id
|
||||
|
||||
-- Delete Sends
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Send]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete Notification Status
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[NotificationStatus]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Delete Notification
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[Notification]
|
||||
WHERE
|
||||
[UserId] IN (@Ids)
|
||||
|
||||
-- Finally, delete the user
|
||||
DELETE
|
||||
FROM
|
||||
[dbo].[User]
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
|
||||
COMMIT TRANSACTION User_DeleteById
|
||||
END
|
@ -92,6 +92,47 @@ public class UserRepositoryTests
|
||||
Assert.True(savedSqlUser == null);
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfUserAutoData]
|
||||
public async Task DeleteManyAsync_Works_DataMatches(IEnumerable<User> users, List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo)
|
||||
{
|
||||
foreach (var sut in suts)
|
||||
{
|
||||
foreach (var user in users)
|
||||
{
|
||||
var postEfUser = await sut.CreateAsync(user);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
var savedEfUser = await sut.GetByIdAsync(postEfUser.Id);
|
||||
Assert.True(savedEfUser != null);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
await sut.DeleteAsync(savedEfUser);
|
||||
sut.ClearChangeTracking();
|
||||
|
||||
savedEfUser = await sut.GetByIdAsync(savedEfUser.Id);
|
||||
Assert.True(savedEfUser == null);
|
||||
}
|
||||
}
|
||||
List<User> userList = new List<User>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
var postSqlUser = await sqlUserRepo.CreateAsync(user);
|
||||
var savedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id);
|
||||
Assert.True(savedSqlUser != null);
|
||||
userList.Add(postSqlUser);
|
||||
|
||||
}
|
||||
await sqlUserRepo.DeleteManyAsync(userList);
|
||||
|
||||
foreach (var user in userList)
|
||||
{
|
||||
var savedSqlUser = await sqlUserRepo.GetByIdAsync(user.Id);
|
||||
Assert.True(savedSqlUser == null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[CiSkippedTheory, EfUserAutoData]
|
||||
public async Task GetByEmailAsync_Works_DataMatches(User user, UserCompare equalityComparer,
|
||||
List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo)
|
||||
|
Loading…
Reference in New Issue
Block a user