1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00
Files
server/test/Api.Test/Controllers/PoliciesControllerTests.cs
Vincent Salucci ded1c58c27 [PM-26426] [PM-26427] Remove feature flag - policy validators/requirements refactor (#6674)
* chore: remove ff from PoliciesController, refs PM-26426

* chore: remove ff from public PoliciesController, refs PM-26426

* chore: remove ff from VerifyOrganizationDomainCommands, refs PM-26426

* chore: remove ff from SsoConfigService, refs PM-26426

* chore: remove ff from public PoliciesControllerTests, refs PM-26426

* chore: remove ff from PoliciesControllerTests, refs PM-26426

* chore: remove ff from VerifyOrganizationDomainCommandTests, refs PM-26426

* chore: remove ff from SsoConfigServiceTests, refs PM-26426

* chore: remove ff definition, refs PM-26427

* chore: dotnet format

* chore: remove unused constructor parameters, refs PM-26426

* chore: fix failing tests for VerifyOrganizationDomainCommandTests and SsoConfigServiceTests, refs PM-26426
2025-12-03 10:42:54 -06:00

509 lines
18 KiB
C#

using System.Security.Claims;
using System.Text.Json;
using Bit.Api.AdminConsole.Controllers;
using Bit.Api.AdminConsole.Models.Request;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tokens;
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,
Organization organization)
{
// Arrange
organization.UsePolicies = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
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));
}
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenUsePoliciesIsFalse_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId)
{
// Arrange
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}
[Theory]
[BitAutoData]
public async Task GetMasterPasswordPolicy_WhenOrgIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Organization organization)
{
// Arrange
organization.UsePolicies = false;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId));
}
[Theory]
[BitAutoData]
public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExistingPolicy(
SutProvider<PoliciesController> sutProvider, Guid orgId, Policy policy, int type)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(true);
policy.Type = (PolicyType)type;
policy.Enabled = true;
policy.Data = null;
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns(policy);
// Act
var result = await sutProvider.Sut.Get(orgId, type);
// Assert
Assert.IsType<PolicyDetailResponseModel>(result);
Assert.Equal(policy.Id, result.Id);
Assert.Equal(policy.Type, result.Type);
Assert.Equal(policy.Enabled, result.Enabled);
Assert.Equal(policy.OrganizationId, result.OrganizationId);
}
[Theory]
[BitAutoData]
public async Task Get_WhenUserCanManagePolicies_WithNonExistingType_ReturnsDefaultPolicy(
SutProvider<PoliciesController> sutProvider, Guid orgId, int type)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(true);
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(orgId, (PolicyType)type)
.Returns((Policy)null);
// Act
var result = await sutProvider.Sut.Get(orgId, type);
// Assert
Assert.IsType<PolicyDetailResponseModel>(result);
Assert.Equal(result.Type, (PolicyType)type);
Assert.False(result.Enabled);
}
[Theory]
[BitAutoData]
public async Task Get_WhenUserCannotManagePolicies_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, int type)
{
// Arrange
sutProvider.GetDependency<ICurrentContext>()
.ManagePolicies(orgId)
.Returns(false);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Get(orgId, type));
}
[Theory]
[BitAutoData]
public async Task GetByToken_WhenOrganizationUseUsePoliciesIsFalse_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email,
Organization organization)
{
// Arrange
organization.UsePolicies = false;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
[Theory]
[BitAutoData]
public async Task GetByToken_WhenOrganizationIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider, Guid orgId, Guid organizationUserId, string token, string email)
{
// Arrange
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns((Organization)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
[Theory]
[BitAutoData]
public async Task GetByToken_WhenTokenIsInvalid_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Guid organizationUserId,
string token,
string email,
Organization organization
)
{
// Arrange
organization.UsePolicies = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
decryptedToken.Valid.Returns(false);
var orgUserInviteTokenDataFactory =
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
.Returns(x =>
{
x[1] = decryptedToken;
return true;
});
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
[Theory]
[BitAutoData]
public async Task GetByToken_WhenUserIsNull_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Guid organizationUserId,
string token,
string email,
Organization organization
)
{
// Arrange
organization.UsePolicies = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
decryptedToken.Valid.Returns(true);
decryptedToken.OrgUserId = organizationUserId;
decryptedToken.OrgUserEmail = email;
var orgUserInviteTokenDataFactory =
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
.Returns(x =>
{
x[1] = decryptedToken;
return true;
});
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
.Returns((OrganizationUser)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
[Theory]
[BitAutoData]
public async Task GetByToken_WhenUserOrgIdDoesNotMatchOrgId_ThrowsNotFoundException(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Guid organizationUserId,
string token,
string email,
OrganizationUser orgUser,
Organization organization
)
{
// Arrange
organization.UsePolicies = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
decryptedToken.Valid.Returns(true);
decryptedToken.OrgUserId = organizationUserId;
decryptedToken.OrgUserEmail = email;
var orgUserInviteTokenDataFactory =
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
.Returns(x =>
{
x[1] = decryptedToken;
return true;
});
orgUser.OrganizationId = Guid.Empty;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
.Returns(orgUser);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId));
}
[Theory]
[BitAutoData]
public async Task GetByToken_ShouldReturnEnabledPolicies(
SutProvider<PoliciesController> sutProvider,
Guid orgId,
Guid organizationUserId,
string token,
string email,
OrganizationUser orgUser,
Organization organization
)
{
// Arrange
organization.UsePolicies = true;
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
organizationRepository.GetByIdAsync(orgId).Returns(organization);
var decryptedToken = Substitute.For<OrgUserInviteTokenable>();
decryptedToken.Valid.Returns(true);
decryptedToken.OrgUserId = organizationUserId;
decryptedToken.OrgUserEmail = email;
var orgUserInviteTokenDataFactory =
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>();
orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any<OrgUserInviteTokenable>())
.Returns(x =>
{
x[1] = decryptedToken;
return true;
});
orgUser.OrganizationId = orgId;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
.Returns(orgUser);
var enabledPolicy = Substitute.For<Policy>();
enabledPolicy.Enabled = true;
var disabledPolicy = Substitute.For<Policy>();
disabledPolicy.Enabled = false;
var policies = new[] { enabledPolicy, disabledPolicy };
sutProvider.GetDependency<IPolicyRepository>()
.GetManyByOrganizationIdAsync(orgId)
.Returns(policies);
// Act
var result = await sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId);
// Assert
var expectedPolicy = result.Data.Single();
Assert.NotNull(result);
Assert.Equal(enabledPolicy.Id, expectedPolicy.Id);
Assert.Equal(enabledPolicy.Type, expectedPolicy.Type);
Assert.Equal(enabledPolicy.Enabled, expectedPolicy.Enabled);
}
[Theory]
[BitAutoData]
public async Task PutVNext_UsesVNextSavePolicyCommand(
SutProvider<PoliciesController> sutProvider, Guid orgId,
SavePolicyRequest model, Policy policy, Guid userId)
{
// Arrange
policy.Data = null;
sutProvider.GetDependency<ICurrentContext>()
.UserId
.Returns(userId);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(orgId)
.Returns(true);
sutProvider.GetDependency<IVNextSavePolicyCommand>()
.SaveAsync(Arg.Any<SavePolicyModel>())
.Returns(policy);
// Act
var result = await sutProvider.Sut.PutVNext(orgId, policy.Type, model);
// Assert
await sutProvider.GetDependency<IVNextSavePolicyCommand>()
.Received(1)
.SaveAsync(Arg.Is<SavePolicyModel>(m => m.PolicyUpdate.OrganizationId == orgId &&
m.PolicyUpdate.Type == policy.Type &&
m.PolicyUpdate.Enabled == model.Policy.Enabled &&
m.PerformedBy.UserId == userId &&
m.PerformedBy.IsOrganizationOwnerOrProvider == true));
await sutProvider.GetDependency<ISavePolicyCommand>()
.DidNotReceiveWithAnyArgs()
.VNextSaveAsync(default);
Assert.NotNull(result);
Assert.Equal(policy.Id, result.Id);
Assert.Equal(policy.Type, result.Type);
}
}