1
0
mirror of https://github.com/bitwarden/server synced 2026-02-21 20:03:40 +00:00

[PM-31052][PM-32469] Add V2UpgradeToken for key rotation without logout (#6995)

* User V2UpgradeToken for key rotation without logout

* reset old v2 upgrade token on manual key rotation

* sql migration fix

* missing table column

* missing view update

* tests for V2UpgradeToken clearing on manual key rotation

* V2 to V2 rotation causes logout. Updated wrapped key 1 to be a valid V2 encrypted string in tests.

* integration tests failures - increase assert recent for date time type from 2 to 5 seconds (usually for UpdatedAt assertions)

* repository test coverage

* migration script update

* new EF migration scripts

* broken EF migration scripts fixed

* refresh views due to User table alternation
This commit is contained in:
Maciej Zieniuk
2026-02-20 20:19:14 +01:00
committed by GitHub
parent a961626957
commit 6a7b8f5a89
35 changed files with 12395 additions and 242 deletions

View File

@@ -2,6 +2,7 @@
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.KeyManagement.UserKey;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Infrastructure.IntegrationTest.AdminConsole;
@@ -527,6 +528,75 @@ public class UserRepositoryTests
Assert.Equal(DateTime.UtcNow, updatedUser.AccountRevisionDate, TimeSpan.FromMinutes(1));
}
[Theory, DatabaseData]
public async Task UpdateUserKeyAndEncryptedDataV2Async_UpdatesAllUserFields(IUserRepository userRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
var newSecurityStamp = Guid.NewGuid().ToString();
user.Key = "new-user-key";
user.PrivateKey = "new-private-key";
user.SecurityStamp = newSecurityStamp;
user.Kdf = KdfType.Argon2id;
user.KdfIterations = 3;
user.KdfMemory = 64;
user.KdfParallelism = 4;
user.Email = $"updated+{Guid.NewGuid()}@example.com";
user.MasterPassword = "new-master-password-hash";
user.MasterPasswordHint = "new-hint";
user.LastKeyRotationDate = DateTime.UtcNow;
user.RevisionDate = DateTime.UtcNow;
user.AccountRevisionDate = DateTime.UtcNow;
user.SignedPublicKey = "new-signed-public-key";
user.SecurityState = "new-security-state";
user.SecurityVersion = 2;
user.V2UpgradeToken = null;
// Act
await userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, []);
// Assert
var updatedUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotNull(updatedUser);
Assert.Equal("new-user-key", updatedUser.Key);
Assert.Equal("new-private-key", updatedUser.PrivateKey);
Assert.Equal(newSecurityStamp, updatedUser.SecurityStamp);
Assert.Equal(KdfType.Argon2id, updatedUser.Kdf);
Assert.Equal(3, updatedUser.KdfIterations);
Assert.Equal(64, updatedUser.KdfMemory);
Assert.Equal(4, updatedUser.KdfParallelism);
Assert.Equal(user.Email, updatedUser.Email);
Assert.Equal("new-master-password-hash", updatedUser.MasterPassword);
Assert.Equal("new-hint", updatedUser.MasterPasswordHint);
Assert.Equal("new-signed-public-key", updatedUser.SignedPublicKey);
Assert.Equal("new-security-state", updatedUser.SecurityState);
Assert.Equal(2, updatedUser.SecurityVersion);
Assert.Null(updatedUser.V2UpgradeToken);
Assert.Equal(DateTime.UtcNow, updatedUser.RevisionDate, TimeSpan.FromMinutes(1));
}
[Theory, DatabaseData]
public async Task UpdateUserKeyAndEncryptedDataV2Async_InvokesUpdateDataActions(IUserRepository userRepository)
{
// Arrange
var user = await userRepository.CreateTestUserAsync();
user.RevisionDate = DateTime.UtcNow;
var actionWasInvoked = false;
UpdateEncryptedDataForKeyRotation action = (_, _) =>
{
actionWasInvoked = true;
return Task.CompletedTask;
};
// Act
await userRepository.UpdateUserKeyAndEncryptedDataV2Async(user, [action]);
// Assert
Assert.True(actionWasInvoked);
}
private static async Task RunUpdateUserDataAsync(UpdateUserData task, Database database)
{
if (database.Type == SupportedDatabaseProviders.SqlServer && !database.UseEf)