1
0
mirror of https://github.com/bitwarden/server synced 2025-12-31 15:43:16 +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:
Maciej Zieniuk
2025-12-18 19:43:03 +01:00
committed by GitHub
parent 2b742b0343
commit a92d7ac129
22 changed files with 1283 additions and 50 deletions

View File

@@ -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));
}
}

View File

@@ -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>());
}
}