1
0
mirror of https://github.com/bitwarden/server synced 2026-02-11 05:53:47 +00:00

[PM-26376] Emergency Access Delete Command (#6857)

* feat: Add initial DeleteEmergencyContactCommand

* chore: remove nullable enable and add comments

* test: add tests for new delete command

* test: update tests to test IMailer was called.

* feat: add delete by GranteeId and allow for multiple grantors to be contacted.

* feat: add DeleteMany stored procedure for EmergencyAccess

* test: add database tests for new SP

* feat: commands use DeleteManyById for emergencyAccessDeletes

* claude: send one email per grantor instead of a bulk email to all grantors. Modified tests to validate.

* feat: change revision dates for confirmed grantees; 

* feat: add AccountRevisionDate bump for grantee users in the confirmed status

* test: update integration test to validate only confirmed users are updated as well as proper deletion of emergency access
This commit is contained in:
Ike
2026-02-03 16:43:44 -05:00
committed by GitHub
parent 82e1a6bd71
commit 68e67e1853
23 changed files with 792 additions and 183 deletions

View File

@@ -42,4 +42,97 @@ public class EmergencyAccessRepositoriesTests
Assert.NotNull(updatedGrantee);
Assert.NotEqual(updatedGrantee.AccountRevisionDate, granteeUser.AccountRevisionDate);
}
/// <summary>
/// Creates 3 Emergency Access records all connected to a single grantor, but separate grantees.
/// All 3 records are then deleted in a single call to DeleteManyAsync.
/// </summary>
[DatabaseTheory, DatabaseData]
public async Task DeleteManyAsync_DeletesMultipleGranteeRecords_UpdatesUserRevisionDates(
IUserRepository userRepository,
IEmergencyAccessRepository emergencyAccessRepository)
{
// Arrange
var grantorUser = await userRepository.CreateAsync(new User
{
Name = "Test Grantor User",
Email = $"test+grantor{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var confirmedGranteeUser1 = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User 1",
Email = $"test+grantee{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var invitedGranteeUser2 = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User 2",
Email = $"test+grantee{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
// The inmemory datetime has a precision issue, so we need to refresh the user to get the stored AccountRevisionDate
invitedGranteeUser2 = await userRepository.GetByIdAsync(invitedGranteeUser2.Id);
var granteeUser3 = await userRepository.CreateAsync(new User
{
Name = "Test Grantee User 3",
Email = $"test+grantee{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var confirmedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
GranteeId = confirmedGranteeUser1.Id,
Status = EmergencyAccessStatusType.Confirmed,
});
var invitedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
GranteeId = invitedGranteeUser2.Id,
Status = EmergencyAccessStatusType.Invited,
});
var acceptedEmergencyAccess = await emergencyAccessRepository.CreateAsync(new EmergencyAccess
{
GrantorId = grantorUser.Id,
GranteeId = granteeUser3.Id,
Status = EmergencyAccessStatusType.Accepted,
});
// Act
await emergencyAccessRepository.DeleteManyAsync([confirmedEmergencyAccess.Id, invitedEmergencyAccess.Id, acceptedEmergencyAccess.Id]);
// Assert
// ensure Grantor records deleted
var grantorEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(grantorUser.Id);
Assert.Empty(grantorEmergencyAccess);
// ensure Grantee records deleted
foreach (var grantee in (List<User>)[confirmedGranteeUser1, invitedGranteeUser2, granteeUser3])
{
var granteeEmergencyAccess = await emergencyAccessRepository.GetManyDetailsByGranteeIdAsync(grantee.Id);
Assert.Empty(granteeEmergencyAccess);
}
// Only the Status.Confirmed grantee's AccountRevisionDate should be updated
var updatedConfirmedGrantee = await userRepository.GetByIdAsync(confirmedGranteeUser1.Id);
Assert.NotNull(updatedConfirmedGrantee);
Assert.NotEqual(updatedConfirmedGrantee.AccountRevisionDate, confirmedGranteeUser1.AccountRevisionDate);
// Invited user should not have an updated AccountRevisionDate
var updatedInvitedGrantee = await userRepository.GetByIdAsync(invitedGranteeUser2.Id);
Assert.NotNull(updatedInvitedGrantee);
Assert.Equal(updatedInvitedGrantee.AccountRevisionDate, invitedGranteeUser2.AccountRevisionDate);
}
}