-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[PM-27279] Implement TDE Registration with V2 Keys #6671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: km/account-keys-command
Are you sure you want to change the base?
Changes from all commits
e8be7bf
3b83307
4633b43
5b766e9
dfa4c3a
1897e66
6945add
4f6e7ac
216e7a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,32 @@ | ||
| ๏ปฟ// FIXME: Update this file to be null safe and then delete the line below | ||
| #nullable disable | ||
|
|
||
| using Bit.Core.Entities; | ||
| ๏ปฟusing Bit.Core.KeyManagement.Models.Api.Response; | ||
| using Bit.Core.KeyManagement.Models.Data; | ||
| using Bit.Core.Models.Api; | ||
|
|
||
| namespace Bit.Api.Models.Response; | ||
|
|
||
| public class KeysResponseModel : ResponseModel | ||
| { | ||
| public KeysResponseModel(User user) | ||
| public KeysResponseModel(UserAccountKeysData accountKeys, string? masterKeyWrappedUserKey) | ||
| : base("keys") | ||
| { | ||
| if (user == null) | ||
| if (masterKeyWrappedUserKey != null) | ||
| { | ||
| throw new ArgumentNullException(nameof(user)); | ||
| Key = masterKeyWrappedUserKey; | ||
| } | ||
|
|
||
| Key = user.Key; | ||
| PublicKey = user.PublicKey; | ||
| PrivateKey = user.PrivateKey; | ||
| PublicKey = accountKeys.PublicKeyEncryptionKeyPairData.PublicKey; | ||
|
Check warning on line 17 in src/Api/Models/Response/KeysResponseModel.cs
|
||
| PrivateKey = accountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey; | ||
|
Check warning on line 18 in src/Api/Models/Response/KeysResponseModel.cs
|
||
| AccountKeys = new PrivateKeysResponseModel(accountKeys); | ||
| } | ||
|
|
||
| public string Key { get; set; } | ||
| /// <summary> | ||
| /// The master key wrapped user key. The master key can either be a master-password master key or a | ||
| /// key-connector master key. | ||
| /// </summary> | ||
| public string? Key { get; set; } | ||
| [Obsolete("Use AccountKeys.PublicKeyEncryptionKeyPair.PublicKey instead")] | ||
| public string PublicKey { get; set; } | ||
| [Obsolete("Use AccountKeys.PublicKeyEncryptionKeyPair.WrappedPrivateKey instead")] | ||
| public string PrivateKey { get; set; } | ||
| public PrivateKeysResponseModel AccountKeys { get; set; } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,9 @@ | |
| using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; | ||
| using Bit.Core.Entities; | ||
| using Bit.Core.Exceptions; | ||
| using Bit.Core.KeyManagement.Commands.Interfaces; | ||
| using Bit.Core.KeyManagement.Kdf; | ||
| using Bit.Core.KeyManagement.Models.Api.Request; | ||
| using Bit.Core.KeyManagement.Models.Data; | ||
| using Bit.Core.KeyManagement.Queries.Interfaces; | ||
| using Bit.Core.Repositories; | ||
|
|
@@ -38,6 +40,7 @@ | |
| private readonly IUserAccountKeysQuery _userAccountKeysQuery; | ||
| private readonly ITwoFactorEmailService _twoFactorEmailService; | ||
| private readonly IChangeKdfCommand _changeKdfCommand; | ||
| private readonly ISetAccountKeysForUserCommand _setAccountKeysForUserCommand; | ||
|
|
||
| public AccountsControllerTests() | ||
| { | ||
|
|
@@ -53,6 +56,7 @@ | |
| _userAccountKeysQuery = Substitute.For<IUserAccountKeysQuery>(); | ||
| _twoFactorEmailService = Substitute.For<ITwoFactorEmailService>(); | ||
| _changeKdfCommand = Substitute.For<IChangeKdfCommand>(); | ||
| _setAccountKeysForUserCommand = Substitute.For<ISetAccountKeysForUserCommand>(); | ||
|
|
||
| _sut = new AccountsController( | ||
| _organizationService, | ||
|
|
@@ -66,7 +70,8 @@ | |
| _featureService, | ||
| _userAccountKeysQuery, | ||
| _twoFactorEmailService, | ||
| _changeKdfCommand | ||
| _changeKdfCommand, | ||
| _setAccountKeysForUserCommand | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -738,5 +743,62 @@ | |
| _userService.GetUserByIdAsync(Arg.Any<Guid>()) | ||
| .Returns(Task.FromResult((User)null)); | ||
| } | ||
|
|
||
| [Theory, BitAutoData] | ||
| public async Task PostKeys_WithAccountKeys_CallsSetAccountKeysCommand( | ||
| User user, | ||
| KeysRequestModel model) | ||
| { | ||
| // Arrange | ||
| user.PublicKey = null; | ||
| user.PrivateKey = null; | ||
| model.AccountKeys = new AccountKeysRequestModel | ||
| { | ||
| UserKeyEncryptedAccountPrivateKey = "wrapped-private-key", | ||
| AccountPublicKey = "public-key" | ||
| }; | ||
|
|
||
| _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); | ||
| _featureService.IsEnabled(Bit.Core.FeatureFlagKeys.ReturnErrorOnExistingKeypair).Returns(false); | ||
|
|
||
| // Act | ||
| var result = await _sut.PostKeys(model); | ||
|
|
||
| // Assert | ||
| await _setAccountKeysForUserCommand.Received(1).SetAccountKeysForUserAsync( | ||
| user, | ||
| model.AccountKeys); | ||
| await _userService.DidNotReceiveWithAnyArgs().SaveUserAsync(Arg.Any<User>()); | ||
| Assert.NotNull(result); | ||
| Assert.Equal("keys", result.Object); | ||
| } | ||
|
|
||
| [Theory, BitAutoData] | ||
| public async Task PostKeys_WithoutAccountKeys_CallsSaveUser( | ||
| User user, | ||
| KeysRequestModel model) | ||
| { | ||
| // Arrange | ||
| user.PublicKey = null; | ||
| user.PrivateKey = null; | ||
| model.AccountKeys = null; | ||
| model.PublicKey = "public-key"; | ||
|
Check warning on line 785 in test/Api.Test/Auth/Controllers/AccountsControllerTests.cs
|
||
| model.EncryptedPrivateKey = "encrypted-private-key"; | ||
|
Check warning on line 786 in test/Api.Test/Auth/Controllers/AccountsControllerTests.cs
|
||
|
|
||
| _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); | ||
| _featureService.IsEnabled(Bit.Core.FeatureFlagKeys.ReturnErrorOnExistingKeypair).Returns(false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ TEST COVERAGE GAP: This test doesn't validate the response construction, which would have caught the null reference bug at line 456 in the controller. Recommendation: Add assertions to verify the response data: // Assert
Assert.NotNull(result);
Assert.Equal("keys", result.Object);
Assert.Equal(user.Key, result.Key);
Assert.NotNull(result.AccountKeys);
// Verify response contains data from the saved user, not null model.AccountKeysYou'll also need to mock |
||
|
|
||
| // Act | ||
| var result = await _sut.PostKeys(model); | ||
|
|
||
| // Assert | ||
| await _userService.Received(1).SaveUserAsync(Arg.Is<User>(u => | ||
| u.PublicKey == model.PublicKey && | ||
|
Check warning on line 796 in test/Api.Test/Auth/Controllers/AccountsControllerTests.cs
|
||
| u.PrivateKey == model.EncryptedPrivateKey)); | ||
|
Check warning on line 797 in test/Api.Test/Auth/Controllers/AccountsControllerTests.cs
|
||
| await _setAccountKeysForUserCommand.DidNotReceiveWithAnyArgs() | ||
| .SetAccountKeysForUserAsync(Arg.Any<User>(), Arg.Any<AccountKeysRequestModel>()); | ||
| Assert.NotNull(result); | ||
| Assert.Equal("keys", result.Object); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AccountKeysproperty has no validation attributes, while the obsolete properties have[Required].This creates an ambiguous state where neither old nor new properties might be provided. Consider adding validation: