1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 08:32:50 -05:00

Merge branch 'master' into flexible-collections/deprecate-custom-collection-perm

# Conflicts:
#	src/Api/AdminConsole/Controllers/OrganizationUsersController.cs
This commit is contained in:
Rui Tome
2023-11-02 15:20:58 +00:00
31 changed files with 2172 additions and 301 deletions

View File

@ -2,6 +2,7 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
@ -27,7 +28,7 @@ public class OrganizationUsersControllerTests
await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1).AcceptUserAsync(orgId, user, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(1).AcceptOrgUserByOrgIdAsync(orgId, user, sutProvider.GetDependency<IUserService>());
}
[Theory]
@ -41,7 +42,7 @@ public class OrganizationUsersControllerTests
await sutProvider.Sut.PutResetPasswordEnrollment(orgId, userId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(0).AcceptUserAsync(orgId, user, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(0).AcceptOrgUserByOrgIdAsync(orgId, user, sutProvider.GetDependency<IUserService>());
}
[Theory]
@ -63,8 +64,8 @@ public class OrganizationUsersControllerTests
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(1)
.AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
.UpdateUserResetPasswordEnrollmentAsync(default, default, default, default);
}
@ -85,8 +86,8 @@ public class OrganizationUsersControllerTests
await sutProvider.Sut.Accept(orgId, orgUserId, model);
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.AcceptUserAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(1)
.AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, sutProvider.GetDependency<IUserService>());
await sutProvider.GetDependency<IOrganizationService>().Received(1)
.UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id);
}

View File

@ -4,6 +4,7 @@ using Bit.Api.Controllers;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -14,6 +15,7 @@ using Bit.Core.Settings;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.Services;
using Bit.Core.Vault.Repositories;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using Xunit;
@ -37,6 +39,7 @@ public class AccountsControllerTests : IDisposable
private readonly IProviderUserRepository _providerUserRepository;
private readonly ICaptchaValidationService _captchaValidationService;
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
public AccountsControllerTests()
{
@ -53,6 +56,8 @@ public class AccountsControllerTests : IDisposable
_sendService = Substitute.For<ISendService>();
_captchaValidationService = Substitute.For<ICaptchaValidationService>();
_policyService = Substitute.For<IPolicyService>();
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
_sut = new AccountsController(
_globalSettings,
_cipherRepository,
@ -66,7 +71,8 @@ public class AccountsControllerTests : IDisposable
_sendRepository,
_sendService,
_captchaValidationService,
_policyService
_policyService,
_setInitialMasterPasswordCommand
);
}
@ -381,6 +387,124 @@ public class AccountsControllerTests : IDisposable
);
}
[Theory]
[BitAutoData(true, false)] // User has PublicKey and PrivateKey, and Keys in request are NOT null
[BitAutoData(true, true)] // User has PublicKey and PrivateKey, and Keys in request are null
[BitAutoData(false, false)] // User has neither PublicKey nor PrivateKey, and Keys in request are NOT null
[BitAutoData(false, true)] // User has neither PublicKey nor PrivateKey, and Keys in request are null
public async Task PostSetPasswordAsync_WhenUserExistsAndSettingPasswordSucceeds_ShouldHandleKeysCorrectlyAndReturn(
bool hasExistingKeys,
bool shouldSetKeysToNull,
User user,
SetPasswordRequestModel setPasswordRequestModel)
{
// Arrange
const string existingPublicKey = "existingPublicKey";
const string existingEncryptedPrivateKey = "existingEncryptedPrivateKey";
const string newPublicKey = "newPublicKey";
const string newEncryptedPrivateKey = "newEncryptedPrivateKey";
if (hasExistingKeys)
{
user.PublicKey = existingPublicKey;
user.PrivateKey = existingEncryptedPrivateKey;
}
else
{
user.PublicKey = null;
user.PrivateKey = null;
}
if (shouldSetKeysToNull)
{
setPasswordRequestModel.Keys = null;
}
else
{
setPasswordRequestModel.Keys = new KeysRequestModel()
{
PublicKey = newPublicKey,
EncryptedPrivateKey = newEncryptedPrivateKey
};
}
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(
user,
setPasswordRequestModel.MasterPasswordHash,
setPasswordRequestModel.Key,
setPasswordRequestModel.OrgIdentifier)
.Returns(Task.FromResult(IdentityResult.Success));
// Act
await _sut.PostSetPasswordAsync(setPasswordRequestModel);
// Assert
await _setInitialMasterPasswordCommand.Received(1)
.SetInitialMasterPasswordAsync(
Arg.Is<User>(u => u == user),
Arg.Is<string>(s => s == setPasswordRequestModel.MasterPasswordHash),
Arg.Is<string>(s => s == setPasswordRequestModel.Key),
Arg.Is<string>(s => s == setPasswordRequestModel.OrgIdentifier));
// Additional Assertions for User object modifications
Assert.Equal(setPasswordRequestModel.MasterPasswordHint, user.MasterPasswordHint);
Assert.Equal(setPasswordRequestModel.Kdf, user.Kdf);
Assert.Equal(setPasswordRequestModel.KdfIterations, user.KdfIterations);
Assert.Equal(setPasswordRequestModel.KdfMemory, user.KdfMemory);
Assert.Equal(setPasswordRequestModel.KdfParallelism, user.KdfParallelism);
Assert.Equal(setPasswordRequestModel.Key, user.Key);
if (hasExistingKeys)
{
// User Keys should not be modified
Assert.Equal(existingPublicKey, user.PublicKey);
Assert.Equal(existingEncryptedPrivateKey, user.PrivateKey);
}
else if (!shouldSetKeysToNull)
{
// User had no keys so they should be set to the request model's keys
Assert.Equal(setPasswordRequestModel.Keys.PublicKey, user.PublicKey);
Assert.Equal(setPasswordRequestModel.Keys.EncryptedPrivateKey, user.PrivateKey);
}
else
{
// User had no keys and the request model's keys were null, so they should be set to null
Assert.Null(user.PublicKey);
Assert.Null(user.PrivateKey);
}
}
[Theory]
[BitAutoData]
public async Task PostSetPasswordAsync_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException(
SetPasswordRequestModel setPasswordRequestModel)
{
// Arrange
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult((User)null));
// Act & Assert
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => _sut.PostSetPasswordAsync(setPasswordRequestModel));
}
[Theory]
[BitAutoData]
public async Task PostSetPasswordAsync_WhenSettingPasswordFails_ShouldThrowBadRequestException(
User user,
SetPasswordRequestModel model)
{
// Arrange
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(Arg.Any<User>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>())
.Returns(Task.FromResult(IdentityResult.Failed(new IdentityError { Description = "Some Error" })));
// Act & Assert
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostSetPasswordAsync(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

View File

@ -0,0 +1,133 @@
using System.Security.Claims;
using System.Text.Json;
using Bit.Api.AdminConsole.Controllers;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.Controllers;
// Note: test names follow MethodName_StateUnderTest_ExpectedBehavior pattern.
[ControllerCustomize(typeof(PoliciesController))]
[SutProviderCustomize]
public class PoliciesControllerTests
{
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenCalled_ReturnsMasterPasswordPolicy(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser,
Policy policy, MasterPasswordPolicyData mpPolicyData)
{
// Arrange
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns((Guid?)userId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(orgId, userId)
.Returns(orgUser);
policy.Type = PolicyType.MasterPassword;
policy.Enabled = true;
// data should be a JSON serialized version of the mpPolicyData object
policy.Data = JsonSerializer.Serialize(mpPolicyData);
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword)
.Returns(policy);
// Act
var result = await sutProvider.Sut.GetMasterPasswordPolicy(orgId);
// Assert
Assert.NotNull(result);
Assert.Equal(policy.Id, result.Id);
Assert.Equal(policy.Type, result.Type);
Assert.Equal(policy.Enabled, result.Enabled);
// Assert that the data is deserialized correctly into a Dictionary<string, object>
// for all MasterPasswordPolicyData properties
Assert.Equal(mpPolicyData.MinComplexity, ((JsonElement)result.Data["MinComplexity"]).GetInt32());
Assert.Equal(mpPolicyData.MinLength, ((JsonElement)result.Data["MinLength"]).GetInt32());
Assert.Equal(mpPolicyData.RequireLower, ((JsonElement)result.Data["RequireLower"]).GetBoolean());
Assert.Equal(mpPolicyData.RequireUpper, ((JsonElement)result.Data["RequireUpper"]).GetBoolean());
Assert.Equal(mpPolicyData.RequireNumbers, ((JsonElement)result.Data["RequireNumbers"]).GetBoolean());
Assert.Equal(mpPolicyData.RequireSpecial, ((JsonElement)result.Data["RequireSpecial"]).GetBoolean());
Assert.Equal(mpPolicyData.EnforceOnLogin, ((JsonElement)result.Data["EnforceOnLogin"]).GetBoolean());
}
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_OrgUserIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId)
{
// Arrange
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns((Guid?)userId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(orgId, userId)
.Returns((OrganizationUser)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_PolicyIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser)
{
// Arrange
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns((Guid?)userId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(orgId, userId)
.Returns(orgUser);
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword)
.Returns((Policy)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_PolicyNotEnabled_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser, Policy policy)
{
// Arrange
sutProvider.GetDependency<IUserService>()
.GetProperUserId(Arg.Any<ClaimsPrincipal>())
.Returns((Guid?)userId);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(orgId, userId)
.Returns(orgUser);
policy.Enabled = false; // Ensuring the policy is not enabled
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword)
.Returns(policy);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}
}