The core infrastructure backend (API, database, Docker, etc).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

722 lines
39 KiB

#nullable enable
using System.Security.Claims;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Auth.Models.Request;
using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Api.KeyManagement.Controllers;
using Bit.Api.KeyManagement.Enums;
using Bit.Api.KeyManagement.Models.Requests;
using Bit.Api.KeyManagement.Validators;
using Bit.Api.Tools.Models.Request;
using Bit.Api.Vault.Models.Request;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Commands.Interfaces;
using Bit.Core.KeyManagement.Models.Api.Request;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Queries.Interfaces;
using Bit.Core.KeyManagement.UserKey;
using Bit.Core.KeyManagement.UserKey.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Api.Test.KeyManagement.Controllers;
[ControllerCustomize(typeof(AccountsKeyManagementController))]
[SutProviderCustomize]
[JsonDocumentCustomize]
public class AccountsKeyManagementControllerTests
{
private static readonly string _mockEncryptedType2String =
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
private static readonly string _mockEncryptedType7String = "7.AOs41Hd8OQiCPXjyJKCiDA==";
public static IEnumerable<object[]> UnimplementedUnlockMethods => new List<object[]>
{
//TDE
new object[] { new UnlockMethodRequestModel { UnlockMethod = UnlockMethod.Tde, KeyConnectorKeyWrappedUserKey = null, MasterPasswordUnlockData = null }},
//Key connector
new object[] { new UnlockMethodRequestModel
{
UnlockMethod = UnlockMethod.KeyConnector,
KeyConnectorKeyWrappedUserKey = "wrapped-user-key", MasterPasswordUnlockData = null
} },
};
[Theory]
[BitAutoData]
public async Task RegenerateKeysAsync_UserNull_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
KeyRegenerationRequestModel data)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.RegenerateKeysAsync(data));
await sutProvider.GetDependency<IOrganizationUserRepository>().ReceivedWithAnyArgs(0)
.GetManyByUserAsync(Arg.Any<Guid>());
await sutProvider.GetDependency<IEmergencyAccessRepository>().ReceivedWithAnyArgs(0)
.GetManyDetailsByGranteeIdAsync(Arg.Any<Guid>());
await sutProvider.GetDependency<IRegenerateUserAsymmetricKeysCommand>().ReceivedWithAnyArgs(0)
.RegenerateKeysAsync(Arg.Any<UserAsymmetricKeys>(),
Arg.Any<ICollection<OrganizationUser>>(),
Arg.Any<ICollection<EmergencyAccessDetails>>());
}
[Theory]
[BitAutoData]
public async Task RegenerateKeysAsync_Success(SutProvider<AccountsKeyManagementController> sutProvider,
KeyRegenerationRequestModel data, User user, ICollection<OrganizationUser> orgUsers,
ICollection<EmergencyAccessDetails> accessDetails)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyByUserAsync(Arg.Is(user.Id)).Returns(orgUsers);
sutProvider.GetDependency<IEmergencyAccessRepository>().GetManyDetailsByGranteeIdAsync(Arg.Is(user.Id))
.Returns(accessDetails);
await sutProvider.Sut.RegenerateKeysAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.GetManyByUserAsync(Arg.Is(user.Id));
await sutProvider.GetDependency<IEmergencyAccessRepository>().Received(1)
.GetManyDetailsByGranteeIdAsync(Arg.Is(user.Id));
await sutProvider.GetDependency<IRegenerateUserAsymmetricKeysCommand>().Received(1)
.RegenerateKeysAsync(
Arg.Is<UserAsymmetricKeys>(u =>
u.UserId == user.Id && u.PublicKey == data.UserPublicKey &&
u.UserKeyEncryptedPrivateKey == data.UserKeyEncryptedUserPrivateKey),
Arg.Is(orgUsers),
Arg.Is(accessDetails));
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_UserCryptoV1_Success(
SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user)
{
data.AccountKeys.SignatureKeyPair = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data);
await sutProvider.GetDependency<IRotationValidator<IEnumerable<EmergencyAccessWithIdRequestModel>, IEnumerable<EmergencyAccess>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.EmergencyAccessUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.OrganizationAccountRecoveryUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.PasskeyUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Ciphers));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Folders));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Sends));
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<PasswordChangeAndRotateUserAccountKeysData>(d =>
d.OldMasterKeyAuthenticationHash == data.OldMasterKeyAuthenticationHash
&& d.MasterPasswordUnlockData.Kdf.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType
&& d.MasterPasswordUnlockData.Kdf.Iterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations
&& d.MasterPasswordUnlockData.Kdf.Memory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory
&& d.MasterPasswordUnlockData.Kdf.Parallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism
&& d.MasterPasswordUnlockData.Salt == data.AccountUnlockData.MasterPasswordUnlockData.MasterPasswordSalt
&& d.MasterPasswordUnlockData.MasterKeyWrappedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey
&& d.MasterPasswordAuthenticationData.Kdf.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType
&& d.MasterPasswordAuthenticationData.Kdf.Iterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations
&& d.MasterPasswordAuthenticationData.Kdf.Memory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory
&& d.MasterPasswordAuthenticationData.Kdf.Parallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism
&& d.MasterPasswordAuthenticationData.Salt == data.AccountUnlockData.MasterPasswordUnlockData.MasterPasswordSalt
&& d.MasterPasswordAuthenticationData.MasterPasswordAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash
&& d.BaseData.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey
&& d.BaseData.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey
));
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_UserCryptoV2_Success_Async(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user)
{
data.AccountKeys.SignatureKeyPair = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = "ed25519",
WrappedSigningKey = "wrappedSigningKey",
VerifyingKey = "verifyingKey"
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data);
await sutProvider.GetDependency<IRotationValidator<IEnumerable<EmergencyAccessWithIdRequestModel>, IEnumerable<EmergencyAccess>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.EmergencyAccessUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.OrganizationAccountRecoveryUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountUnlockData.PasskeyUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Ciphers));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Folders));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(data.AccountData.Sends));
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<PasswordChangeAndRotateUserAccountKeysData>(d =>
d.OldMasterKeyAuthenticationHash == data.OldMasterKeyAuthenticationHash
&& d.MasterPasswordUnlockData.Kdf.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType
&& d.MasterPasswordUnlockData.Kdf.Iterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations
&& d.MasterPasswordUnlockData.Kdf.Memory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory
&& d.MasterPasswordUnlockData.Kdf.Parallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism
&& d.MasterPasswordUnlockData.Salt == data.AccountUnlockData.MasterPasswordUnlockData.MasterPasswordSalt
&& d.MasterPasswordUnlockData.MasterKeyWrappedUserKey == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyEncryptedUserKey
&& d.MasterPasswordAuthenticationData.Kdf.KdfType == data.AccountUnlockData.MasterPasswordUnlockData.KdfType
&& d.MasterPasswordAuthenticationData.Kdf.Iterations == data.AccountUnlockData.MasterPasswordUnlockData.KdfIterations
&& d.MasterPasswordAuthenticationData.Kdf.Memory == data.AccountUnlockData.MasterPasswordUnlockData.KdfMemory
&& d.MasterPasswordAuthenticationData.Kdf.Parallelism == data.AccountUnlockData.MasterPasswordUnlockData.KdfParallelism
&& d.MasterPasswordAuthenticationData.Salt == data.AccountUnlockData.MasterPasswordUnlockData.MasterPasswordSalt
&& d.MasterPasswordAuthenticationData.MasterPasswordAuthenticationHash == data.AccountUnlockData.MasterPasswordUnlockData.MasterKeyAuthenticationHash
&& d.BaseData.AccountKeys!.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.WrappedPrivateKey
&& d.BaseData.AccountKeys!.PublicKeyEncryptionKeyPairData.PublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.PublicKey
&& d.BaseData.AccountKeys!.PublicKeyEncryptionKeyPairData.SignedPublicKey == data.AccountKeys.PublicKeyEncryptionKeyPair!.SignedPublicKey
&& d.BaseData.AccountKeys!.SignatureKeyPairData!.SignatureAlgorithm == Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519
&& d.BaseData.AccountKeys!.SignatureKeyPairData.WrappedSigningKey == data.AccountKeys.SignatureKeyPair!.WrappedSigningKey
&& d.BaseData.AccountKeys!.SignatureKeyPairData.VerifyingKey == data.AccountKeys.SignatureKeyPair!.VerifyingKey
));
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_NoUser_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data)
{
data.AccountKeys.SignatureKeyPair = null;
User? user = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data));
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_WrongData_Throws(SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data, User user, IdentityErrorDescriber _identityErrorDescriber)
{
data.AccountKeys.SignatureKeyPair = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>().PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()));
try
{
await sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data);
Assert.Fail("Should have thrown");
}
catch (BadRequestException ex)
{
Assert.NotEmpty(ex.ModelState!.Values);
}
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_WithV2UpgradeToken_PassesTokenToCommand(
SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data,
User user)
{
// Arrange
data.AccountKeys.SignatureKeyPair = null;
data.AccountUnlockData.V2UpgradeToken = new V2UpgradeTokenRequestModel
{
WrappedUserKey1 = _mockEncryptedType7String,
WrappedUserKey2 = _mockEncryptedType2String
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>()
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
// Act
await sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data);
// Assert
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<PasswordChangeAndRotateUserAccountKeysData>(d =>
d.BaseData.V2UpgradeToken != null &&
d.BaseData.V2UpgradeToken.WrappedUserKey1 == _mockEncryptedType7String &&
d.BaseData.V2UpgradeToken.WrappedUserKey2 == _mockEncryptedType2String));
}
[Theory]
[BitAutoData]
public async Task PasswordChangeAndRotateUserAccountKeysAsync_WithoutV2UpgradeToken_PassesNullToCommand(
SutProvider<AccountsKeyManagementController> sutProvider,
RotateUserAccountKeysAndDataRequestModel data,
User user)
{
// Arrange
data.AccountKeys.SignatureKeyPair = null;
data.AccountUnlockData.V2UpgradeToken = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
sutProvider.GetDependency<IRotateUserAccountKeysCommand>()
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Any<User>(), Arg.Any<PasswordChangeAndRotateUserAccountKeysData>())
.Returns(IdentityResult.Success);
// Act
await sutProvider.Sut.PasswordChangeAndRotateUserAccountKeysAsync(data);
// Assert
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.PasswordChangeAndRotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<PasswordChangeAndRotateUserAccountKeysData>(d =>
d.BaseData.V2UpgradeToken == null));
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V1_UserNull_Throws(
SutProvider<AccountsKeyManagementController> sutProvider,
SetKeyConnectorKeyRequestModel data)
{
data.KeyConnectorKeyWrappedUserKey = null;
data.AccountKeys = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(data));
await sutProvider.GetDependency<IUserService>().ReceivedWithAnyArgs(0)
.SetKeyConnectorKeyAsync(Arg.Any<User>(), Arg.Any<string>(), Arg.Any<string>());
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V1_SetKeyConnectorKeyFails_ThrowsBadRequestWithErrorResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
SetKeyConnectorKeyRequestModel data, User expectedUser)
{
data.KeyConnectorKeyWrappedUserKey = null;
data.AccountKeys = null;
expectedUser.PublicKey = null;
expectedUser.PrivateKey = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.SetKeyConnectorKeyAsync(Arg.Any<User>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(IdentityResult.Failed(new IdentityError { Description = "set key connector key error" }));
var badRequestException =
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(data));
Assert.Equal(1, badRequestException.ModelState!.ErrorCount);
Assert.Equal("set key connector key error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
await sutProvider.GetDependency<IUserService>().Received(1)
.SetKeyConnectorKeyAsync(Arg.Do<User>(user =>
{
Assert.Equal(expectedUser.Id, user.Id);
Assert.Equal(data.Key, user.Key);
Assert.Equal(data.Kdf, user.Kdf);
Assert.Equal(data.KdfIterations, user.KdfIterations);
Assert.Equal(data.KdfMemory, user.KdfMemory);
Assert.Equal(data.KdfParallelism, user.KdfParallelism);
Assert.Equal(data.Keys!.PublicKey, user.PublicKey);
Assert.Equal(data.Keys!.EncryptedPrivateKey, user.PrivateKey);
}), Arg.Is(data.Key), Arg.Is(data.OrgIdentifier));
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V1_SetKeyConnectorKeySucceeds_OkResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
SetKeyConnectorKeyRequestModel data, User expectedUser)
{
data.KeyConnectorKeyWrappedUserKey = null;
data.AccountKeys = null;
expectedUser.PublicKey = null;
expectedUser.PrivateKey = null;
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.SetKeyConnectorKeyAsync(Arg.Any<User>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.PostSetKeyConnectorKeyAsync(data);
await sutProvider.GetDependency<IUserService>().Received(1)
.SetKeyConnectorKeyAsync(Arg.Do<User>(user =>
{
Assert.Equal(expectedUser.Id, user.Id);
Assert.Equal(data.Key, user.Key);
Assert.Equal(data.Kdf, user.Kdf);
Assert.Equal(data.KdfIterations, user.KdfIterations);
Assert.Equal(data.KdfMemory, user.KdfMemory);
Assert.Equal(data.KdfParallelism, user.KdfParallelism);
Assert.Equal(data.Keys!.PublicKey, user.PublicKey);
Assert.Equal(data.Keys!.EncryptedPrivateKey, user.PrivateKey);
}), Arg.Is(data.Key), Arg.Is(data.OrgIdentifier));
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V2_UserNull_Throws(
SutProvider<AccountsKeyManagementController> sutProvider)
{
var request = new SetKeyConnectorKeyRequestModel
{
KeyConnectorKeyWrappedUserKey = "wrapped-user-key",
AccountKeys = new AccountKeysRequestModel
{
AccountPublicKey = "public-key",
UserKeyEncryptedAccountPrivateKey = "encrypted-private-key"
},
OrgIdentifier = "test-org"
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(request));
await sutProvider.GetDependency<ISetKeyConnectorKeyCommand>().DidNotReceive()
.SetKeyConnectorKeyForUserAsync(Arg.Any<User>(), Arg.Any<KeyConnectorKeysData>());
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V2_Success(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser)
{
var request = new SetKeyConnectorKeyRequestModel
{
KeyConnectorKeyWrappedUserKey = "wrapped-user-key",
AccountKeys = new AccountKeysRequestModel
{
AccountPublicKey = "public-key",
UserKeyEncryptedAccountPrivateKey = "encrypted-private-key"
},
OrgIdentifier = "test-org"
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
await sutProvider.Sut.PostSetKeyConnectorKeyAsync(request);
await sutProvider.GetDependency<ISetKeyConnectorKeyCommand>().Received(1)
.SetKeyConnectorKeyForUserAsync(Arg.Is(expectedUser),
Arg.Do<KeyConnectorKeysData>(data =>
{
Assert.Equal(request.KeyConnectorKeyWrappedUserKey, data.KeyConnectorKeyWrappedUserKey);
Assert.Equal(request.AccountKeys.AccountPublicKey, data.AccountKeys.AccountPublicKey);
Assert.Equal(request.AccountKeys.UserKeyEncryptedAccountPrivateKey,
data.AccountKeys.UserKeyEncryptedAccountPrivateKey);
Assert.Equal(request.OrgIdentifier, data.OrgIdentifier);
}));
}
[Theory]
[BitAutoData]
public async Task PostSetKeyConnectorKeyAsync_V2_CommandThrows_PropagatesException(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser)
{
var request = new SetKeyConnectorKeyRequestModel
{
KeyConnectorKeyWrappedUserKey = "wrapped-user-key",
AccountKeys = new AccountKeysRequestModel
{
AccountPublicKey = "public-key",
UserKeyEncryptedAccountPrivateKey = "encrypted-private-key"
},
OrgIdentifier = "test-org"
};
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<ISetKeyConnectorKeyCommand>()
.When(x => x.SetKeyConnectorKeyForUserAsync(Arg.Any<User>(), Arg.Any<KeyConnectorKeysData>()))
.Do(_ => throw new BadRequestException("Command failed"));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.PostSetKeyConnectorKeyAsync(request));
Assert.Equal("Command failed", exception.Message);
await sutProvider.GetDependency<ISetKeyConnectorKeyCommand>().Received(1)
.SetKeyConnectorKeyForUserAsync(Arg.Is(expectedUser),
Arg.Do<KeyConnectorKeysData>(data =>
{
Assert.Equal(request.KeyConnectorKeyWrappedUserKey, data.KeyConnectorKeyWrappedUserKey);
Assert.Equal(request.AccountKeys.AccountPublicKey, data.AccountKeys.AccountPublicKey);
Assert.Equal(request.AccountKeys.UserKeyEncryptedAccountPrivateKey,
data.AccountKeys.UserKeyEncryptedAccountPrivateKey);
Assert.Equal(request.OrgIdentifier, data.OrgIdentifier);
}));
}
[Theory]
[BitAutoData]
public async Task PostConvertToKeyConnectorAsync_UserNull_Throws(
SutProvider<AccountsKeyManagementController> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.PostConvertToKeyConnectorAsync());
await sutProvider.GetDependency<IUserService>().ReceivedWithAnyArgs(0)
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string?>());
}
[Theory]
[BitAutoData]
public async Task PostConvertToKeyConnectorAsync_ConvertToKeyConnectorFails_ThrowsBadRequestWithErrorResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string?>())
.Returns(IdentityResult.Failed(new IdentityError { Description = "convert to key connector error" }));
var badRequestException =
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostConvertToKeyConnectorAsync());
Assert.Equal(1, badRequestException.ModelState!.ErrorCount);
Assert.Equal("convert to key connector error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
await sutProvider.GetDependency<IUserService>().Received(1)
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser), Arg.Any<string?>());
}
[Theory]
[BitAutoData]
public async Task PostConvertToKeyConnectorAsync_ConvertToKeyConnectorSucceeds_OkResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string?>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.PostConvertToKeyConnectorAsync();
await sutProvider.GetDependency<IUserService>().Received(1)
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser), Arg.Any<string?>());
}
[Theory]
[BitAutoData]
public async Task PostEnrollToKeyConnectorAsync_UserNull_Throws(
SutProvider<AccountsKeyManagementController> sutProvider,
KeyConnectorEnrollmentRequestModel data)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.PostEnrollToKeyConnectorAsync(data));
await sutProvider.GetDependency<IUserService>().ReceivedWithAnyArgs(0)
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string>());
}
[Theory]
[BitAutoData]
public async Task PostEnrollToKeyConnectorAsync_ConvertToKeyConnectorFails_ThrowsBadRequestWithErrorResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser,
KeyConnectorEnrollmentRequestModel data)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string>())
.Returns(IdentityResult.Failed(new IdentityError { Description = "convert to key connector error" }));
var badRequestException =
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.PostEnrollToKeyConnectorAsync(data));
Assert.Equal(1, badRequestException.ModelState!.ErrorCount);
Assert.Equal("convert to key connector error", badRequestException.ModelState.Root.Errors[0].ErrorMessage);
await sutProvider.GetDependency<IUserService>().Received(1)
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser), Arg.Is(data.KeyConnectorKeyWrappedUserKey));
}
[Theory]
[BitAutoData]
public async Task PostEnrollToKeyConnectorAsync_ConvertToKeyConnectorSucceeds_OkResponse(
SutProvider<AccountsKeyManagementController> sutProvider,
User expectedUser,
KeyConnectorEnrollmentRequestModel data)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IUserService>()
.ConvertToKeyConnectorAsync(Arg.Any<User>(), Arg.Any<string>())
.Returns(IdentityResult.Success);
await sutProvider.Sut.PostEnrollToKeyConnectorAsync(data);
await sutProvider.GetDependency<IUserService>().Received(1)
.ConvertToKeyConnectorAsync(Arg.Is(expectedUser), Arg.Is(data.KeyConnectorKeyWrappedUserKey));
}
[Theory]
[BitAutoData]
public async Task GetKeyConnectorConfirmationDetailsAsync_NoUser_Throws(
SutProvider<AccountsKeyManagementController> sutProvider, string orgSsoIdentifier)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() =>
sutProvider.Sut.GetKeyConnectorConfirmationDetailsAsync(orgSsoIdentifier));
await sutProvider.GetDependency<IKeyConnectorConfirmationDetailsQuery>().ReceivedWithAnyArgs(0)
.Run(Arg.Any<string>(), Arg.Any<Guid>());
}
[Theory]
[BitAutoData]
public async Task GetKeyConnectorConfirmationDetailsAsync_Success(
SutProvider<AccountsKeyManagementController> sutProvider, User expectedUser, string orgSsoIdentifier)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>())
.Returns(expectedUser);
sutProvider.GetDependency<IKeyConnectorConfirmationDetailsQuery>().Run(orgSsoIdentifier, expectedUser.Id)
.Returns(
new KeyConnectorConfirmationDetails { OrganizationName = "test" }
);
var result = await sutProvider.Sut.GetKeyConnectorConfirmationDetailsAsync(orgSsoIdentifier);
Assert.NotNull(result);
Assert.Equal("test", result.OrganizationName);
await sutProvider.GetDependency<IKeyConnectorConfirmationDetailsQuery>().Received(1)
.Run(orgSsoIdentifier, expectedUser.Id);
}
[Theory]
[BitAutoData]
public async Task RotateUserKeysAsync_WhenUserIsNull_Throws(SutProvider<AccountsKeyManagementController> sutProvider, RotateUserKeysRequestModel request)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).ReturnsNull();
await Assert.ThrowsAsync<UnauthorizedAccessException>(() =>
sutProvider.Sut.RotateUserKeysAsync(request));
}
[Theory]
[BitMemberAutoData(nameof(UnimplementedUnlockMethods))]
public async Task RotateUserKeysAsync_UnimplementedUnlockMethod_Throws(UnlockMethodRequestModel unlockMethod,
SutProvider<AccountsKeyManagementController> sutProvider, RotateUserKeysRequestModel request, User user)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
request.UnlockMethodData = unlockMethod;
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.RotateUserKeysAsync(request));
}
[Theory]
[BitAutoData]
public async Task RotateUserKeysAsync_MasterPassword_Success(
SutProvider<AccountsKeyManagementController> sutProvider, RotateUserKeysRequestModel request, User user)
{
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
request = SetupValidRotateUserKeysRequest(request);
await sutProvider.Sut.RotateUserKeysAsync(request);
await AssertCommonValidatorsCalledAsync(sutProvider, request);
await sutProvider.GetDependency<IRotateUserAccountKeysCommand>().Received(1)
.MasterPasswordRotateUserAccountKeysAsync(Arg.Is(user), Arg.Is<MasterPasswordRotateUserAccountKeysData>(d =>
d.MasterPasswordUnlockData.Kdf.KdfType == request.UnlockMethodData.MasterPasswordUnlockData!.Kdf.KdfType
&& d.MasterPasswordUnlockData.Kdf.Iterations == request.UnlockMethodData.MasterPasswordUnlockData.Kdf.Iterations
&& d.MasterPasswordUnlockData.Kdf.Memory == request.UnlockMethodData.MasterPasswordUnlockData.Kdf.Memory
&& d.MasterPasswordUnlockData.Kdf.Parallelism == request.UnlockMethodData.MasterPasswordUnlockData.Kdf.Parallelism
&& d.MasterPasswordUnlockData.Salt == request.UnlockMethodData.MasterPasswordUnlockData.Salt
&& d.MasterPasswordUnlockData.MasterKeyWrappedUserKey == request.UnlockMethodData.MasterPasswordUnlockData.MasterKeyWrappedUserKey
&& d.BaseData.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == request.WrappedAccountCryptographicState.PublicKeyEncryptionKeyPair.WrappedPrivateKey
&& d.BaseData.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey == request.WrappedAccountCryptographicState.PublicKeyEncryptionKeyPair.PublicKey
&& d.BaseData.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey == request.WrappedAccountCryptographicState.PublicKeyEncryptionKeyPair.SignedPublicKey
&& d.BaseData.AccountKeys.SignatureKeyPairData!.SignatureAlgorithm == Core.KeyManagement.Enums.SignatureAlgorithm.Ed25519
&& d.BaseData.AccountKeys.SignatureKeyPairData.WrappedSigningKey == request.WrappedAccountCryptographicState.SignatureKeyPair.WrappedSigningKey
&& d.BaseData.AccountKeys.SignatureKeyPairData.VerifyingKey == request.WrappedAccountCryptographicState.SignatureKeyPair.VerifyingKey
));
}
private static async Task AssertCommonValidatorsCalledAsync(SutProvider<AccountsKeyManagementController> sutProvider, RotateUserKeysRequestModel request)
{
await sutProvider.GetDependency<IRotationValidator<IEnumerable<EmergencyAccessWithIdRequestModel>, IEnumerable<EmergencyAccess>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.UnlockData.EmergencyAccessUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.UnlockData.OrganizationAccountRecoveryUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.UnlockData.PasskeyUnlockData));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.AccountData.Ciphers));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.AccountData.Folders));
await sutProvider.GetDependency<IRotationValidator<IEnumerable<SendWithIdRequestModel>, IReadOnlyList<Send>>>().Received(1)
.ValidateAsync(Arg.Any<User>(), Arg.Is(request.AccountData.Sends));
}
private static RotateUserKeysRequestModel SetupValidRotateUserKeysRequest(RotateUserKeysRequestModel request)
{
request.WrappedAccountCryptographicState.SignatureKeyPair = new SignatureKeyPairRequestModel
{
SignatureAlgorithm = "ed25519",
WrappedSigningKey = "wrappedSigningKey",
VerifyingKey = "verifyingKey"
};
request.UnlockMethodData = new UnlockMethodRequestModel()
{
UnlockMethod = UnlockMethod.MasterPassword,
MasterPasswordUnlockData = new MasterPasswordUnlockDataRequestModel()
{
Salt = "test",
MasterKeyWrappedUserKey = "test",
Kdf = new KdfRequestModel()
{
Iterations = 6000,
KdfType = KdfType.PBKDF2_SHA256,
}
},
KeyConnectorKeyWrappedUserKey = null,
};
return request;
}
}