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:
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
133
test/Api.Test/Controllers/PoliciesControllerTests.cs
Normal file
133
test/Api.Test/Controllers/PoliciesControllerTests.cs
Normal 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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user