1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

[PM-5938] Prevent permanent vault coruption on key-rotation with desycned vault (#4098)

* Add check to verify the vault state for rotation is not obviously desynced (empty)

* Add unit test for key rotation guardrail

* Move de-synced vault detection to validators

* Add tests
This commit is contained in:
Bernd Schoolmann 2024-05-30 11:08:26 +02:00 committed by GitHub
parent f73b7c7fa8
commit 0189952e1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 70 additions and 25 deletions

View File

@ -27,13 +27,9 @@ public class OrganizationUserRotationValidator : IRotationValidator<IEnumerable<
}
var result = new List<OrganizationUser>();
if (resetPasswordKeys == null || !resetPasswordKeys.Any())
{
return result;
}
var existing = await _organizationUserRepository.GetManyByUserAsync(user.Id);
if (existing == null || !existing.Any())
if (existing == null || existing.Count == 0)
{
return result;
}

View File

@ -24,13 +24,9 @@ public class EmergencyAccessRotationValidator : IRotationValidator<IEnumerable<E
IEnumerable<EmergencyAccessWithIdRequestModel> emergencyAccessKeys)
{
var result = new List<EmergencyAccess>();
if (emergencyAccessKeys == null || !emergencyAccessKeys.Any())
{
return result;
}
var existing = await _emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(user.Id);
if (existing == null || !existing.Any())
if (existing == null || existing.Count == 0)
{
return result;
}

View File

@ -30,13 +30,9 @@ public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRe
public async Task<IReadOnlyList<Send>> ValidateAsync(User user, IEnumerable<SendWithIdRequestModel> sends)
{
var result = new List<Send>();
if (sends == null || !sends.Any())
{
return result;
}
var existingSends = await _sendRepository.GetManyByUserIdAsync(user.Id);
if (existingSends == null || !existingSends.Any())
if (existingSends == null || existingSends.Count == 0)
{
return result;
}

View File

@ -26,13 +26,9 @@ public class CipherRotationValidator : IRotationValidator<IEnumerable<CipherWith
public async Task<IEnumerable<Cipher>> ValidateAsync(User user, IEnumerable<CipherWithIdRequestModel> ciphers)
{
var result = new List<Cipher>();
if (ciphers == null || !ciphers.Any())
{
return result;
}
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, UseFlexibleCollections);
if (existingCiphers == null || !existingCiphers.Any())
if (existingCiphers == null || existingCiphers.Count == 0)
{
return result;
}

View File

@ -19,13 +19,9 @@ public class FolderRotationValidator : IRotationValidator<IEnumerable<FolderWith
public async Task<IEnumerable<Folder>> ValidateAsync(User user, IEnumerable<FolderWithIdRequestModel> folders)
{
var result = new List<Folder>();
if (folders == null || !folders.Any())
{
return result;
}
var existingFolders = await _folderRepository.GetManyByUserIdAsync(user.Id);
if (existingFolders == null || !existingFolders.Any())
if (existingFolders == null || existingFolders.Count == 0)
{
return result;
}

View File

@ -140,4 +140,25 @@ public class OrganizationUserRotationValidatorTests
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.ValidateAsync(user, resetPasswordKeys));
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_NoOrganizationsInRequestButInDatabase_Throws(
SutProvider<OrganizationUserRotationValidator> sutProvider, User user,
IEnumerable<ResetPasswordWithOrgIdRequestModel> resetPasswordKeys)
{
var existingUserResetPassword = resetPasswordKeys
.Select(a =>
new OrganizationUser
{
Id = new Guid(),
ResetPasswordKey = a.ResetPasswordKey,
OrganizationId = a.OrganizationId
}).ToList();
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyByUserAsync(user.Id)
.Returns(existingUserResetPassword);
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.ValidateAsync(user, Enumerable.Empty<ResetPasswordWithOrgIdRequestModel>()));
}
}

View File

@ -133,4 +133,25 @@ public class EmergencyAccessRotationValidatorTests
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.ValidateAsync(user, emergencyAccessKeys));
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_SentKeysAreEmptyButDatabaseIsNot_Throws(
SutProvider<EmergencyAccessRotationValidator> sutProvider, User user,
IEnumerable<EmergencyAccessWithIdRequestModel> emergencyAccessKeys)
{
sutProvider.GetDependency<IUserService>().CanAccessPremium(user).Returns(true);
var userEmergencyAccess = emergencyAccessKeys.Select(e => new EmergencyAccessDetails
{
Id = e.Id,
GrantorName = user.Name,
GrantorEmail = user.Email,
KeyEncrypted = e.KeyEncrypted,
Type = e.Type
}).ToList();
sutProvider.GetDependency<IEmergencyAccessRepository>().GetManyDetailsByGrantorIdAsync(user.Id)
.Returns(userEmergencyAccess);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.ValidateAsync(user, Enumerable.Empty<EmergencyAccessWithIdRequestModel>()));
}
}

View File

@ -42,4 +42,16 @@ public class CipherRotationValidatorTests
Assert.DoesNotContain(result, c => c.Id == ciphers.First().Id);
}
[Theory, BitAutoData]
public async Task ValidateAsync_SentCiphersAreEmptyButDatabaseCiphersAreNot_Throws(
SutProvider<CipherRotationValidator> sutProvider, User user, IEnumerable<CipherWithIdRequestModel> ciphers)
{
var userCiphers = ciphers.Select(c => new CipherDetails { Id = c.Id.GetValueOrDefault(), Type = c.Type })
.ToList();
sutProvider.GetDependency<ICipherRepository>().GetManyByUserIdAsync(user.Id, Arg.Any<bool>())
.Returns(userCiphers);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.ValidateAsync(user, Enumerable.Empty<CipherWithIdRequestModel>()));
}
}

View File

@ -39,4 +39,14 @@ public class FolderRotationValidatorTests
Assert.DoesNotContain(result, c => c.Id == folders.First().Id);
}
[Theory, BitAutoData]
public async Task ValidateAsync_SentFoldersAreEmptyButDatabaseFoldersAreNot_Throws(
SutProvider<FolderRotationValidator> sutProvider, User user, IEnumerable<FolderWithIdRequestModel> folders)
{
var userFolders = folders.Select(f => f.ToFolder(new Folder())).ToList();
sutProvider.GetDependency<IFolderRepository>().GetManyByUserIdAsync(user.Id).Returns(userFolders);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.ValidateAsync(user, Enumerable.Empty<FolderWithIdRequestModel>()));
}
}

View File

@ -49,4 +49,5 @@ public class RotateUserKeyCommandTests
await sutProvider.GetDependency<IPushNotificationService>().ReceivedWithAnyArgs()
.PushLogOutAsync(default, default);
}
}