1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537)

* [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails

* [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync

* [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user

* [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data

* [EC-787] Returning PolicyData on stored procedures

* [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection

* [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync

* [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync

* [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync

* [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync

* [EC-787] Changed integration test to check for single result

* [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete

* [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails

* [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser

* [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync

* [EC-787] Removed 'OrganizationUserType' parameter from queries

* [EC-787] Formatted OrganizationUserPolicyDetailsCompare

* [EC-787] Renamed SQL migration files

* [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json

* [EC-787] Refactored excluded user types for each Policy

* [EC-787] Updated dates on dbo_future files

* [EC-787] Remove dbo_future files from sql proj

* [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync

* [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType

* Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
Rui Tomé 2023-05-12 08:22:19 +01:00 committed by GitHub
parent 99b0953acd
commit 8d3fe12170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 560 additions and 319 deletions

View File

@ -0,0 +1,24 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
public class OrganizationUserPolicyDetails
{
public Guid OrganizationUserId { get; set; }
public Guid OrganizationId { get; set; }
public PolicyType PolicyType { get; set; }
public bool PolicyEnabled { get; set; }
public string PolicyData { get; set; }
public OrganizationUserType OrganizationUserType { get; set; }
public OrganizationUserStatusType OrganizationUserStatus { get; set; }
public string OrganizationUserPermissionsData { get; set; }
public bool IsProvider { get; set; }
}

View File

@ -39,4 +39,5 @@ public interface IOrganizationUserRepository : IRepository<OrganizationUser, Gui
Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole); Task<IEnumerable<OrganizationUserUserDetails>> GetManyByMinimumRoleAsync(Guid organizationId, OrganizationUserType minRole);
Task RevokeAsync(Guid id); Task RevokeAsync(Guid id);
Task RestoreAsync(Guid id, OrganizationUserStatusType status); Task RestoreAsync(Guid id, OrganizationUserStatusType status);
Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType);
} }

View File

@ -8,8 +8,4 @@ public interface IPolicyRepository : IRepository<Policy, Guid>
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);
} }

View File

@ -1,4 +1,6 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Models.Data.Organizations.Policies;
namespace Bit.Core.Services; namespace Bit.Core.Services;
@ -12,4 +14,6 @@ public interface IPolicyService
/// Get the combined master password policy options for the specified user. /// Get the combined master password policy options for the specified user.
/// </summary> /// </summary>
Task<MasterPasswordPolicyData> GetMasterPasswordPolicyForUserAsync(User user); Task<MasterPasswordPolicyData> GetMasterPasswordPolicyForUserAsync(User user);
Task<ICollection<OrganizationUserPolicyDetails>> GetPoliciesApplicableToUserAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted);
Task<bool> AnyPoliciesApplicableToUserAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted);
} }

View File

@ -42,6 +42,7 @@ public class OrganizationService : IOrganizationService
private readonly IApplicationCacheService _applicationCacheService; private readonly IApplicationCacheService _applicationCacheService;
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoUserRepository _ssoUserRepository; private readonly ISsoUserRepository _ssoUserRepository;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
@ -70,6 +71,7 @@ public class OrganizationService : IOrganizationService
IApplicationCacheService applicationCacheService, IApplicationCacheService applicationCacheService,
IPaymentService paymentService, IPaymentService paymentService,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
IPolicyService policyService,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository, ISsoUserRepository ssoUserRepository,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
@ -97,6 +99,7 @@ public class OrganizationService : IOrganizationService
_applicationCacheService = applicationCacheService; _applicationCacheService = applicationCacheService;
_paymentService = paymentService; _paymentService = paymentService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_policyService = policyService;
_ssoConfigRepository = ssoConfigRepository; _ssoConfigRepository = ssoConfigRepository;
_ssoUserRepository = ssoUserRepository; _ssoUserRepository = ssoUserRepository;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
@ -690,8 +693,8 @@ public class OrganizationService : IOrganizationService
private async Task ValidateSignUpPoliciesAsync(Guid ownerId) private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{ {
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(ownerId, PolicyType.SingleOrg); var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0) if (anySingleOrgPolicies)
{ {
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.");
@ -1296,7 +1299,7 @@ public class OrganizationService : IOrganizationService
// Enforce Single Organization Policy of organization user is trying to join // Enforce Single Organization Policy of organization user is trying to join
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id); var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId); var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
var invitedSingleOrgPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id, var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg, OrganizationUserStatusType.Invited); PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId)) if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
@ -1306,9 +1309,9 @@ public class OrganizationService : IOrganizationService
} }
// 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 singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(user.Id, var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg); PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0) if (anySingleOrgPolicies)
{ {
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 " +
"another organization which forbids it"); "another organization which forbids it");
@ -1317,7 +1320,7 @@ public class OrganizationService : IOrganizationService
// Enforce Two Factor Authentication Policy of organization user is trying to join // Enforce Two Factor Authentication Policy of organization user is trying to join
if (!await userService.TwoFactorIsEnabledAsync(user)) if (!await userService.TwoFactorIsEnabledAsync(user))
{ {
var invitedTwoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id, var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited); PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId)) if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
{ {
@ -2384,7 +2387,7 @@ public class OrganizationService : IOrganizationService
// Enforce Single Organization Policy of organization user is being restored to // Enforce Single Organization Policy of organization user is being restored to
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId); var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId); var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
var singleOrgPoliciesApplyingToRevokedUsers = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(userId, var singleOrgPoliciesApplyingToRevokedUsers = await _policyService.GetPoliciesApplicableToUserAsync(userId,
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked); PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId); var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);
@ -2395,9 +2398,9 @@ public class OrganizationService : IOrganizationService
} }
// 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 singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId, var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId,
PolicyType.SingleOrg); PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0) if (anySingleOrgPolicies)
{ {
throw new BadRequestException("You cannot restore this user because they are a member of " + throw new BadRequestException("You cannot restore this user because they are a member of " +
"another organization which forbids it"); "another organization which forbids it");
@ -2407,7 +2410,7 @@ public class OrganizationService : IOrganizationService
var user = await _userRepository.GetByIdAsync(userId); var user = await _userRepository.GetByIdAsync(userId);
if (!await userService.TwoFactorIsEnabledAsync(user)) if (!await userService.TwoFactorIsEnabledAsync(user))
{ {
var invitedTwoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(userId, var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited); PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId)) if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
{ {

View File

@ -3,8 +3,10 @@ using Bit.Core.Auth.Repositories;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings;
namespace Bit.Core.Services; namespace Bit.Core.Services;
@ -16,6 +18,7 @@ public class PolicyService : IPolicyService
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IMailService _mailService; private readonly IMailService _mailService;
private readonly GlobalSettings _globalSettings;
public PolicyService( public PolicyService(
IEventService eventService, IEventService eventService,
@ -23,7 +26,8 @@ public class PolicyService : IPolicyService
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
IMailService mailService) IMailService mailService,
GlobalSettings globalSettings)
{ {
_eventService = eventService; _eventService = eventService;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -31,6 +35,7 @@ public class PolicyService : IPolicyService
_policyRepository = policyRepository; _policyRepository = policyRepository;
_ssoConfigRepository = ssoConfigRepository; _ssoConfigRepository = ssoConfigRepository;
_mailService = mailService; _mailService = mailService;
_globalSettings = globalSettings;
} }
public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService, public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService,
@ -164,6 +169,47 @@ public class PolicyService : IPolicyService
return enforcedOptions; return enforcedOptions;
} }
public async Task<ICollection<OrganizationUserPolicyDetails>> GetPoliciesApplicableToUserAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted)
{
var result = await QueryOrganizationUserPolicyDetailsAsync(userId, policyType, minStatus);
return result.ToList();
}
public async Task<bool> AnyPoliciesApplicableToUserAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted)
{
var result = await QueryOrganizationUserPolicyDetailsAsync(userId, policyType, minStatus);
return result.Any();
}
private async Task<IEnumerable<OrganizationUserPolicyDetails>> QueryOrganizationUserPolicyDetailsAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted)
{
var organizationUserPolicyDetails = await _organizationUserRepository.GetByUserIdWithPolicyDetailsAsync(userId, policyType);
var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType);
return organizationUserPolicyDetails.Where(o =>
o.PolicyEnabled &&
!excludedUserTypes.Contains(o.OrganizationUserType) &&
o.OrganizationUserStatus >= minStatus &&
!o.IsProvider);
}
private OrganizationUserType[] GetUserTypesExcludedFromPolicy(PolicyType policyType)
{
switch (policyType)
{
case PolicyType.MasterPassword:
return Array.Empty<OrganizationUserType>();
case PolicyType.RequireSso:
// If 'EnforceSsoPolicyForAllUsers' is set to true then SSO policy applies to all user types otherwise it does not apply to Owner or Admin
if (_globalSettings.Sso.EnforceSsoPolicyForAllUsers)
{
return Array.Empty<OrganizationUserType>();
}
break;
}
return new[] { OrganizationUserType.Owner, OrganizationUserType.Admin };
}
private async Task DependsOnSingleOrgAsync(Organization org) private async Task DependsOnSingleOrgAsync(Organization org)
{ {
var singleOrg = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SingleOrg); var singleOrg = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SingleOrg);

View File

@ -46,6 +46,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
private readonly IApplicationCacheService _applicationCacheService; private readonly IApplicationCacheService _applicationCacheService;
private readonly IPaymentService _paymentService; private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IDataProtector _organizationServiceDataProtector; private readonly IDataProtector _organizationServiceDataProtector;
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
private readonly IFido2 _fido2; private readonly IFido2 _fido2;
@ -77,6 +78,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
IDataProtectionProvider dataProtectionProvider, IDataProtectionProvider dataProtectionProvider,
IPaymentService paymentService, IPaymentService paymentService,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
IPolicyService policyService,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
IFido2 fido2, IFido2 fido2,
ICurrentContext currentContext, ICurrentContext currentContext,
@ -110,6 +112,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
_applicationCacheService = applicationCacheService; _applicationCacheService = applicationCacheService;
_paymentService = paymentService; _paymentService = paymentService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_policyService = policyService;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector( _organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector"); "OrganizationServiceDataProtector");
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
@ -1414,8 +1417,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService) private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService)
{ {
var twoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id, var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
PolicyType.TwoFactorAuthentication);
var removeOrgUserTasks = twoFactorPolicies.Select(async p => var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
{ {

View File

@ -24,6 +24,7 @@ public class SendService : ISendService
private readonly ISendRepository _sendRepository; private readonly ISendRepository _sendRepository;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly ISendFileStorageService _sendFileStorageService; private readonly ISendFileStorageService _sendFileStorageService;
@ -45,12 +46,14 @@ public class SendService : ISendService
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
GlobalSettings globalSettings, GlobalSettings globalSettings,
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
IPolicyService policyService,
ICurrentContext currentContext) ICurrentContext currentContext)
{ {
_sendRepository = sendRepository; _sendRepository = sendRepository;
_userRepository = userRepository; _userRepository = userRepository;
_userService = userService; _userService = userService;
_policyRepository = policyRepository; _policyRepository = policyRepository;
_policyService = policyService;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_sendFileStorageService = sendFileStorageService; _sendFileStorageService = sendFileStorageService;
_passwordHasher = passwordHasher; _passwordHasher = passwordHasher;
@ -282,17 +285,17 @@ public class SendService : ISendService
return; return;
} }
var disableSendPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId.Value, var anyDisableSendPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value,
PolicyType.DisableSend); PolicyType.DisableSend);
if (disableSendPolicyCount > 0) if (anyDisableSendPolicies)
{ {
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())
{ {
var sendOptionsPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(userId.Value, PolicyType.SendOptions); var sendOptionsPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId.Value, PolicyType.SendOptions);
if (sendOptionsPolicies.Any(p => p.GetDataModel<SendOptionsPolicyData>()?.DisableHideEmail ?? false)) if (sendOptionsPolicies.Any(p => CoreHelpers.LoadClassFromJsonData<SendOptionsPolicyData>(p.PolicyData)?.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.");
} }

View File

@ -30,7 +30,7 @@ public class CipherService : ICipherService
private readonly IAttachmentStorageService _attachmentStorageService; private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository; private readonly IPolicyService _policyService;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
private readonly IReferenceEventService _referenceEventService; private readonly IReferenceEventService _referenceEventService;
@ -47,7 +47,7 @@ public class CipherService : ICipherService
IAttachmentStorageService attachmentStorageService, IAttachmentStorageService attachmentStorageService,
IEventService eventService, IEventService eventService,
IUserService userService, IUserService userService,
IPolicyRepository policyRepository, IPolicyService policyService,
GlobalSettings globalSettings, GlobalSettings globalSettings,
IReferenceEventService referenceEventService, IReferenceEventService referenceEventService,
ICurrentContext currentContext) ICurrentContext currentContext)
@ -62,7 +62,7 @@ public class CipherService : ICipherService
_attachmentStorageService = attachmentStorageService; _attachmentStorageService = attachmentStorageService;
_eventService = eventService; _eventService = eventService;
_userService = userService; _userService = userService;
_policyRepository = policyRepository; _policyService = policyService;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_referenceEventService = referenceEventService; _referenceEventService = referenceEventService;
_currentContext = currentContext; _currentContext = currentContext;
@ -134,9 +134,8 @@ public class CipherService : ICipherService
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 personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(savingUserId, var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership);
PolicyType.PersonalOwnership); if (anyPersonalOwnershipPolicies)
if (personalOwnershipPolicyCount > 0)
{ {
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.");
} }
@ -632,9 +631,8 @@ public class CipherService : ICipherService
var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId; var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId;
// 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 personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId.Value, var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value, PolicyType.PersonalOwnership);
PolicyType.PersonalOwnership); if (anyPersonalOwnershipPolicies)
if (personalOwnershipPolicyCount > 0)
{ {
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.");

View File

@ -39,9 +39,8 @@ public abstract class BaseRequestValidator<T> where T : class
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IPolicyRepository _policyRepository;
private readonly IUserRepository _userRepository;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IUserRepository _userRepository;
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory; private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
public BaseRequestValidator( public BaseRequestValidator(
@ -76,7 +75,7 @@ public abstract class BaseRequestValidator<T> where T : class
_logger = logger; _logger = logger;
_currentContext = currentContext; _currentContext = currentContext;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_policyRepository = policyRepository; _policyService = policyService;
_userRepository = userRepository; _userRepository = userRepository;
_policyService = policyService; _policyService = policyService;
_tokenDataFactory = tokenDataFactory; _tokenDataFactory = tokenDataFactory;
@ -341,33 +340,11 @@ public abstract class BaseRequestValidator<T> where T : class
return true; return true;
} }
// Is user apart of any orgs? Use cache for initial checks. // Check if user belongs to any organization with an active SSO policy
var orgs = (await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id)) var anySsoPoliciesApplicableToUser = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.RequireSso, OrganizationUserStatusType.Confirmed);
.ToList(); if (anySsoPoliciesApplicableToUser)
if (orgs.Any())
{ {
// Get all org abilities return false;
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
// Parse all user orgs that are enabled and have the ability to use sso
var ssoOrgs = orgs.Where(o => OrgCanUseSso(orgAbilities, o.Id));
if (ssoOrgs.Any())
{
// Parse users orgs and determine if require sso policy is enabled
var userOrgs = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
foreach (var userOrg in userOrgs.Where(o => o.Enabled && o.UseSso))
{
var orgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(userOrg.OrganizationId,
PolicyType.RequireSso);
// Owners and Admins are exempt from this policy
if (orgPolicy != null && orgPolicy.Enabled &&
(_globalSettings.Sso.EnforceSsoPolicyForAllUsers ||
(userOrg.Type != OrganizationUserType.Owner && userOrg.Type != OrganizationUserType.Admin)))
{
return false;
}
}
}
} }
// Default - continue validation process // Default - continue validation process
@ -380,12 +357,6 @@ public abstract class BaseRequestValidator<T> where T : class
orgAbilities[orgId].Enabled && orgAbilities[orgId].Using2fa; orgAbilities[orgId].Enabled && orgAbilities[orgId].Using2fa;
} }
private bool OrgCanUseSso(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
{
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseSso;
}
private Device GetDeviceFromRequest(ValidatedRequest request) private Device GetDeviceFromRequest(ValidatedRequest request)
{ {
var deviceIdentifier = request.Raw["DeviceIdentifier"]?.ToString(); var deviceIdentifier = request.Raw["DeviceIdentifier"]?.ToString();

View File

@ -491,4 +491,17 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
} }
public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<OrganizationUserPolicyDetails>(
$"[{Schema}].[{Table}_ReadByUserIdWithPolicyDetails]",
new { UserId = userId, PolicyType = policyType },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
} }

View File

@ -56,32 +56,4 @@ public class PolicyRepository : Repository<Policy, Guid>, IPolicyRepository
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;
}
}
} }

View File

@ -587,4 +587,38 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
} }
} }
public async Task<IEnumerable<OrganizationUserPolicyDetails>> GetByUserIdWithPolicyDetailsAsync(Guid userId, PolicyType policyType)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
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
let email = dbContext.Users.Find(userId).Email // Invited orgUsers do not have a UserId associated with them, so we have to match up their email
where p.Type == policyType &&
(ou.UserId == userId || ou.Email == email)
select new OrganizationUserPolicyDetails
{
OrganizationUserId = ou.Id,
OrganizationId = p.OrganizationId,
PolicyType = p.Type,
PolicyEnabled = p.Enabled,
PolicyData = p.Data,
OrganizationUserType = ou.Type,
OrganizationUserStatus = ou.Status,
OrganizationUserPermissionsData = ou.Permissions,
IsProvider = providerOrganizations.Any(po => po.OrganizationId == p.OrganizationId)
};
return await query.ToListAsync();
}
}
} }

View File

@ -48,29 +48,4 @@ public class PolicyRepository : Repository<Core.Entities.Policy, Policy, Guid>,
return Mapper.Map<List<Core.Entities.Policy>>(results); return Mapper.Map<List<Core.Entities.Policy>>(results);
} }
} }
public async Task<ICollection<Core.Entities.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<Core.Entities.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);
}
}
} }

View File

@ -1,50 +0,0 @@
using Bit.Core.Enums;
using Bit.Infrastructure.EntityFramework.Models;
namespace Bit.Infrastructure.EntityFramework.Repositories.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;
string userEmail = null;
if (_minimumStatus == OrganizationUserStatusType.Invited)
{
// Invited orgUsers do not have a UserId associated with them, so we have to match up their email
userEmail = dbContext.Users.Find(_userId)?.Email;
}
var query = from p in dbContext.Policies
join ou in dbContext.OrganizationUsers
on p.OrganizationId equals ou.OrganizationId
where
((_minimumStatus > OrganizationUserStatusType.Invited && ou.UserId == _userId) ||
(_minimumStatus == OrganizationUserStatusType.Invited && ou.Email == userEmail)) &&
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;
}
}

View File

@ -0,0 +1,34 @@
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
@UserId UNIQUEIDENTIFIER,
@PolicyType TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
OU.[Id] AS OrganizationUserId,
P.[OrganizationId],
P.[Type] AS PolicyType,
P.[Enabled] AS PolicyEnabled,
P.[Data] AS PolicyData,
OU.[Type] AS OrganizationUserType,
OU.[Status] AS OrganizationUserStatus,
OU.[Permissions] AS OrganizationUserPermissionsData,
CASE WHEN EXISTS (
SELECT 1
FROM [dbo].[ProviderUserView] PU
INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]
WHERE PU.[UserId] = OU.[UserId] AND PO.[OrganizationId] = P.[OrganizationId]
) THEN 1 ELSE 0 END AS IsProvider
FROM [dbo].[PolicyView] P
INNER JOIN [dbo].[OrganizationUserView] OU
ON P.[OrganizationId] = OU.[OrganizationId]
WHERE P.[Type] = @PolicyType AND
(
(OU.[Status] != 0 AND OU.[UserId] = @UserId) -- OrgUsers who have accepted their invite and are linked to a UserId
OR EXISTS (
SELECT 1
FROM [dbo].[UserView] U
WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email
)
)
END

View File

@ -0,0 +1,2 @@
-- Created 2023-03
-- DELETE FILE

View File

@ -0,0 +1,2 @@
-- Created 2023-03
-- DELETE FILE

View File

@ -0,0 +1,2 @@
-- Created 2023-03
-- DELETE FILE

View File

@ -5,12 +5,14 @@ using Bit.Core.Auth.Repositories;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
using PolicyFixtures = Bit.Core.Test.AutoFixture.PolicyFixtures; using PolicyFixtures = Bit.Core.Test.AutoFixture.PolicyFixtures;
namespace Bit.Core.Test.Services; namespace Bit.Core.Test.Services;
@ -394,10 +396,132 @@ public class PolicyServiceTests
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
} }
[Theory, BitAutoData]
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.GetPoliciesApplicableToUserAsync(userId, PolicyType.RequireSso);
Assert.Empty(result);
}
[Theory, BitAutoData]
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsOnePolicy(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
sutProvider.GetDependency<GlobalSettings>().Sso.EnforceSsoPolicyForAllUsers.Returns(true);
var result = await sutProvider.Sut
.GetPoliciesApplicableToUserAsync(userId, PolicyType.RequireSso);
Assert.Single(result);
Assert.True(result.All(details => details.PolicyEnabled));
Assert.True(result.All(details => details.PolicyType == PolicyType.RequireSso));
Assert.True(result.All(details => details.OrganizationUserType == OrganizationUserType.Owner));
Assert.True(result.All(details => details.OrganizationUserStatus == OrganizationUserStatusType.Confirmed));
Assert.True(result.All(details => !details.IsProvider));
}
[Theory, BitAutoData]
public async Task GetPoliciesApplicableToUserAsync_WithDisableTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.GetPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend);
Assert.Empty(result);
}
[Theory, BitAutoData]
public async Task GetPoliciesApplicableToUserAsync_WithDisableSendTypeFilter_WithInvitedUserStatusFilter_ReturnsOnePolicy(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.GetPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend, OrganizationUserStatusType.Invited);
Assert.Single(result);
Assert.True(result.All(details => details.PolicyEnabled));
Assert.True(result.All(details => details.PolicyType == PolicyType.DisableSend));
Assert.True(result.All(details => details.OrganizationUserType == OrganizationUserType.User));
Assert.True(result.All(details => details.OrganizationUserStatus == OrganizationUserStatusType.Invited));
Assert.True(result.All(details => !details.IsProvider));
}
[Theory, BitAutoData]
public async Task AnyPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsFalse(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.RequireSso);
Assert.False(result);
}
[Theory, BitAutoData]
public async Task AnyPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsTrue(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
sutProvider.GetDependency<GlobalSettings>().Sso.EnforceSsoPolicyForAllUsers.Returns(true);
var result = await sutProvider.Sut
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.RequireSso);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task AnyPoliciesApplicableToUserAsync_WithDisableTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsFalse(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend);
Assert.False(result);
}
[Theory, BitAutoData]
public async Task AnyPoliciesApplicableToUserAsync_WithDisableSendTypeFilter_WithInvitedUserStatusFilter_ReturnsTrue(Guid userId, SutProvider<PolicyService> sutProvider)
{
SetupUserPolicies(userId, sutProvider);
var result = await sutProvider.Sut
.AnyPoliciesApplicableToUserAsync(userId, PolicyType.DisableSend, OrganizationUserStatusType.Invited);
Assert.True(result);
}
private static void SetupOrg(SutProvider<PolicyService> sutProvider, Guid organizationId, Organization organization) private static void SetupOrg(SutProvider<PolicyService> sutProvider, Guid organizationId, Organization organization)
{ {
sutProvider.GetDependency<IOrganizationRepository>() sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organizationId) .GetByIdAsync(organizationId)
.Returns(Task.FromResult(organization)); .Returns(Task.FromResult(organization));
} }
private static void SetupUserPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByUserIdWithPolicyDetailsAsync(userId, PolicyType.RequireSso)
.Returns(new List<OrganizationUserPolicyDetails>
{
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = false, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false},
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = false },
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.RequireSso, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.Owner, OrganizationUserStatus = OrganizationUserStatusType.Confirmed, IsProvider = true }
});
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByUserIdWithPolicyDetailsAsync(userId, PolicyType.DisableSend)
.Returns(new List<OrganizationUserPolicyDetails>
{
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = false },
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = true }
});
}
} }

View File

@ -3,6 +3,7 @@ using System.Text.Json;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
@ -33,8 +34,8 @@ public class SendServiceTests
send.Id = default; send.Id = default;
send.Type = sendType; send.Type = sendType;
sutProvider.GetDependency<IPolicyRepository>().GetCountByTypeApplicableToUserIdAsync( sutProvider.GetDependency<IPolicyService>().AnyPoliciesApplicableToUserAsync(
Arg.Any<Guid>(), PolicyType.DisableSend).Returns(disableSendPolicyAppliesToUser ? 1 : 0); Arg.Any<Guid>(), PolicyType.DisableSend).Returns(disableSendPolicyAppliesToUser);
} }
// Disable Send policy check // Disable Send policy check
@ -79,10 +80,10 @@ public class SendServiceTests
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}); });
sutProvider.GetDependency<IPolicyRepository>().GetManyByTypeApplicableToUserIdAsync( sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(
Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<Policy> Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<OrganizationUserPolicyDetails>()
{ {
policy, new() { PolicyType = policy.Type, PolicyData = policy.Data, OrganizationId = policy.OrganizationId, PolicyEnabled = policy.Enabled }
}); });
} }

View File

@ -0,0 +1,43 @@
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
public class OrganizationUserPolicyDetailsCompare : IEqualityComparer<OrganizationUserPolicyDetails>
{
public bool Equals(OrganizationUserPolicyDetails x, OrganizationUserPolicyDetails y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (ReferenceEquals(x, null))
{
return false;
}
if (ReferenceEquals(y, null))
{
return false;
}
if (x.GetType() != y.GetType())
{
return false;
}
return x.OrganizationId.Equals(y.OrganizationId) &&
x.PolicyType == y.PolicyType &&
x.PolicyEnabled == y.PolicyEnabled &&
x.PolicyData == y.PolicyData &&
x.OrganizationUserType == y.OrganizationUserType &&
x.OrganizationUserStatus == y.OrganizationUserStatus &&
x.OrganizationUserPermissionsData == y.OrganizationUserPermissionsData &&
x.IsProvider == y.IsProvider;
}
public int GetHashCode(OrganizationUserPolicyDetails obj)
{
return HashCode.Combine(obj.OrganizationId, (int)obj.PolicyType, obj.PolicyEnabled, obj.PolicyData, (int)obj.OrganizationUserType, (int)obj.OrganizationUserStatus, obj.OrganizationUserPermissionsData, obj.IsProvider);
}
}

View File

@ -1,4 +1,10 @@
using Bit.Core.Entities; using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Test.AutoFixture.Attributes; using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture; using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
@ -144,4 +150,137 @@ public class OrganizationUserRepositoryTests
savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(postSqlOrgUser.Id); savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(postSqlOrgUser.Id);
Assert.True(savedSqlOrgUser == null); Assert.True(savedSqlOrgUser == null);
} }
[CiSkippedTheory]
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, false)] // Ordinary user
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Invited, true, false)] // Invited user
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Owner, false, OrganizationUserStatusType.Confirmed, true, false)] // Owner
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Admin, false, OrganizationUserStatusType.Confirmed, true, false)] // Admin
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, true, OrganizationUserStatusType.Confirmed, true, false)] // canManagePolicies
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, true)] // Provider
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, false, false)] // Policy disabled
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, true, false)] // No policy of Type
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Invited, true, false)] // User not minStatus
public async void GetByUserIdWithPolicyDetailsAsync_Works_DataMatches(
// Inline data
OrganizationUserType userType,
bool canManagePolicies,
OrganizationUserStatusType orgUserStatus,
bool policyEnabled,
bool isProvider,
// Auto data - models
Policy policy,
User user,
Organization organization,
OrganizationUser orgUser,
Provider provider,
ProviderOrganization providerOrganization,
ProviderUser providerUser,
OrganizationUserPolicyDetailsCompare equalityComparer,
// Auto data - EF repos
List<EfRepo.PolicyRepository> efPolicyRepository,
List<EfRepo.UserRepository> efUserRepository,
List<EfRepo.OrganizationRepository> efOrganizationRepository,
List<EfRepo.OrganizationUserRepository> suts,
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 = efPolicyRepository.ToList<IPolicyRepository>();
policyRepos.Add(sqlPolicyRepo);
var userRepos = efUserRepository.ToList<IUserRepository>();
userRepos.Add(sqlUserRepo);
var orgRepos = efOrganizationRepository.ToList<IOrganizationRepository>();
orgRepos.Add(sqlOrganizationRepo);
var orgUserRepos = suts.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;
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<OrganizationUserPolicyDetails>();
foreach (var policyRepo in policyRepos)
{
var i = policyRepos.IndexOf(policyRepo);
// Seed database
user.CreationDate = user.RevisionDate = DateTime.Now;
var savedUser = await userRepos[i].CreateAsync(user);
var savedOrg = await orgRepos[i].CreateAsync(organization);
// Invited orgUsers are not associated with an account yet, so they are identified by Email not UserId
if (orgUserStatus == OrganizationUserStatusType.Invited)
{
orgUser.Email = savedUser.Email;
orgUser.UserId = null;
}
else
{
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 (efPolicyRepository.Contains(policyRepo))
{
(policyRepo as EfRepo.BaseEntityFrameworkRepository).ClearChangeTracking();
}
// Act
var result = await orgUserRepos[i].GetByUserIdWithPolicyDetailsAsync(savedUser.Id, policy.Type);
results.Add(result.FirstOrDefault());
}
// Assert
var distinctItems = results.Distinct(equalityComparer);
Assert.Single(distinctItems);
}
} }

View File

@ -1,9 +1,4 @@
using System.Text.Json; using Bit.Core.Entities;
using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Test.AutoFixture.Attributes; using Bit.Core.Test.AutoFixture.Attributes;
using Bit.Infrastructure.EFIntegration.Test.AutoFixture; using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
@ -53,143 +48,4 @@ public class PolicyRepositoryTests
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, false, true, true, false)] // Ordinary user
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Invited, true, true, true, false)] // Invited user
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Owner, false, OrganizationUserStatusType.Confirmed, false, true, true, false)] // Owner
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.Admin, false, OrganizationUserStatusType.Confirmed, false, true, true, false)] // Admin
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, true, OrganizationUserStatusType.Confirmed, false, true, true, false)] // canManagePolicies
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, false, true, true, true)] // Provider
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, false, false, true, false)] // Policy disabled
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Confirmed, false, true, false, false)] // No policy of Type
[EfPolicyApplicableToUserInlineAutoData(OrganizationUserType.User, false, OrganizationUserStatusType.Invited, false, true, true, false)] // User not minStatus
public async void GetManyByTypeApplicableToUser_Works_DataMatches(
// Inline data
OrganizationUserType userType,
bool canManagePolicies,
OrganizationUserStatusType orgUserStatus,
bool includeInvited,
bool policyEnabled,
bool policySameType,
bool isProvider,
// Auto data - models
Policy policy,
User user,
Organization organization,
OrganizationUser orgUser,
Provider provider,
ProviderOrganization providerOrganization,
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<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);
// Invited orgUsers are not associated with an account yet, so they are identified by Email not UserId
if (orgUserStatus == OrganizationUserStatusType.Invited)
{
orgUser.Email = savedUser.Email;
orgUser.UserId = null;
}
else
{
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 EfRepo.BaseEntityFrameworkRepository).ClearChangeTracking();
}
var minStatus = includeInvited ? OrganizationUserStatusType.Invited : OrganizationUserStatusType.Accepted;
// Act
var result = await policyRepo.GetManyByTypeApplicableToUserIdAsync(savedUser.Id, queriedPolicyType, minStatus);
results.Add(result.FirstOrDefault());
}
// Assert
var distinctItems = results.Distinct(equalityComparer);
Assert.True(results.All(r => r == null) ||
!distinctItems.Skip(1).Any());
}
} }

View File

@ -0,0 +1,35 @@
CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]
@UserId UNIQUEIDENTIFIER,
@PolicyType TINYINT
AS
BEGIN
SET NOCOUNT ON
SELECT
OU.[Id] AS OrganizationUserId,
P.[OrganizationId],
P.[Type] AS PolicyType,
P.[Enabled] AS PolicyEnabled,
P.[Data] AS PolicyData,
OU.[Type] AS OrganizationUserType,
OU.[Status] AS OrganizationUserStatus,
OU.[Permissions] AS OrganizationUserPermissionsData,
CASE WHEN EXISTS (
SELECT 1
FROM [dbo].[ProviderUserView] PU
INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId]
WHERE PU.[UserId] = OU.[UserId] AND PO.[OrganizationId] = P.[OrganizationId]
) THEN 1 ELSE 0 END AS IsProvider
FROM [dbo].[PolicyView] P
INNER JOIN [dbo].[OrganizationUserView] OU
ON P.[OrganizationId] = OU.[OrganizationId]
WHERE P.[Type] = @PolicyType AND
(
(OU.[Status] != 0 AND OU.[UserId] = @UserId) -- OrgUsers who have accepted their invite and are linked to a UserId
OR EXISTS (
SELECT 1
FROM [dbo].[UserView] U
WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email
)
)
END
GO

View File

@ -0,0 +1,11 @@
-- Stored Procedure: Policy_CountByTypeApplicableToUser
DROP PROCEDURE [dbo].[Policy_CountByTypeApplicableToUser];
GO
-- Stored Procedure: Policy_ReadByTypeApplicableToUser
DROP PROCEDURE [dbo].[Policy_ReadByTypeApplicableToUser];
GO
-- Function: PolicyApplicableToUser
DROP FUNCTION [dbo].[PolicyApplicableToUser];
GO