mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
Refactor policy checks (#1536)
* Move policy checking logic inside PolicyService * Refactor to use currentContext.ManagePolicies * Make orgUser status check more semantic * Fix single org user checks * Use CoreHelper implementation to deserialize json * Refactor policy checks to use db query * Use new db query for enforcing 2FA Policy * Add Policy_ReadByTypeApplicableToUser * Stub out EF implementations * Refactor: use PolicyRepository only * Refactor tests * Copy SQL queries to proj and update sqlproj file * Refactor importCiphersAsync to use new method * Add EF implementations and tests * Refactor SQL to remove unnecessary operations
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
using AutoFixture;
|
||||
using AutoMapper;
|
||||
using Bit.Core.Models.EntityFramework;
|
||||
using Bit.Core.Models.EntityFramework.Provider;
|
||||
using System.Collections.Generic;
|
||||
using AutoFixture.Kernel;
|
||||
using System;
|
||||
@ -76,6 +77,9 @@ namespace Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures
|
||||
cfg.AddProfile<InstallationMapperProfile>();
|
||||
cfg.AddProfile<OrganizationMapperProfile>();
|
||||
cfg.AddProfile<OrganizationUserMapperProfile>();
|
||||
cfg.AddProfile<ProviderMapperProfile>();
|
||||
cfg.AddProfile<ProviderUserMapperProfile>();
|
||||
cfg.AddProfile<ProviderOrganizationMapperProfile>();
|
||||
cfg.AddProfile<PolicyMapperProfile>();
|
||||
cfg.AddProfile<SendMapperProfile>();
|
||||
cfg.AddProfile<SsoConfigMapperProfile>();
|
||||
|
@ -86,12 +86,36 @@ namespace Bit.Core.Test.AutoFixture.PolicyFixtures
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfPolicyApplicableToUser : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||
fixture.Customizations.Add(new PolicyBuilder());
|
||||
fixture.Customizations.Add(new OrganizationBuilder());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<PolicyRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderUserRepository>());
|
||||
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderOrganizationRepository>());
|
||||
}
|
||||
}
|
||||
|
||||
internal class EfPolicyAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public EfPolicyAutoDataAttribute() : base(new SutProviderCustomization(), new EfPolicy())
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class EfPolicyApplicableToUserInlineAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public EfPolicyApplicableToUserInlineAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), typeof(EfPolicyApplicableToUser) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
internal class InlineEfPolicyAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineEfPolicyAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
|
@ -18,4 +18,13 @@ namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class PolicyCompareIncludingOrganization: PolicyCompare
|
||||
{
|
||||
public new bool Equals(Policy x, Policy y)
|
||||
{
|
||||
return base.Equals(x, y) &&
|
||||
x.OrganizationId == y.OrganizationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ using Bit.Core.Repositories.EntityFramework;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Core.Test.AutoFixture.PolicyFixtures;
|
||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
using TableModel = Bit.Core.Models.Table;
|
||||
@ -10,6 +11,11 @@ using System.Collections.Generic;
|
||||
using EfRepo = Bit.Core.Repositories.EntityFramework;
|
||||
using SqlRepo = Bit.Core.Repositories.SqlServer;
|
||||
using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers;
|
||||
using Bit.Core.Models.Data;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Test.Repositories.EntityFramework
|
||||
{
|
||||
@ -51,6 +57,130 @@ namespace Bit.Core.Test.Repositories.EntityFramework
|
||||
|
||||
var distinctItems = savedPolicys.Distinct(equalityComparer);
|
||||
Assert.True(!distinctItems.Skip(1).Any());
|
||||
}
|
||||
}
|
||||
|
||||
[CiSkippedTheory]
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, true, false)] // Ordinary user
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Owner, false, OrganizationUserStatusType.Confirmed, true, true, false)] // Owner
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Admin, false, OrganizationUserStatusType.Confirmed, true, true, false)] // Admin
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, true, OrganizationUserStatusType.Confirmed, true, true, false)] // canManagePolicies
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, true, true)] // Provider
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, false, true, false)] // Policy disabled
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, false, false)] // No policy of Type
|
||||
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Invited, true, true, false)] // User not minStatus
|
||||
public async void GetManyByTypeApplicableToUser_Works_DataMatches_Corre(
|
||||
// Inline data
|
||||
OrganizationUserType userType,
|
||||
bool canManagePolicies,
|
||||
OrganizationUserStatusType orgUserStatus,
|
||||
bool policyEnabled,
|
||||
bool policySameType,
|
||||
bool isProvider,
|
||||
|
||||
// Auto data - models
|
||||
TableModel.Policy policy,
|
||||
TableModel.User user,
|
||||
TableModel.Organization organization,
|
||||
TableModel.OrganizationUser orgUser,
|
||||
TableModel.Provider.Provider provider,
|
||||
TableModel.Provider.ProviderOrganization providerOrganization,
|
||||
TableModel.Provider.ProviderUser providerUser,
|
||||
PolicyCompareIncludingOrganization equalityComparer,
|
||||
|
||||
// Auto data - EF repos
|
||||
List<EfRepo.PolicyRepository> suts,
|
||||
List<EfRepo.UserRepository> efUserRepository,
|
||||
List<EfRepo.OrganizationRepository> efOrganizationRepository,
|
||||
List<EfRepo.OrganizationUserRepository> efOrganizationUserRepository,
|
||||
List<EfRepo.ProviderRepository> efProviderRepository,
|
||||
List<EfRepo.ProviderOrganizationRepository> efProviderOrganizationRepository,
|
||||
List<EfRepo.ProviderUserRepository> efProviderUserRepository,
|
||||
|
||||
// Auto data - SQL repos
|
||||
SqlRepo.PolicyRepository sqlPolicyRepo,
|
||||
SqlRepo.UserRepository sqlUserRepo,
|
||||
SqlRepo.OrganizationRepository sqlOrganizationRepo,
|
||||
SqlRepo.ProviderRepository sqlProviderRepo,
|
||||
SqlRepo.OrganizationUserRepository sqlOrganizationUserRepo,
|
||||
SqlRepo.ProviderOrganizationRepository sqlProviderOrganizationRepo,
|
||||
SqlRepo.ProviderUserRepository sqlProviderUserRepo
|
||||
)
|
||||
{
|
||||
// Combine EF and SQL repos into one list per type
|
||||
var policyRepos = suts.ToList<IPolicyRepository>();
|
||||
policyRepos.Add(sqlPolicyRepo);
|
||||
var userRepos = efUserRepository.ToList<IUserRepository>();
|
||||
userRepos.Add(sqlUserRepo);
|
||||
var orgRepos = efOrganizationRepository.ToList<IOrganizationRepository>();
|
||||
orgRepos.Add(sqlOrganizationRepo);
|
||||
var orgUserRepos = efOrganizationUserRepository.ToList<IOrganizationUserRepository>();
|
||||
orgUserRepos.Add(sqlOrganizationUserRepo);
|
||||
var providerRepos = efProviderRepository.ToList<IProviderRepository>();
|
||||
providerRepos.Add(sqlProviderRepo);
|
||||
var providerOrgRepos = efProviderOrganizationRepository.ToList<IProviderOrganizationRepository>();
|
||||
providerOrgRepos.Add(sqlProviderOrganizationRepo);
|
||||
var providerUserRepos = efProviderUserRepository.ToList<IProviderUserRepository>();
|
||||
providerUserRepos.Add(sqlProviderUserRepo);
|
||||
|
||||
// Arrange data
|
||||
var savedPolicyType = PolicyType.SingleOrg;
|
||||
var queriedPolicyType = policySameType ? savedPolicyType : PolicyType.DisableSend;
|
||||
|
||||
orgUser.Type = userType;
|
||||
orgUser.Status = orgUserStatus;
|
||||
var permissionsData = new Permissions { ManagePolicies = canManagePolicies };
|
||||
orgUser.Permissions = JsonSerializer.Serialize(permissionsData, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
policy.Enabled = policyEnabled;
|
||||
policy.Type = savedPolicyType;
|
||||
|
||||
var results = new List<TableModel.Policy>();
|
||||
|
||||
foreach (var policyRepo in policyRepos)
|
||||
{
|
||||
var i = policyRepos.IndexOf(policyRepo);
|
||||
|
||||
// Seed database
|
||||
var savedUser = await userRepos[i].CreateAsync(user);
|
||||
var savedOrg = await orgRepos[i].CreateAsync(organization);
|
||||
|
||||
orgUser.UserId = savedUser.Id;
|
||||
orgUser.OrganizationId = savedOrg.Id;
|
||||
await orgUserRepos[i].CreateAsync(orgUser);
|
||||
|
||||
if (isProvider)
|
||||
{
|
||||
var savedProvider = await providerRepos[i].CreateAsync(provider);
|
||||
|
||||
providerOrganization.OrganizationId = savedOrg.Id;
|
||||
providerOrganization.ProviderId = savedProvider.Id;
|
||||
await providerOrgRepos[i].CreateAsync(providerOrganization);
|
||||
|
||||
providerUser.UserId = savedUser.Id;
|
||||
providerUser.ProviderId = savedProvider.Id;
|
||||
await providerUserRepos[i].CreateAsync(providerUser);
|
||||
}
|
||||
|
||||
policy.OrganizationId = savedOrg.Id;
|
||||
await policyRepo.CreateAsync(policy);
|
||||
if (suts.Contains(policyRepo))
|
||||
{
|
||||
(policyRepo as BaseEntityFrameworkRepository).ClearChangeTracking();
|
||||
}
|
||||
|
||||
// Act
|
||||
var result = await policyRepo.GetManyByTypeApplicableToUserIdAsync(savedUser.Id, queriedPolicyType, OrganizationUserStatusType.Accepted);
|
||||
results.Add(result.FirstOrDefault());
|
||||
}
|
||||
|
||||
// Assert
|
||||
var distinctItems = results.Distinct(equalityComparer);
|
||||
|
||||
Assert.True(results.All(r => r == null) ||
|
||||
!distinctItems.Skip(1).Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,34 +12,31 @@ using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Test.AutoFixture.SendFixtures;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
public class SendServiceTests
|
||||
{
|
||||
private void SaveSendAsync_Setup(SendType sendType, bool disableSendPolicyAppliesToUser,
|
||||
SutProvider<SendService> sutProvider, Send send)
|
||||
{
|
||||
send.Id = default;
|
||||
send.Type = sendType;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetCountByTypeApplicableToUserIdAsync(
|
||||
Arg.Any<Guid>(), PolicyType.DisableSend).Returns(disableSendPolicyAppliesToUser ? 1 : 0);
|
||||
}
|
||||
|
||||
// Disable Send policy check
|
||||
|
||||
private void SaveSendAsync_DisableSend_Setup(SendType sendType, bool canManagePolicies,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
{
|
||||
send.Id = default;
|
||||
send.Type = sendType;
|
||||
|
||||
policies.First().Type = PolicyType.DisableSend;
|
||||
policies.First().Enabled = true;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByUserIdAsync(send.UserId.Value).Returns(policies);
|
||||
sutProvider.GetDependency<ICurrentContext>().ManagePolicies(Arg.Any<Guid>()).Returns(canManagePolicies);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableSend_CantManagePolicies_throws(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
public async void SaveSendAsync_DisableSend_Applies_throws(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send)
|
||||
{
|
||||
SaveSendAsync_DisableSend_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
|
||||
SaveSendAsync_Setup(sendType, disableSendPolicyAppliesToUser: true, sutProvider, send);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveSendAsync(send));
|
||||
}
|
||||
@ -47,61 +44,47 @@ namespace Bit.Core.Test.Services
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableSend_DisabledPolicy_CantManagePolicies_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
public async void SaveSendAsync_DisableSend_DoesntApply_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send)
|
||||
{
|
||||
SaveSendAsync_DisableSend_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
|
||||
foreach (var policy in policies.Where(p => p.Type == PolicyType.DisableSend))
|
||||
{
|
||||
policy.Enabled = false;
|
||||
}
|
||||
SaveSendAsync_Setup(sendType, disableSendPolicyAppliesToUser: false, sutProvider, send);
|
||||
|
||||
await sutProvider.Sut.SaveSendAsync(send);
|
||||
|
||||
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableSend_CanManagePolicies_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
// Send Options Policy - Disable Hide Email check
|
||||
|
||||
private void SaveSendAsync_HideEmail_Setup(bool disableHideEmailAppliesToUser,
|
||||
SutProvider<SendService> sutProvider, Send send, Policy policy)
|
||||
{
|
||||
SaveSendAsync_DisableSend_Setup(sendType, canManagePolicies: true, sutProvider, send, policies);
|
||||
|
||||
await sutProvider.Sut.SaveSendAsync(send);
|
||||
|
||||
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
|
||||
}
|
||||
|
||||
// SendOptionsPolicy.DisableHideEmail check
|
||||
|
||||
private void SaveSendAsync_DisableHideEmail_Setup(SendType sendType, bool canManagePolicies,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
{
|
||||
send.Id = default;
|
||||
send.Type = sendType;
|
||||
send.HideEmail = true;
|
||||
|
||||
var dataObj = new SendOptionsPolicyData();
|
||||
dataObj.DisableHideEmail = true;
|
||||
var sendOptions = new SendOptionsPolicyData
|
||||
{
|
||||
DisableHideEmail = disableHideEmailAppliesToUser
|
||||
};
|
||||
policy.Data = JsonSerializer.Serialize(sendOptions, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
policies.First().Type = PolicyType.SendOptions;
|
||||
policies.First().Enabled = true;
|
||||
policies.First().Data = JsonConvert.SerializeObject(dataObj);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByUserIdAsync(send.UserId.Value).Returns(policies);
|
||||
sutProvider.GetDependency<ICurrentContext>().ManagePolicies(Arg.Any<Guid>()).Returns(canManagePolicies);
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByTypeApplicableToUserIdAsync(
|
||||
Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<Policy>
|
||||
{
|
||||
policy,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableHideEmail_CantManagePolicies_throws(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
public async void SaveSendAsync_DisableHideEmail_Applies_throws(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, Policy policy)
|
||||
{
|
||||
SaveSendAsync_DisableHideEmail_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
|
||||
SaveSendAsync_Setup(sendType, false, sutProvider, send);
|
||||
SaveSendAsync_HideEmail_Setup(true, sutProvider, send, policy);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveSendAsync(send));
|
||||
}
|
||||
@ -109,33 +92,11 @@ namespace Bit.Core.Test.Services
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableHideEmail_CantManagePolicies_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
public async void SaveSendAsync_DisableHideEmail_DoesntApply_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, Policy policy)
|
||||
{
|
||||
SaveSendAsync_DisableHideEmail_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
|
||||
|
||||
var policyData = new SendOptionsPolicyData();
|
||||
policyData.DisableHideEmail = false;
|
||||
var policyDataSerialized = JsonConvert.SerializeObject(policyData);
|
||||
|
||||
foreach (var policy in policies.Where(p => p.Type == PolicyType.SendOptions))
|
||||
{
|
||||
policies.First().Enabled = true;
|
||||
policies.First().Data = policyDataSerialized;
|
||||
}
|
||||
|
||||
await sutProvider.Sut.SaveSendAsync(send);
|
||||
|
||||
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineUserSendAutoData(SendType.File)]
|
||||
[InlineUserSendAutoData(SendType.Text)]
|
||||
public async void SaveSendAsync_DisableHideEmail_CanManagePolicies_success(SendType sendType,
|
||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
||||
{
|
||||
SaveSendAsync_DisableHideEmail_Setup(sendType, canManagePolicies: true, sutProvider, send, policies);
|
||||
SaveSendAsync_Setup(sendType, false, sutProvider, send);
|
||||
SaveSendAsync_HideEmail_Setup(false, sutProvider, send, policy);
|
||||
|
||||
await sutProvider.Sut.SaveSendAsync(send);
|
||||
|
||||
|
Reference in New Issue
Block a user