mirror of
https://github.com/bitwarden/server
synced 2025-12-31 07:33:43 +00:00
[PM-27280] Support v2 encryption on key-connector signups (#6712)
* account v2 registration for key connector * use new user repository functions * test coverage * integration test coverage * documentation * code review * missing test coverage * fix failing test * failing test * incorrect ticket number * moved back request model to Api, created dedicated data class in Core * sql stored procedure type mismatch, simplification * key connector authorization handler
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.KeyManagement.Authorization;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Authorization;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class KeyConnectorAuthorizationHandlerTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UserCanUseKeyConnector_Success(
|
||||
User user,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations
|
||||
.Returns(new List<CurrentContextOrganization>());
|
||||
|
||||
var requirement = KeyConnectorOperations.Use;
|
||||
var context = new AuthorizationHandlerContext([requirement], claimsPrincipal, user);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UserAlreadyUsesKeyConnector_Fails(
|
||||
User user,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = true;
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations
|
||||
.Returns(new List<CurrentContextOrganization>());
|
||||
|
||||
var requirement = KeyConnectorOperations.Use;
|
||||
var context = new AuthorizationHandlerContext([requirement], claimsPrincipal, user);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UserIsOwner_Fails(
|
||||
User user,
|
||||
Guid organizationId,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
var organizations = new List<CurrentContextOrganization>
|
||||
{
|
||||
new() { Id = organizationId, Type = OrganizationUserType.Owner }
|
||||
};
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns(organizations);
|
||||
|
||||
var requirement = KeyConnectorOperations.Use;
|
||||
var context = new AuthorizationHandlerContext([requirement], claimsPrincipal, user);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UserIsAdmin_Fails(
|
||||
User user,
|
||||
Guid organizationId,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
var organizations = new List<CurrentContextOrganization>
|
||||
{
|
||||
new() { Id = organizationId, Type = OrganizationUserType.Admin }
|
||||
};
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns(organizations);
|
||||
|
||||
var requirement = KeyConnectorOperations.Use;
|
||||
var context = new AuthorizationHandlerContext([requirement], claimsPrincipal, user);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UserIsRegularMember_Success(
|
||||
User user,
|
||||
Guid organizationId,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
var organizations = new List<CurrentContextOrganization>
|
||||
{
|
||||
new() { Id = organizationId, Type = OrganizationUserType.User }
|
||||
};
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns(organizations);
|
||||
|
||||
var requirement = KeyConnectorOperations.Use;
|
||||
var context = new AuthorizationHandlerContext([requirement], claimsPrincipal, user);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.HandleAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.HasSucceeded);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleRequirementAsync_UnsupportedRequirement_ThrowsArgumentException(
|
||||
User user,
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
SutProvider<KeyConnectorAuthorizationHandler> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().Organizations
|
||||
.Returns(new List<CurrentContextOrganization>());
|
||||
|
||||
var unsupportedRequirement = new KeyConnectorOperationsRequirement("UnsupportedOperation");
|
||||
var context = new AuthorizationHandlerContext([unsupportedRequirement], claimsPrincipal, user);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => sutProvider.Sut.HandleAsync(context));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Commands;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SetKeyConnectorKeyCommandTests
|
||||
{
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SetKeyConnectorKeyForUserAsync_Success_SetsAccountKeys(
|
||||
User user,
|
||||
KeyConnectorKeysData data,
|
||||
SutProvider<SetKeyConnectorKeyCommand> sutProvider)
|
||||
{
|
||||
// Set up valid V2 encryption data
|
||||
if (data.AccountKeys!.SignatureKeyPair != null)
|
||||
{
|
||||
data.AccountKeys.SignatureKeyPair.SignatureAlgorithm = "ed25519";
|
||||
}
|
||||
|
||||
var expectedAccountKeysData = data.AccountKeys.ToAccountKeysData();
|
||||
|
||||
// Arrange
|
||||
user.UsesKeyConnector = false;
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
var httpContext = Substitute.For<HttpContext>();
|
||||
httpContext.User.Returns(new ClaimsPrincipal());
|
||||
currentContext.HttpContext.Returns(httpContext);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), user, Arg.Any<IEnumerable<IAuthorizationRequirement>>())
|
||||
.Returns(AuthorizationResult.Success());
|
||||
|
||||
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||
var mockUpdateUserData = Substitute.For<UpdateUserData>();
|
||||
userRepository.SetKeyConnectorUserKey(user.Id, data.KeyConnectorKeyWrappedUserKey!)
|
||||
.Returns(mockUpdateUserData);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.SetKeyConnectorKeyForUserAsync(user, data);
|
||||
|
||||
// Assert
|
||||
|
||||
userRepository
|
||||
.Received(1)
|
||||
.SetKeyConnectorUserKey(user.Id, data.KeyConnectorKeyWrappedUserKey);
|
||||
|
||||
await userRepository
|
||||
.Received(1)
|
||||
.SetV2AccountCryptographicStateAsync(
|
||||
user.Id,
|
||||
Arg.Is<UserAccountKeysData>(data =>
|
||||
data.PublicKeyEncryptionKeyPairData.PublicKey == expectedAccountKeysData.PublicKeyEncryptionKeyPairData.PublicKey &&
|
||||
data.PublicKeyEncryptionKeyPairData.WrappedPrivateKey == expectedAccountKeysData.PublicKeyEncryptionKeyPairData.WrappedPrivateKey &&
|
||||
data.PublicKeyEncryptionKeyPairData.SignedPublicKey == expectedAccountKeysData.PublicKeyEncryptionKeyPairData.SignedPublicKey &&
|
||||
data.SignatureKeyPairData!.SignatureAlgorithm == expectedAccountKeysData.SignatureKeyPairData!.SignatureAlgorithm &&
|
||||
data.SignatureKeyPairData.WrappedSigningKey == expectedAccountKeysData.SignatureKeyPairData.WrappedSigningKey &&
|
||||
data.SignatureKeyPairData.VerifyingKey == expectedAccountKeysData.SignatureKeyPairData.VerifyingKey &&
|
||||
data.SecurityStateData!.SecurityState == expectedAccountKeysData.SecurityStateData!.SecurityState &&
|
||||
data.SecurityStateData.SecurityVersion == expectedAccountKeysData.SecurityStateData.SecurityVersion),
|
||||
Arg.Is<IEnumerable<UpdateUserData>>(actions =>
|
||||
actions.Count() == 1 && actions.First() == mockUpdateUserData));
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector);
|
||||
|
||||
await sutProvider.GetDependency<IAcceptOrgUserCommand>()
|
||||
.Received(1)
|
||||
.AcceptOrgUserByOrgSsoIdAsync(data.OrgIdentifier, user, sutProvider.GetDependency<IUserService>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SetKeyConnectorKeyForUserAsync_UserCantUseKeyConnector_ThrowsException(
|
||||
User user,
|
||||
KeyConnectorKeysData data,
|
||||
SutProvider<SetKeyConnectorKeyCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
user.UsesKeyConnector = true;
|
||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
||||
var httpContext = Substitute.For<HttpContext>();
|
||||
httpContext.User.Returns(new ClaimsPrincipal());
|
||||
currentContext.HttpContext.Returns(httpContext);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), user, Arg.Any<IEnumerable<IAuthorizationRequirement>>())
|
||||
.Returns(AuthorizationResult.Failed());
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SetKeyConnectorKeyForUserAsync(user, data));
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SetKeyConnectorUserKey(Arg.Any<Guid>(), Arg.Any<string>());
|
||||
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SetV2AccountCryptographicStateAsync(Arg.Any<Guid>(), Arg.Any<UserAccountKeysData>(), Arg.Any<IEnumerable<UpdateUserData>>());
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogUserEventAsync(Arg.Any<Guid>(), Arg.Any<EventType>());
|
||||
|
||||
await sutProvider.GetDependency<IAcceptOrgUserCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.AcceptOrgUserByOrgSsoIdAsync(Arg.Any<string>(), Arg.Any<User>(), Arg.Any<IUserService>());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user