mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01: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:
parent
fbf3e0dcdc
commit
66629b2f1c
@ -54,5 +54,30 @@ namespace Bit.Core.Repositories.EntityFramework
|
|||||||
return Mapper.Map<List<TableModel.Policy>>(results);
|
return Mapper.Map<List<TableModel.Policy>>(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<Policy>> GetManyByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
|
||||||
|
var query = new PolicyReadByTypeApplicableToUserQuery(userId, policyType, minStatus);
|
||||||
|
var results = await query.Run(dbContext).ToListAsync();
|
||||||
|
return Mapper.Map<List<TableModel.Policy>>(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetCountByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
|
||||||
|
var query = new PolicyReadByTypeApplicableToUserQuery(userId, policyType, minStatus);
|
||||||
|
return await GetCountFromQuery(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.EntityFramework;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Core.Repositories.EntityFramework.Queries
|
||||||
|
{
|
||||||
|
public class PolicyReadByTypeApplicableToUserQuery : IQuery<Policy>
|
||||||
|
{
|
||||||
|
private readonly Guid _userId;
|
||||||
|
private readonly PolicyType _policyType;
|
||||||
|
private readonly OrganizationUserStatusType _minimumStatus;
|
||||||
|
|
||||||
|
public PolicyReadByTypeApplicableToUserQuery(Guid userId, PolicyType policyType, OrganizationUserStatusType minimumStatus)
|
||||||
|
{
|
||||||
|
_userId = userId;
|
||||||
|
_policyType = policyType;
|
||||||
|
_minimumStatus = minimumStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<Policy> Run(DatabaseContext dbContext)
|
||||||
|
{
|
||||||
|
var providerOrganizations = from pu in dbContext.ProviderUsers
|
||||||
|
where pu.UserId == _userId
|
||||||
|
join po in dbContext.ProviderOrganizations
|
||||||
|
on pu.ProviderId equals po.ProviderId
|
||||||
|
select po;
|
||||||
|
|
||||||
|
var query = from p in dbContext.Policies
|
||||||
|
join ou in dbContext.OrganizationUsers
|
||||||
|
on p.OrganizationId equals ou.OrganizationId
|
||||||
|
where ou.UserId == _userId &&
|
||||||
|
p.Type == _policyType &&
|
||||||
|
p.Enabled &&
|
||||||
|
ou.Status >= _minimumStatus &&
|
||||||
|
ou.Type >= OrganizationUserType.User &&
|
||||||
|
(ou.Permissions == null ||
|
||||||
|
ou.Permissions.Contains($"\"managePolicies\":false")) &&
|
||||||
|
!providerOrganizations.Any(po => po.OrganizationId == p.OrganizationId)
|
||||||
|
select p;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,5 +11,9 @@ namespace Bit.Core.Repositories
|
|||||||
Task<Policy> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type);
|
Task<Policy> GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type);
|
||||||
Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);
|
Task<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);
|
||||||
Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId);
|
Task<ICollection<Policy>> GetManyByUserIdAsync(Guid userId);
|
||||||
|
Task<ICollection<Policy>> GetManyByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted);
|
||||||
|
Task<int> GetCountByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,5 +58,33 @@ namespace Bit.Core.Repositories.SqlServer
|
|||||||
return results.ToList();
|
return results.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<Policy>> GetManyByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<Policy>(
|
||||||
|
$"[{Schema}].[{Table}_ReadByTypeApplicableToUser]",
|
||||||
|
new { UserId = userId, PolicyType = policyType, MinimumStatus = minStatus },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetCountByTypeApplicableToUserIdAsync(Guid userId, PolicyType policyType,
|
||||||
|
OrganizationUserStatusType minStatus)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var result = await connection.ExecuteScalarAsync<int>(
|
||||||
|
$"[{Schema}].[{Table}_CountByTypeApplicableToUser]",
|
||||||
|
new { UserId = userId, PolicyType = policyType, MinimumStatus = minStatus },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace Bit.Core.Services
|
|||||||
private readonly ICollectionRepository _collectionRepository;
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
|
||||||
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
private readonly ICollectionCipherRepository _collectionCipherRepository;
|
||||||
private readonly IPushNotificationService _pushService;
|
private readonly IPushNotificationService _pushService;
|
||||||
private readonly IAttachmentStorageService _attachmentStorageService;
|
private readonly IAttachmentStorageService _attachmentStorageService;
|
||||||
@ -43,7 +42,6 @@ namespace Bit.Core.Services
|
|||||||
ICollectionRepository collectionRepository,
|
ICollectionRepository collectionRepository,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
|
||||||
ICollectionCipherRepository collectionCipherRepository,
|
ICollectionCipherRepository collectionCipherRepository,
|
||||||
IPushNotificationService pushService,
|
IPushNotificationService pushService,
|
||||||
IAttachmentStorageService attachmentStorageService,
|
IAttachmentStorageService attachmentStorageService,
|
||||||
@ -58,7 +56,6 @@ namespace Bit.Core.Services
|
|||||||
_collectionRepository = collectionRepository;
|
_collectionRepository = collectionRepository;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
|
||||||
_collectionCipherRepository = collectionCipherRepository;
|
_collectionCipherRepository = collectionCipherRepository;
|
||||||
_pushService = pushService;
|
_pushService = pushService;
|
||||||
_attachmentStorageService = attachmentStorageService;
|
_attachmentStorageService = attachmentStorageService;
|
||||||
@ -139,20 +136,12 @@ namespace Bit.Core.Services
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Make sure the user can save new ciphers to their personal vault
|
// Make sure the user can save new ciphers to their personal vault
|
||||||
var userPolicies = await _policyRepository.GetManyByUserIdAsync(savingUserId);
|
var personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(savingUserId,
|
||||||
if (userPolicies != null)
|
PolicyType.PersonalOwnership);
|
||||||
{
|
if (personalOwnershipPolicyCount > 0)
|
||||||
foreach (var policy in userPolicies.Where(p => p.Enabled && p.Type == PolicyType.PersonalOwnership))
|
|
||||||
{
|
|
||||||
var org = await _organizationUserRepository.GetDetailsByUserAsync(savingUserId, policy.OrganizationId,
|
|
||||||
OrganizationUserStatusType.Confirmed);
|
|
||||||
if (org != null && org.Enabled && org.UsePolicies
|
|
||||||
&& org.Type != OrganizationUserType.Admin && org.Type != OrganizationUserType.Owner)
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.");
|
throw new BadRequestException("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
await _cipherRepository.CreateAsync(cipher);
|
await _cipherRepository.CreateAsync(cipher);
|
||||||
}
|
}
|
||||||
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created);
|
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created);
|
||||||
@ -688,27 +677,14 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;
|
var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;
|
||||||
|
|
||||||
// Check user is allowed to import to personal vault
|
// Make sure the user can save new ciphers to their personal vault
|
||||||
if (userId.HasValue)
|
var personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId.Value,
|
||||||
{
|
PolicyType.PersonalOwnership);
|
||||||
var policies = await _policyRepository.GetManyByUserIdAsync(userId.Value);
|
if (personalOwnershipPolicyCount > 0)
|
||||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId.Value);
|
|
||||||
|
|
||||||
var orgsWithBlockingPolicy = policies
|
|
||||||
.Where(p => p.Enabled && p.Type == PolicyType.PersonalOwnership)
|
|
||||||
.Select(p => p.OrganizationId);
|
|
||||||
var blockedByPolicy = allOrgUsers.Any(ou =>
|
|
||||||
ou.Type != OrganizationUserType.Owner &&
|
|
||||||
ou.Type != OrganizationUserType.Admin &&
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited &&
|
|
||||||
orgsWithBlockingPolicy.Contains(ou.OrganizationId));
|
|
||||||
|
|
||||||
if (blockedByPolicy)
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot import items into your personal vault because you are " +
|
throw new BadRequestException("You cannot import items into your personal vault because you are " +
|
||||||
"a member of an organization which forbids it.");
|
"a member of an organization which forbids it.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var cipher in ciphers)
|
foreach (var cipher in ciphers)
|
||||||
{
|
{
|
||||||
|
@ -639,16 +639,8 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||||
{
|
{
|
||||||
var policies = await _policyRepository.GetManyByUserIdAsync(ownerId);
|
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(ownerId, PolicyType.SingleOrg);
|
||||||
var orgUsers = await _organizationUserRepository.GetManyByUserAsync(ownerId);
|
if (singleOrgPolicyCount > 0)
|
||||||
|
|
||||||
var orgsWithSingleOrgPolicy = policies.Where(p => p.Enabled && p.Type == PolicyType.SingleOrg)
|
|
||||||
.Select(p => p.OrganizationId);
|
|
||||||
var blockedBySingleOrgPolicy = orgUsers.Any(ou => ou is { Type: OrganizationUserType.Owner } &&
|
|
||||||
ou.Type != OrganizationUserType.Admin &&
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited &&
|
|
||||||
orgsWithSingleOrgPolicy.Contains(ou.OrganizationId));
|
|
||||||
if (blockedBySingleOrgPolicy)
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
||||||
"which has a policy that prohibits you from being a member of any other organization.");
|
"which has a policy that prohibits you from being a member of any other organization.");
|
||||||
@ -1324,49 +1316,38 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool notExempt(OrganizationUser organizationUser)
|
|
||||||
{
|
|
||||||
return organizationUser.Type != OrganizationUserType.Owner &&
|
|
||||||
organizationUser.Type != OrganizationUserType.Admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
|
||||||
|
|
||||||
// Enforce Single Organization Policy of organization user is trying to join
|
// Enforce Single Organization Policy of organization user is trying to join
|
||||||
var thisSingleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgUser.OrganizationId, PolicyType.SingleOrg);
|
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||||
if (thisSingleOrgPolicy != null &&
|
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
||||||
thisSingleOrgPolicy.Enabled &&
|
var invitedSingleOrgPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id,
|
||||||
notExempt(orgUser) &&
|
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
|
||||||
allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId))
|
|
||||||
|
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You may not join this organization until you leave or remove " +
|
throw new BadRequestException("You may not join this organization until you leave or remove " +
|
||||||
"all other organizations.");
|
"all other organizations.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce Single Organization Policy of other organizations user is a member of
|
// Enforce Single Organization Policy of other organizations user is a member of
|
||||||
var policies = await _policyRepository.GetManyByUserIdAsync(user.Id);
|
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(user.Id,
|
||||||
|
PolicyType.SingleOrg);
|
||||||
var orgsWithSingleOrgPolicy = policies.Where(p => p.Enabled && p.Type == PolicyType.SingleOrg)
|
if (singleOrgPolicyCount > 0)
|
||||||
.Select(p => p.OrganizationId);
|
|
||||||
var blockedBySingleOrgPolicy = allOrgUsers.Any(ou => notExempt(ou) &&
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited &&
|
|
||||||
orgsWithSingleOrgPolicy.Contains(ou.OrganizationId));
|
|
||||||
|
|
||||||
if (blockedBySingleOrgPolicy)
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot join this organization because you are a member of " +
|
throw new BadRequestException("You cannot join this organization because you are a member of " +
|
||||||
"an organization which forbids it");
|
"another organization which forbids it");
|
||||||
}
|
}
|
||||||
|
|
||||||
var twoFactorPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgUser.OrganizationId, PolicyType.TwoFactorAuthentication);
|
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
||||||
if (!await userService.TwoFactorIsEnabledAsync(user) &&
|
if (!await userService.TwoFactorIsEnabledAsync(user))
|
||||||
twoFactorPolicy != null &&
|
{
|
||||||
twoFactorPolicy.Enabled &&
|
var invitedTwoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id,
|
||||||
notExempt(orgUser))
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||||
|
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot join this organization until you enable " +
|
throw new BadRequestException("You cannot join this organization until you enable " +
|
||||||
"two-step login on your user account.");
|
"two-step login on your user account.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
orgUser.Status = OrganizationUserStatusType.Accepted;
|
orgUser.Status = OrganizationUserStatusType.Accepted;
|
||||||
orgUser.UserId = user.Id;
|
orgUser.UserId = user.Id;
|
||||||
|
@ -10,6 +10,7 @@ using Bit.Core.Models.Data;
|
|||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
@ -280,40 +281,24 @@ namespace Bit.Core.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var policies = await _policyRepository.GetManyByUserIdAsync(userId.Value);
|
var disableSendPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId.Value,
|
||||||
|
PolicyType.DisableSend);
|
||||||
if (policies == null)
|
if (disableSendPolicyCount > 0)
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.DisableSend))
|
|
||||||
{
|
|
||||||
if (!await _currentContext.ManagePolicies(policy.OrganizationId))
|
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
|
throw new BadRequestException("Due to an Enterprise Policy, you are only able to delete an existing Send.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (send.HideEmail.GetValueOrDefault())
|
if (send.HideEmail.GetValueOrDefault())
|
||||||
{
|
{
|
||||||
foreach (var policy in policies.Where(p => p.Enabled && p.Type == PolicyType.SendOptions))
|
var sendOptionsPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(userId.Value, PolicyType.SendOptions);
|
||||||
|
foreach (var policy in sendOptionsPolicies)
|
||||||
{
|
{
|
||||||
if (await _currentContext.ManagePolicies(policy.OrganizationId))
|
var data = CoreHelpers.LoadClassFromJsonData<SendOptionsPolicyData>(policy.Data);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
SendOptionsPolicyData data = null;
|
|
||||||
if (policy.Data != null)
|
|
||||||
{
|
|
||||||
data = JsonConvert.DeserializeObject<SendOptionsPolicyData>(policy.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data?.DisableHideEmail ?? false)
|
if (data?.DisableHideEmail ?? false)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
|
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to hide your email address from recipients when creating or editing a Send.");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,24 +1303,18 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService)
|
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService)
|
||||||
{
|
{
|
||||||
var policies = await _policyRepository.GetManyByUserIdAsync(user.Id);
|
var twoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id,
|
||||||
var twoFactorPolicies = policies.Where(p => p.Type == PolicyType.TwoFactorAuthentication && p.Enabled);
|
PolicyType.TwoFactorAuthentication);
|
||||||
if (twoFactorPolicies.Any())
|
|
||||||
|
var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
|
||||||
{
|
{
|
||||||
var userOrgs = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
await organizationService.DeleteUserAsync(p.OrganizationId, user.Id);
|
||||||
var ownerOrgs = userOrgs.Where(o => o.Type == OrganizationUserType.Owner)
|
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
|
||||||
.Select(o => o.OrganizationId).ToHashSet();
|
|
||||||
foreach (var policy in twoFactorPolicies)
|
|
||||||
{
|
|
||||||
if (!ownerOrgs.Contains(policy.OrganizationId))
|
|
||||||
{
|
|
||||||
await organizationService.DeleteUserAsync(policy.OrganizationId, user.Id);
|
|
||||||
var organization = await _organizationRepository.GetByIdAsync(policy.OrganizationId);
|
|
||||||
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
|
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
|
||||||
organization.Name, user.Email);
|
organization.Name, user.Email);
|
||||||
}
|
}).ToArray();
|
||||||
}
|
|
||||||
}
|
await Task.WhenAll(removeOrgUserTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
||||||
|
@ -368,5 +368,8 @@
|
|||||||
<Build Include="dbo\Stored Procedures\ProviderOrganizationOrganizationDetails_ReadByProviderId.sql" />
|
<Build Include="dbo\Stored Procedures\ProviderOrganizationOrganizationDetails_ReadByProviderId.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderId.sql" />
|
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderId.sql" />
|
||||||
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderUserId.sql" />
|
<Build Include="dbo\Stored Procedures\User_BumpAccountRevisionDateByProviderUserId.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Policy_CountByTypeApplicableToUser.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\Policy_ReadByTypeApplicableToUser.sql" />
|
||||||
|
<Build Include="dbo\Functions\PolicyApplicableToUser.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
35
src/Sql/dbo/Functions/PolicyApplicableToUser.sql
Normal file
35
src/Sql/dbo/Functions/PolicyApplicableToUser.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
CREATE FUNCTION [dbo].[PolicyApplicableToUser]
|
||||||
|
(
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
)
|
||||||
|
RETURNS TABLE
|
||||||
|
AS RETURN
|
||||||
|
SELECT
|
||||||
|
P.*
|
||||||
|
FROM
|
||||||
|
[dbo].[PolicyView] P
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUserView] OU ON P.[OrganizationId] = OU.[OrganizationId]
|
||||||
|
LEFT JOIN
|
||||||
|
(SELECT
|
||||||
|
PU.UserId,
|
||||||
|
PO.OrganizationId
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderUserView] PU
|
||||||
|
INNER JOIN
|
||||||
|
[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]) PUPO
|
||||||
|
ON PUPO.UserId = OU.UserId
|
||||||
|
AND PUPO.OrganizationId = P.OrganizationId
|
||||||
|
WHERE
|
||||||
|
OU.[UserId] = @UserId
|
||||||
|
AND P.[Type] = @PolicyType
|
||||||
|
AND P.[Enabled] = 1
|
||||||
|
AND OU.[Status] >= @MinimumStatus
|
||||||
|
AND OU.[Type] >= 2 -- Not an owner (0) or admin (1)
|
||||||
|
AND ( -- Can't manage policies
|
||||||
|
OU.[Permissions] IS NULL
|
||||||
|
OR COALESCE(JSON_VALUE(OU.[Permissions], '$.managePolicies'), 'false') = 'false'
|
||||||
|
)
|
||||||
|
AND PUPO.[UserId] IS NULL -- Not a provider
|
@ -0,0 +1,11 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Policy_CountByTypeApplicableToUser]
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM [dbo].[PolicyApplicableToUser](@UserId, @PolicyType, @MinimumStatus)
|
||||||
|
END
|
@ -0,0 +1,11 @@
|
|||||||
|
CREATE PROCEDURE [dbo].[Policy_ReadByTypeApplicableToUser]
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM [dbo].[PolicyApplicableToUser](@UserId, @PolicyType, @MinimumStatus)
|
||||||
|
END
|
@ -1,6 +1,7 @@
|
|||||||
using AutoFixture;
|
using AutoFixture;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Bit.Core.Models.EntityFramework;
|
using Bit.Core.Models.EntityFramework;
|
||||||
|
using Bit.Core.Models.EntityFramework.Provider;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AutoFixture.Kernel;
|
using AutoFixture.Kernel;
|
||||||
using System;
|
using System;
|
||||||
@ -76,6 +77,9 @@ namespace Bit.Core.Test.AutoFixture.EntityFrameworkRepositoryFixtures
|
|||||||
cfg.AddProfile<InstallationMapperProfile>();
|
cfg.AddProfile<InstallationMapperProfile>();
|
||||||
cfg.AddProfile<OrganizationMapperProfile>();
|
cfg.AddProfile<OrganizationMapperProfile>();
|
||||||
cfg.AddProfile<OrganizationUserMapperProfile>();
|
cfg.AddProfile<OrganizationUserMapperProfile>();
|
||||||
|
cfg.AddProfile<ProviderMapperProfile>();
|
||||||
|
cfg.AddProfile<ProviderUserMapperProfile>();
|
||||||
|
cfg.AddProfile<ProviderOrganizationMapperProfile>();
|
||||||
cfg.AddProfile<PolicyMapperProfile>();
|
cfg.AddProfile<PolicyMapperProfile>();
|
||||||
cfg.AddProfile<SendMapperProfile>();
|
cfg.AddProfile<SendMapperProfile>();
|
||||||
cfg.AddProfile<SsoConfigMapperProfile>();
|
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
|
internal class EfPolicyAutoDataAttribute : CustomAutoDataAttribute
|
||||||
{
|
{
|
||||||
public EfPolicyAutoDataAttribute() : base(new SutProviderCustomization(), new EfPolicy())
|
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
|
internal class InlineEfPolicyAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
{
|
{
|
||||||
public InlineEfPolicyAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
public InlineEfPolicyAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||||
|
@ -18,4 +18,13 @@ namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers
|
|||||||
return base.GetHashCode();
|
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;
|
||||||
using Bit.Core.Test.AutoFixture.Attributes;
|
using Bit.Core.Test.AutoFixture.Attributes;
|
||||||
using Bit.Core.Test.AutoFixture.PolicyFixtures;
|
using Bit.Core.Test.AutoFixture.PolicyFixtures;
|
||||||
|
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using TableModel = Bit.Core.Models.Table;
|
using TableModel = Bit.Core.Models.Table;
|
||||||
@ -10,6 +11,11 @@ using System.Collections.Generic;
|
|||||||
using EfRepo = Bit.Core.Repositories.EntityFramework;
|
using EfRepo = Bit.Core.Repositories.EntityFramework;
|
||||||
using SqlRepo = Bit.Core.Repositories.SqlServer;
|
using SqlRepo = Bit.Core.Repositories.SqlServer;
|
||||||
using Bit.Core.Test.Repositories.EntityFramework.EqualityComparers;
|
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
|
namespace Bit.Core.Test.Repositories.EntityFramework
|
||||||
{
|
{
|
||||||
@ -52,5 +58,129 @@ namespace Bit.Core.Test.Repositories.EntityFramework
|
|||||||
var distinctItems = savedPolicys.Distinct(equalityComparer);
|
var distinctItems = savedPolicys.Distinct(equalityComparer);
|
||||||
Assert.True(!distinctItems.Skip(1).Any());
|
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 Bit.Core.Test.AutoFixture.SendFixtures;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Newtonsoft.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Services
|
namespace Bit.Core.Test.Services
|
||||||
{
|
{
|
||||||
public class SendServiceTests
|
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
|
// 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]
|
[Theory]
|
||||||
[InlineUserSendAutoData(SendType.File)]
|
[InlineUserSendAutoData(SendType.File)]
|
||||||
[InlineUserSendAutoData(SendType.Text)]
|
[InlineUserSendAutoData(SendType.Text)]
|
||||||
public async void SaveSendAsync_DisableSend_CantManagePolicies_throws(SendType sendType,
|
public async void SaveSendAsync_DisableSend_Applies_throws(SendType sendType,
|
||||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
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));
|
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveSendAsync(send));
|
||||||
}
|
}
|
||||||
@ -47,61 +44,47 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory]
|
[Theory]
|
||||||
[InlineUserSendAutoData(SendType.File)]
|
[InlineUserSendAutoData(SendType.File)]
|
||||||
[InlineUserSendAutoData(SendType.Text)]
|
[InlineUserSendAutoData(SendType.Text)]
|
||||||
public async void SaveSendAsync_DisableSend_DisabledPolicy_CantManagePolicies_success(SendType sendType,
|
public async void SaveSendAsync_DisableSend_DoesntApply_success(SendType sendType,
|
||||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
SutProvider<SendService> sutProvider, Send send)
|
||||||
{
|
{
|
||||||
SaveSendAsync_DisableSend_Setup(sendType, canManagePolicies: false, sutProvider, send, policies);
|
SaveSendAsync_Setup(sendType, disableSendPolicyAppliesToUser: false, sutProvider, send);
|
||||||
foreach (var policy in policies.Where(p => p.Type == PolicyType.DisableSend))
|
|
||||||
{
|
|
||||||
policy.Enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sutProvider.Sut.SaveSendAsync(send);
|
await sutProvider.Sut.SaveSendAsync(send);
|
||||||
|
|
||||||
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
|
await sutProvider.GetDependency<ISendRepository>().Received(1).CreateAsync(send);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
// Send Options Policy - Disable Hide Email check
|
||||||
[InlineUserSendAutoData(SendType.File)]
|
|
||||||
[InlineUserSendAutoData(SendType.Text)]
|
private void SaveSendAsync_HideEmail_Setup(bool disableHideEmailAppliesToUser,
|
||||||
public async void SaveSendAsync_DisableSend_CanManagePolicies_success(SendType sendType,
|
SutProvider<SendService> sutProvider, Send send, Policy policy)
|
||||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
|
||||||
{
|
{
|
||||||
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;
|
send.HideEmail = true;
|
||||||
|
|
||||||
var dataObj = new SendOptionsPolicyData();
|
var sendOptions = new SendOptionsPolicyData
|
||||||
dataObj.DisableHideEmail = true;
|
{
|
||||||
|
DisableHideEmail = disableHideEmailAppliesToUser
|
||||||
|
};
|
||||||
|
policy.Data = JsonSerializer.Serialize(sendOptions, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
});
|
||||||
|
|
||||||
policies.First().Type = PolicyType.SendOptions;
|
sutProvider.GetDependency<IPolicyRepository>().GetManyByTypeApplicableToUserIdAsync(
|
||||||
policies.First().Enabled = true;
|
Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<Policy>
|
||||||
policies.First().Data = JsonConvert.SerializeObject(dataObj);
|
{
|
||||||
|
policy,
|
||||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByUserIdAsync(send.UserId.Value).Returns(policies);
|
});
|
||||||
sutProvider.GetDependency<ICurrentContext>().ManagePolicies(Arg.Any<Guid>()).Returns(canManagePolicies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineUserSendAutoData(SendType.File)]
|
[InlineUserSendAutoData(SendType.File)]
|
||||||
[InlineUserSendAutoData(SendType.Text)]
|
[InlineUserSendAutoData(SendType.Text)]
|
||||||
public async void SaveSendAsync_DisableHideEmail_CantManagePolicies_throws(SendType sendType,
|
public async void SaveSendAsync_DisableHideEmail_Applies_throws(SendType sendType,
|
||||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
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));
|
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SaveSendAsync(send));
|
||||||
}
|
}
|
||||||
@ -109,33 +92,11 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory]
|
[Theory]
|
||||||
[InlineUserSendAutoData(SendType.File)]
|
[InlineUserSendAutoData(SendType.File)]
|
||||||
[InlineUserSendAutoData(SendType.Text)]
|
[InlineUserSendAutoData(SendType.Text)]
|
||||||
public async void SaveSendAsync_DisableHideEmail_CantManagePolicies_success(SendType sendType,
|
public async void SaveSendAsync_DisableHideEmail_DoesntApply_success(SendType sendType,
|
||||||
SutProvider<SendService> sutProvider, Send send, List<Policy> policies)
|
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(false, sutProvider, send, policy);
|
||||||
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);
|
|
||||||
|
|
||||||
await sutProvider.Sut.SaveSendAsync(send);
|
await sutProvider.Sut.SaveSendAsync(send);
|
||||||
|
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
-- PolicyApplicableToUser
|
||||||
|
IF OBJECT_ID('[dbo].[PolicyApplicableToUser]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP FUNCTION [dbo].[PolicyApplicableToUser]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE FUNCTION [dbo].[PolicyApplicableToUser]
|
||||||
|
(
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
)
|
||||||
|
RETURNS TABLE
|
||||||
|
AS RETURN
|
||||||
|
SELECT
|
||||||
|
P.*
|
||||||
|
FROM
|
||||||
|
[dbo].[PolicyView] P
|
||||||
|
INNER JOIN
|
||||||
|
[dbo].[OrganizationUserView] OU ON P.[OrganizationId] = OU.[OrganizationId]
|
||||||
|
LEFT JOIN
|
||||||
|
(SELECT
|
||||||
|
PU.UserId,
|
||||||
|
PO.OrganizationId
|
||||||
|
FROM
|
||||||
|
[dbo].[ProviderUserView] PU
|
||||||
|
INNER JOIN
|
||||||
|
[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]) PUPO
|
||||||
|
ON PUPO.UserId = OU.UserId
|
||||||
|
AND PUPO.OrganizationId = P.OrganizationId
|
||||||
|
WHERE
|
||||||
|
OU.[UserId] = @UserId
|
||||||
|
AND P.[Type] = @PolicyType
|
||||||
|
AND P.[Enabled] = 1
|
||||||
|
AND OU.[Status] >= @MinimumStatus
|
||||||
|
AND OU.[Type] >= 2 -- Not an owner (0) or admin (1)
|
||||||
|
AND ( -- Can't manage policies
|
||||||
|
OU.[Permissions] IS NULL
|
||||||
|
OR COALESCE(JSON_VALUE(OU.[Permissions], '$.managePolicies'), 'false') = 'false'
|
||||||
|
)
|
||||||
|
AND PUPO.[UserId] IS NULL -- Not a provider
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Policy_ReadByTypeApplicableToUser
|
||||||
|
IF OBJECT_ID('[dbo].[Policy_ReadByTypeApplicableToUser]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE [dbo].[Policy_ReadByTypeApplicableToUser]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[Policy_ReadByTypeApplicableToUser]
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT *
|
||||||
|
FROM [dbo].[PolicyApplicableToUser](@UserId, @PolicyType, @MinimumStatus)
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Policy_CountByTypeApplicableToUser
|
||||||
|
IF OBJECT_ID('[dbo].[Policy_CountByTypeApplicableToUser]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE [dbo].[Policy_CountByTypeApplicableToUser]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[Policy_CountByTypeApplicableToUser]
|
||||||
|
@UserId UNIQUEIDENTIFIER,
|
||||||
|
@PolicyType TINYINT,
|
||||||
|
@MinimumStatus TINYINT
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM [dbo].[PolicyApplicableToUser](@UserId, @PolicyType, @MinimumStatus)
|
||||||
|
END
|
Loading…
Reference in New Issue
Block a user