1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00
Bernd Schoolmann 55980e8038
[PM-16603] Add userkey rotation v2 (#5204)
* Implement userkey rotation v2

* Update request models

* Cleanup

* Update tests

* Improve test

* Add tests

* Fix formatting

* Fix test

* Remove whitespace

* Fix namespace

* Enable nullable on models

* Fix build

* Add tests and enable nullable on masterpasswordunlockdatamodel

* Fix test

* Remove rollback

* Add tests

* Make masterpassword hint optional

* Update user query

* Add EF test

* Improve test

* Cleanup

* Set masterpassword hint

* Remove connection close

* Add tests for invalid kdf types

* Update test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Fix formatting

* Update src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Fix imports

* Fix tests

* Remove null check

* Add rollback

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
2025-03-25 15:23:01 +01:00

317 lines
12 KiB
C#

using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Entities;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
using Xunit;
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
using SqlAuthRepo = Bit.Infrastructure.Dapper.Auth.Repositories;
using SqlRepo = Bit.Infrastructure.Dapper.Repositories;
namespace Bit.Infrastructure.EFIntegration.Test.Repositories;
public class UserRepositoryTests
{
[CiSkippedTheory, EfUserAutoData]
public async Task CreateAsync_Works_DataMatches(
User user, UserCompare equalityComparer,
List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo
)
{
var savedUsers = new List<User>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var savedUser = await sut.GetByIdAsync(postEfUser.Id);
savedUsers.Add(savedUser);
}
var sqlUser = await sqlUserRepo.CreateAsync(user);
savedUsers.Add(await sqlUserRepo.GetByIdAsync(sqlUser.Id));
var distinctItems = savedUsers.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task ReplaceAsync_Works_DataMatches(User postUser, User replaceUser,
UserCompare equalityComparer, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var savedUsers = new List<User>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(postUser);
replaceUser.Id = postEfUser.Id;
await sut.ReplaceAsync(replaceUser);
var replacedUser = await sut.GetByIdAsync(replaceUser.Id);
savedUsers.Add(replacedUser);
}
var postSqlUser = await sqlUserRepo.CreateAsync(postUser);
replaceUser.Id = postSqlUser.Id;
await sqlUserRepo.ReplaceAsync(replaceUser);
savedUsers.Add(await sqlUserRepo.GetByIdAsync(replaceUser.Id));
var distinctItems = savedUsers.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task DeleteAsync_Works_DataMatches(User user, List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo)
{
foreach (var sut in suts)
{
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);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var savedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id);
Assert.True(savedSqlUser != null);
await sqlUserRepo.DeleteAsync(postSqlUser);
savedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id);
Assert.True(savedSqlUser == null);
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetByEmailAsync_Works_DataMatches(User user, UserCompare equalityComparer,
List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo)
{
var savedUsers = new List<User>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var savedUser = await sut.GetByEmailAsync(postEfUser.Email.ToUpperInvariant());
savedUsers.Add(savedUser);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
savedUsers.Add(await sqlUserRepo.GetByEmailAsync(postSqlUser.Email.ToUpperInvariant()));
var distinctItems = savedUsers.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetKdfInformationByEmailAsync_Works_DataMatches(User user,
UserKdfInformationCompare equalityComparer, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var savedKdfInformation = new List<UserKdfInformation>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var kdfInformation = await sut.GetKdfInformationByEmailAsync(postEfUser.Email.ToUpperInvariant());
savedKdfInformation.Add(kdfInformation);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var sqlKdfInformation = await sqlUserRepo.GetKdfInformationByEmailAsync(postSqlUser.Email);
savedKdfInformation.Add(sqlKdfInformation);
var distinctItems = savedKdfInformation.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task SearchAsync_Works_DataMatches(User user, int skip, int take,
UserCompare equalityCompare, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var searchedEfUsers = new List<User>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var searchedEfUsersCollection = await sut.SearchAsync(postEfUser.Email.ToUpperInvariant(), skip, take);
searchedEfUsers.Concat(searchedEfUsersCollection.ToList());
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var searchedSqlUsers = await sqlUserRepo.SearchAsync(postSqlUser.Email.ToUpperInvariant(), skip, take);
var distinctItems = searchedEfUsers.Concat(searchedSqlUsers).Distinct(equalityCompare);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetManyByPremiumAsync_Works_DataMatches(User user,
List<EfRepo.UserRepository> suts, SqlRepo.UserRepository sqlUserRepo)
{
var returnedUsers = new List<User>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var searchedEfUsers = await sut.GetManyByPremiumAsync(user.Premium);
returnedUsers.Concat(searchedEfUsers.ToList());
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var searchedSqlUsers = await sqlUserRepo.GetManyByPremiumAsync(user.Premium);
returnedUsers.Concat(searchedSqlUsers.ToList());
Assert.True(returnedUsers.All(x => x.Premium == user.Premium));
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetPublicKeyAsync_Works_DataMatches(User user, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var returnedKeys = new List<string>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var efKey = await sut.GetPublicKeyAsync(postEfUser.Id);
returnedKeys.Add(efKey);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var sqlKey = await sqlUserRepo.GetPublicKeyAsync(postSqlUser.Id);
returnedKeys.Add(sqlKey);
Assert.True(!returnedKeys.Distinct().Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetAccountRevisionDateAsync(User user, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var returnedKeys = new List<string>();
foreach (var sut in suts)
{
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var efKey = await sut.GetPublicKeyAsync(postEfUser.Id);
returnedKeys.Add(efKey);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
var sqlKey = await sqlUserRepo.GetPublicKeyAsync(postSqlUser.Id);
returnedKeys.Add(sqlKey);
Assert.True(!returnedKeys.Distinct().Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task UpdateRenewalReminderDateAsync_Works_DataMatches(User user,
DateTime updatedReminderDate, List<EfRepo.UserRepository> suts,
SqlRepo.UserRepository sqlUserRepo)
{
var savedDates = new List<DateTime?>();
foreach (var sut in suts)
{
var postEfUser = user;
postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
await sut.UpdateRenewalReminderDateAsync(postEfUser.Id, updatedReminderDate);
sut.ClearChangeTracking();
var replacedUser = await sut.GetByIdAsync(postEfUser.Id);
savedDates.Add(replacedUser.RenewalReminderDate);
}
var postSqlUser = await sqlUserRepo.CreateAsync(user);
await sqlUserRepo.UpdateRenewalReminderDateAsync(postSqlUser.Id, updatedReminderDate);
var replacedSqlUser = await sqlUserRepo.GetByIdAsync(postSqlUser.Id);
savedDates.Add(replacedSqlUser.RenewalReminderDate);
var distinctItems = savedDates.GroupBy(e => e.ToString());
Assert.True(!distinctItems.Skip(1).Any() &&
savedDates.All(e => e.ToString() == updatedReminderDate.ToString()));
}
[CiSkippedTheory, EfUserAutoData]
public async Task GetBySsoUserAsync_Works_DataMatches(User user, Organization org,
SsoUser ssoUser, UserCompare equalityComparer, List<EfRepo.UserRepository> suts,
List<EfRepo.SsoUserRepository> ssoUserRepos, List<EfRepo.OrganizationRepository> orgRepos,
SqlRepo.UserRepository sqlUserRepo, SqlAuthRepo.SsoUserRepository sqlSsoUserRepo,
SqlRepo.OrganizationRepository sqlOrgRepo)
{
var returnedList = new List<User>();
foreach (var sut in suts)
{
var i = suts.IndexOf(sut);
var postEfUser = await sut.CreateAsync(user);
sut.ClearChangeTracking();
var efOrg = await orgRepos[i].CreateAsync(org);
sut.ClearChangeTracking();
ssoUser.UserId = postEfUser.Id;
ssoUser.OrganizationId = efOrg.Id;
var postEfSsoUser = await ssoUserRepos[i].CreateAsync(ssoUser);
sut.ClearChangeTracking();
var returnedUser = await sut.GetBySsoUserAsync(postEfSsoUser.ExternalId.ToUpperInvariant(), efOrg.Id);
returnedList.Add(returnedUser);
}
var sqlUser = await sqlUserRepo.CreateAsync(user);
var sqlOrganization = await sqlOrgRepo.CreateAsync(org);
ssoUser.UserId = sqlUser.Id;
ssoUser.OrganizationId = sqlOrganization.Id;
var postSqlSsoUser = await sqlSsoUserRepo.CreateAsync(ssoUser);
var returnedSqlUser = await sqlUserRepo
.GetBySsoUserAsync(postSqlSsoUser.ExternalId, sqlOrganization.Id);
returnedList.Add(returnedSqlUser);
var distinctItems = returnedList.Distinct(equalityComparer);
Assert.True(!distinctItems.Skip(1).Any());
}
[CiSkippedTheory, EfUserAutoData]
public async Task UpdateUserKeyAndEncryptedDataAsync_Works_DataMatches(User user, SqlRepo.UserRepository sqlUserRepo)
{
var sqlUser = await sqlUserRepo.CreateAsync(user);
sqlUser.Kdf = KdfType.PBKDF2_SHA256;
sqlUser.KdfIterations = 6_000_000;
sqlUser.KdfMemory = 7_000_000;
sqlUser.KdfParallelism = 8_000_000;
sqlUser.MasterPassword = "masterPasswordHash";
sqlUser.MasterPasswordHint = "masterPasswordHint";
sqlUser.Email = "example@example.com";
await sqlUserRepo.UpdateUserKeyAndEncryptedDataV2Async(sqlUser, []);
var updatedUser = await sqlUserRepo.GetByIdAsync(sqlUser.Id);
Assert.Equal(sqlUser.Kdf, updatedUser.Kdf);
Assert.Equal(sqlUser.KdfIterations, updatedUser.KdfIterations);
Assert.Equal(sqlUser.KdfMemory, updatedUser.KdfMemory);
Assert.Equal(sqlUser.KdfParallelism, updatedUser.KdfParallelism);
Assert.Equal(sqlUser.MasterPassword, updatedUser.MasterPassword);
Assert.Equal(sqlUser.MasterPasswordHint, updatedUser.MasterPasswordHint);
Assert.Equal(sqlUser.Email, updatedUser.Email);
}
}