1
0
mirror of https://github.com/bitwarden/server synced 2025-12-22 11:13:27 +00:00

[PM-23229] Add extra validation to kdf changes + authentication data + unlock data (#6121)

* Added MasterPasswordUnlock to UserDecryptionOptions as part of identity response

* Implement support for authentication data and unlock data in kdf change

* Extract to kdf command and add tests

* Fix namespace

* Delete empty file

* Fix build

* Clean up tests

* Fix tests

* Add comments

* Cleanup

* Cleanup

* Cleanup

* Clean-up and fix build

* Address feedback; force new parameters on KDF change request

* Clean-up and add tests

* Re-add logger

* Update logger to interface

* Clean up, remove Kdf Request Model

* Remove kdf request model tests

* Fix types in test

* Address feedback to rename request model and re-add tests

* Fix namespace

* Move comments

* Rename InnerKdfRequestModel to KdfRequestModel

---------

Co-authored-by: Maciej Zieniuk <mzieniuk@bitwarden.com>
This commit is contained in:
Bernd Schoolmann
2025-09-24 05:10:46 +09:00
committed by GitHub
parent 744f11733d
commit ff092a031e
25 changed files with 729 additions and 173 deletions

View File

@@ -10,6 +10,7 @@ using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Kdf;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture.Attributes;
@@ -33,6 +34,7 @@ public class AccountsControllerTests : IDisposable
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly IFeatureService _featureService;
private readonly ITwoFactorEmailService _twoFactorEmailService;
private readonly IChangeKdfCommand _changeKdfCommand;
public AccountsControllerTests()
@@ -47,7 +49,7 @@ public class AccountsControllerTests : IDisposable
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
_featureService = Substitute.For<IFeatureService>();
_twoFactorEmailService = Substitute.For<ITwoFactorEmailService>();
_changeKdfCommand = Substitute.For<IChangeKdfCommand>();
_sut = new AccountsController(
_organizationService,
@@ -59,7 +61,8 @@ public class AccountsControllerTests : IDisposable
_tdeOffboardingPasswordCommand,
_twoFactorIsEnabledQuery,
_featureService,
_twoFactorEmailService
_twoFactorEmailService,
_changeKdfCommand
);
}
@@ -242,12 +245,18 @@ public class AccountsControllerTests : IDisposable
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
_userService.ChangePasswordAsync(user, Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Success));
await _sut.PostPassword(new PasswordRequestModel());
await _sut.PostPassword(new PasswordRequestModel
{
MasterPasswordHash = "masterPasswordHash",
NewMasterPasswordHash = "newMasterPasswordHash",
MasterPasswordHint = "masterPasswordHint",
Key = "key"
});
await _userService.Received(1).ChangePasswordAsync(user, default, default, default, default);
await _userService.Received(1).ChangePasswordAsync(user, Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>());
}
[Fact]
@@ -256,7 +265,13 @@ public class AccountsControllerTests : IDisposable
ConfigureUserServiceToReturnNullPrincipal();
await Assert.ThrowsAsync<UnauthorizedAccessException>(
() => _sut.PostPassword(new PasswordRequestModel())
() => _sut.PostPassword(new PasswordRequestModel
{
MasterPasswordHash = "masterPasswordHash",
NewMasterPasswordHash = "newMasterPasswordHash",
MasterPasswordHint = "masterPasswordHint",
Key = "key"
})
);
}
@@ -265,11 +280,17 @@ public class AccountsControllerTests : IDisposable
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangePasswordAsync(user, default, default, default, default)
_userService.ChangePasswordAsync(user, Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed()));
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostPassword(new PasswordRequestModel())
() => _sut.PostPassword(new PasswordRequestModel
{
MasterPasswordHash = "masterPasswordHash",
NewMasterPasswordHash = "newMasterPasswordHash",
MasterPasswordHint = "masterPasswordHint",
Key = "key"
})
);
}
@@ -593,6 +614,30 @@ public class AccountsControllerTests : IDisposable
await _twoFactorEmailService.Received(1).SendNewDeviceVerificationEmailAsync(user);
}
[Theory]
[BitAutoData]
public async Task PostKdf_WithNullAuthenticationData_ShouldFail(
User user, PasswordRequestModel model)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
model.AuthenticationData = null;
// Act
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
}
[Theory]
[BitAutoData]
public async Task PostKdf_WithNullUnlockData_ShouldFail(
User user, PasswordRequestModel model)
{
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
model.UnlockData = null;
// Act
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostKdf(model));
}
// Below are helper functions that currently belong to this
// test class, but ultimately may need to be split out into
// something greater in order to share common test steps with