1
0
mirror of https://github.com/bitwarden/server synced 2026-01-30 16:23:37 +00:00

feat: change revision dates for confirmed grantees; test: updated tests to check assert grantees revision dates are updated

This commit is contained in:
Ike Kottlowski
2026-01-29 16:09:53 -05:00
parent 81422f3960
commit bdfac87d2a
4 changed files with 79 additions and 21 deletions

View File

@@ -147,16 +147,23 @@ public class EmergencyAccessRepository : Repository<Core.Auth.Entities.Emergency
/// <inheritdoc />
public async Task DeleteManyAsync(ICollection<Guid> emergencyAccessIds)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var rangeToRemove = from ea in dbContext.EmergencyAccesses
where emergencyAccessIds.Contains(ea.Id)
select ea;
dbContext.EmergencyAccesses.RemoveRange(rangeToRemove);
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var rangeToRemove = from ea in dbContext.EmergencyAccesses
where emergencyAccessIds.Contains(ea.Id)
select ea;
dbContext.EmergencyAccesses.RemoveRange(rangeToRemove);
await dbContext.SaveChangesAsync();
}
var granteeIds = rangeToRemove
.Where(ea => ea.Status == EmergencyAccessStatusType.Confirmed)
.Where(ea => ea.GranteeId.HasValue)
.Select(ea => ea.GranteeId!.Value) // .Value is safe here due to the Where above
.Distinct();
await dbContext.UserBumpManyAccountRevisionDatesAsync(
[.. granteeIds]
);
await dbContext.SaveChangesAsync();
}
}

View File

@@ -1,9 +1,22 @@
CREATE PROCEDURE [dbo].[EmergencyAccess_DeleteManyById]
@EmergencyAccessIds [dbo].[GuidIdArray] READONLY
@EmergencyAccessIds [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
-- track GranteeIds for bumping revision date prior to deletion
DECLARE @GranteeIds AS TABLE (UserId UNIQUEIDENTIFIER)
-- this matches the logic in User_BumpAccountRevisionDateByEmergencyAccessGranteeId
INSERT INTO @GranteeIds
(UserId)
SELECT DISTINCT GranteeId
FROM
[dbo].[EmergencyAccess] EA
WHERE EA.Id IN (SELECT Id
FROM @EmergencyAccessIds
WHERE EA.[Status] = 2 )
DECLARE @BatchSize INT = 100
-- Delete EmergencyAccess Records
@@ -13,11 +26,17 @@ BEGIN
DELETE TOP(@BatchSize) EA
FROM
[dbo].[EmergencyAccess] EA
INNER JOIN
INNER JOIN
@EmergencyAccessIds EAI ON EAI.Id = EA.Id
SET @BatchSize = @@ROWCOUNT
END
-- Bump AccountRevisionDate for affected users after deletions
Exec [dbo].[User_BumpManyAccountRevisionDates]
(
SELECT [UserId]
FROM @GranteeIds
)
END
GO

View File

@@ -48,7 +48,7 @@ public class EmergencyAccessRepositoriesTests
/// All 3 records are then deleted in a single call to DeleteManyAsync.
/// </summary>
[DatabaseTheory, DatabaseData]
public async Task DeleteManyAsync_DeletesMultipleGranteeRecords(
public async Task DeleteManyAsync_DeletesMultipleGranteeRecords_UpdatesUserRevisionDates(
IUserRepository userRepository,
IEmergencyAccessRepository emergencyAccessRepository)
{
@@ -61,7 +61,7 @@ public class EmergencyAccessRepositoriesTests
SecurityStamp = "stamp",
});
var granteeUser1 = await userRepository.CreateAsync(new User
var confirmedGranteeUser1 = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User 1",
Email = $"test+grantee{Guid.NewGuid()}@email.com",
@@ -88,7 +88,7 @@ public class EmergencyAccessRepositoriesTests
var confirmedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
GranteeId = granteeUser1.Id,
GranteeId = confirmedGranteeUser1.Id,
Status = EmergencyAccessStatusType.Confirmed,
});
@@ -110,7 +110,20 @@ public class EmergencyAccessRepositoriesTests
await emergencyAccessRepository.DeleteManyAsync([confirmedEmergencyAccess.Id, invitedEmergencyAccess.Id, acceptedEmergencyAccess.Id]);
// Assert
var emergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
Assert.Empty(emergencyAccess);
// ensure Grantor records deleted
var grantorEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
Assert.Empty(grantorEmergencyAccess);
// ensure Grantee records deleted
foreach (User grantee in (List<User>)[confirmedGranteeUser1, granteeUser2, granteeUser3])
{
var granteeEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(grantee.Id);
Assert.Empty(granteeEmergencyAccess);
}
// Only the Status.Confirmed grantee's AccountRevisionDate should be updated
var updatedGrantee = await userRepository.GetByIdAsync(confirmedGranteeUser1.Id);
Assert.NotNull(updatedGrantee);
Assert.NotEqual(updatedGrantee.AccountRevisionDate, confirmedGranteeUser1.AccountRevisionDate);
}
}

View File

@@ -1,9 +1,22 @@
CREATE OR ALTER PROCEDURE [dbo].[EmergencyAccess_DeleteManyById]
@EmergencyAccessIds [dbo].[GuidIdArray] READONLY
@EmergencyAccessIds [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
-- track GranteeIds for bumping revision date prior to deletion
DECLARE @GranteeIds AS TABLE (UserId UNIQUEIDENTIFIER)
-- this matches the logic in User_BumpAccountRevisionDateByEmergencyAccessGranteeId
INSERT INTO @GranteeIds
(UserId)
SELECT DISTINCT GranteeId
FROM
[dbo].[EmergencyAccess] EA
WHERE EA.Id IN (SELECT Id
FROM @EmergencyAccessIds
WHERE EA.[Status] = 2 )
DECLARE @BatchSize INT = 100
-- Delete EmergencyAccess Records
@@ -13,11 +26,17 @@ BEGIN
DELETE TOP(@BatchSize) EA
FROM
[dbo].[EmergencyAccess] EA
INNER JOIN
INNER JOIN
@EmergencyAccessIds EAI ON EAI.Id = EA.Id
SET @BatchSize = @@ROWCOUNT
END
-- Bump AccountRevisionDate for affected users after deletions
Exec [dbo].[User_BumpManyAccountRevisionDates]
(
SELECT [UserId]
FROM @GranteeIds
)
END
GO