using System.Security.Claims; using System.Text.Json; using Bit.Api.AdminConsole.Controllers; 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.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 sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser, Policy policy, MasterPasswordPolicyData mpPolicyData, Organization organization) { // Arrange organization.UsePolicies = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); sutProvider.GetDependency() .GetProperUserId(Arg.Any()) .Returns((Guid?)userId); sutProvider.GetDependency() .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() .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 // 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 sutProvider, Guid orgId, Guid userId) { // Arrange sutProvider.GetDependency() .GetProperUserId(Arg.Any()) .Returns((Guid?)userId); sutProvider.GetDependency() .GetByOrganizationAsync(orgId, userId) .Returns((OrganizationUser)null); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId)); } [Theory] [BitAutoData] public async Task GetMasterPasswordPolicy_PolicyIsNull_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser) { // Arrange sutProvider.GetDependency() .GetProperUserId(Arg.Any()) .Returns((Guid?)userId); sutProvider.GetDependency() .GetByOrganizationAsync(orgId, userId) .Returns(orgUser); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword) .Returns((Policy)null); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId)); } [Theory] [BitAutoData] public async Task GetMasterPasswordPolicy_PolicyNotEnabled_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid userId, OrganizationUser orgUser, Policy policy) { // Arrange sutProvider.GetDependency() .GetProperUserId(Arg.Any()) .Returns((Guid?)userId); sutProvider.GetDependency() .GetByOrganizationAsync(orgId, userId) .Returns(orgUser); policy.Enabled = false; // Ensuring the policy is not enabled sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(orgId, PolicyType.MasterPassword) .Returns(policy); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId)); } [Theory] [BitAutoData] public async Task GetMasterPasswordPolicy_WhenUsePoliciesIsFalse_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId) { // Arrange var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns((Organization)null); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId)); } [Theory] [BitAutoData] public async Task GetMasterPasswordPolicy_WhenOrgIsNull_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Organization organization) { // Arrange organization.UsePolicies = false; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetMasterPasswordPolicy(orgId)); } [Theory] [BitAutoData] public async Task Get_WhenUserCanManagePolicies_WithExistingType_ReturnsExistingPolicy( SutProvider sutProvider, Guid orgId, Policy policy, int type) { // Arrange sutProvider.GetDependency() .ManagePolicies(orgId) .Returns(true); policy.Type = (PolicyType)type; policy.Enabled = true; policy.Data = null; sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(orgId, (PolicyType)type) .Returns(policy); // Act var result = await sutProvider.Sut.Get(orgId, type); // Assert Assert.IsType(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 sutProvider, Guid orgId, int type) { // Arrange sutProvider.GetDependency() .ManagePolicies(orgId) .Returns(true); sutProvider.GetDependency() .GetByOrganizationIdTypeAsync(orgId, (PolicyType)type) .Returns((Policy)null); // Act var result = await sutProvider.Sut.Get(orgId, type); // Assert Assert.IsType(result); Assert.Equal(result.Type, (PolicyType)type); Assert.False(result.Enabled); } [Theory] [BitAutoData] public async Task Get_WhenUserCannotManagePolicies_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, int type) { // Arrange sutProvider.GetDependency() .ManagePolicies(orgId) .Returns(false); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.Get(orgId, type)); } [Theory] [BitAutoData] public async Task GetByToken_WhenOrganizationUseUsePoliciesIsFalse_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email, Organization organization) { // Arrange organization.UsePolicies = false; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId)); } [Theory] [BitAutoData] public async Task GetByToken_WhenOrganizationIsNull_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email) { // Arrange var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns((Organization)null); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId)); } [Theory] [BitAutoData] public async Task GetByToken_WhenTokenIsInvalid_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email, Organization organization ) { // Arrange organization.UsePolicies = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); var decryptedToken = Substitute.For(); decryptedToken.Valid.Returns(false); var orgUserInviteTokenDataFactory = sutProvider.GetDependency>(); orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any()) .Returns(x => { x[1] = decryptedToken; return true; }); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId)); } [Theory] [BitAutoData] public async Task GetByToken_WhenUserIsNull_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email, Organization organization ) { // Arrange organization.UsePolicies = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); var decryptedToken = Substitute.For(); decryptedToken.Valid.Returns(true); decryptedToken.OrgUserId = organizationUserId; decryptedToken.OrgUserEmail = email; var orgUserInviteTokenDataFactory = sutProvider.GetDependency>(); orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any()) .Returns(x => { x[1] = decryptedToken; return true; }); sutProvider.GetDependency() .GetByIdAsync(organizationUserId) .Returns((OrganizationUser)null); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId)); } [Theory] [BitAutoData] public async Task GetByToken_WhenUserOrgIdDoesNotMatchOrgId_ThrowsNotFoundException( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email, OrganizationUser orgUser, Organization organization ) { // Arrange organization.UsePolicies = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); var decryptedToken = Substitute.For(); decryptedToken.Valid.Returns(true); decryptedToken.OrgUserId = organizationUserId; decryptedToken.OrgUserEmail = email; var orgUserInviteTokenDataFactory = sutProvider.GetDependency>(); orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any()) .Returns(x => { x[1] = decryptedToken; return true; }); orgUser.OrganizationId = Guid.Empty; sutProvider.GetDependency() .GetByIdAsync(organizationUserId) .Returns(orgUser); // Act & Assert await Assert.ThrowsAsync(() => sutProvider.Sut.GetByToken(orgId, email, token, organizationUserId)); } [Theory] [BitAutoData] public async Task GetByToken_ShouldReturnEnabledPolicies( SutProvider sutProvider, Guid orgId, Guid organizationUserId, string token, string email, OrganizationUser orgUser, Organization organization ) { // Arrange organization.UsePolicies = true; var organizationRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(orgId).Returns(organization); var decryptedToken = Substitute.For(); decryptedToken.Valid.Returns(true); decryptedToken.OrgUserId = organizationUserId; decryptedToken.OrgUserEmail = email; var orgUserInviteTokenDataFactory = sutProvider.GetDependency>(); orgUserInviteTokenDataFactory.TryUnprotect(token, out Arg.Any()) .Returns(x => { x[1] = decryptedToken; return true; }); orgUser.OrganizationId = orgId; sutProvider.GetDependency() .GetByIdAsync(organizationUserId) .Returns(orgUser); var enabledPolicy = Substitute.For(); enabledPolicy.Enabled = true; var disabledPolicy = Substitute.For(); disabledPolicy.Enabled = false; var policies = new[] { enabledPolicy, disabledPolicy }; sutProvider.GetDependency() .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); } }