1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05: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
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 RevokeAsync(Guid id);
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<ICollection<Policy>> GetManyByOrganizationIdAsync(Guid organizationId);
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.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
namespace Bit.Core.Services;
@ -12,4 +14,6 @@ public interface IPolicyService
/// Get the combined master password policy options for the specified user.
/// </summary>
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 IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly IReferenceEventService _referenceEventService;
@ -70,6 +71,7 @@ public class OrganizationService : IOrganizationService
IApplicationCacheService applicationCacheService,
IPaymentService paymentService,
IPolicyRepository policyRepository,
IPolicyService policyService,
ISsoConfigRepository ssoConfigRepository,
ISsoUserRepository ssoUserRepository,
IReferenceEventService referenceEventService,
@ -97,6 +99,7 @@ public class OrganizationService : IOrganizationService
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
_policyRepository = policyRepository;
_policyService = policyService;
_ssoConfigRepository = ssoConfigRepository;
_ssoUserRepository = ssoUserRepository;
_referenceEventService = referenceEventService;
@ -690,8 +693,8 @@ public class OrganizationService : IOrganizationService
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(ownerId, PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0)
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{
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.");
@ -1296,7 +1299,7 @@ public class OrganizationService : IOrganizationService
// Enforce Single Organization Policy of organization user is trying to join
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
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);
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
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(user.Id,
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0)
if (anySingleOrgPolicies)
{
throw new BadRequestException("You cannot join this organization because you are a member of " +
"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
if (!await userService.TwoFactorIsEnabledAsync(user))
{
var invitedTwoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id,
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
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
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
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);
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
var singleOrgPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId,
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId,
PolicyType.SingleOrg);
if (singleOrgPolicyCount > 0)
if (anySingleOrgPolicies)
{
throw new BadRequestException("You cannot restore this user because they are a member of " +
"another organization which forbids it");
@ -2407,7 +2410,7 @@ public class OrganizationService : IOrganizationService
var user = await _userRepository.GetByIdAsync(userId);
if (!await userService.TwoFactorIsEnabledAsync(user))
{
var invitedTwoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(userId,
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
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.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories;
using Bit.Core.Settings;
namespace Bit.Core.Services;
@ -16,6 +18,7 @@ public class PolicyService : IPolicyService
private readonly IPolicyRepository _policyRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IMailService _mailService;
private readonly GlobalSettings _globalSettings;
public PolicyService(
IEventService eventService,
@ -23,7 +26,8 @@ public class PolicyService : IPolicyService
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository,
ISsoConfigRepository ssoConfigRepository,
IMailService mailService)
IMailService mailService,
GlobalSettings globalSettings)
{
_eventService = eventService;
_organizationRepository = organizationRepository;
@ -31,6 +35,7 @@ public class PolicyService : IPolicyService
_policyRepository = policyRepository;
_ssoConfigRepository = ssoConfigRepository;
_mailService = mailService;
_globalSettings = globalSettings;
}
public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService,
@ -164,6 +169,47 @@ public class PolicyService : IPolicyService
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)
{
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 IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IDataProtector _organizationServiceDataProtector;
private readonly IReferenceEventService _referenceEventService;
private readonly IFido2 _fido2;
@ -77,6 +78,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
IDataProtectionProvider dataProtectionProvider,
IPaymentService paymentService,
IPolicyRepository policyRepository,
IPolicyService policyService,
IReferenceEventService referenceEventService,
IFido2 fido2,
ICurrentContext currentContext,
@ -110,6 +112,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
_policyRepository = policyRepository;
_policyService = policyService;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
_referenceEventService = referenceEventService;
@ -1414,8 +1417,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService)
{
var twoFactorPolicies = await _policyRepository.GetManyByTypeApplicableToUserIdAsync(user.Id,
PolicyType.TwoFactorAuthentication);
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
{

View File

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

View File

@ -30,7 +30,7 @@ public class CipherService : ICipherService
private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService;
private readonly IUserService _userService;
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly GlobalSettings _globalSettings;
private const long _fileSizeLeeway = 1024L * 1024L; // 1MB
private readonly IReferenceEventService _referenceEventService;
@ -47,7 +47,7 @@ public class CipherService : ICipherService
IAttachmentStorageService attachmentStorageService,
IEventService eventService,
IUserService userService,
IPolicyRepository policyRepository,
IPolicyService policyService,
GlobalSettings globalSettings,
IReferenceEventService referenceEventService,
ICurrentContext currentContext)
@ -62,7 +62,7 @@ public class CipherService : ICipherService
_attachmentStorageService = attachmentStorageService;
_eventService = eventService;
_userService = userService;
_policyRepository = policyRepository;
_policyService = policyService;
_globalSettings = globalSettings;
_referenceEventService = referenceEventService;
_currentContext = currentContext;
@ -134,9 +134,8 @@ public class CipherService : ICipherService
else
{
// Make sure the user can save new ciphers to their personal vault
var personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(savingUserId,
PolicyType.PersonalOwnership);
if (personalOwnershipPolicyCount > 0)
var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership);
if (anyPersonalOwnershipPolicies)
{
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;
// Make sure the user can save new ciphers to their personal vault
var personalOwnershipPolicyCount = await _policyRepository.GetCountByTypeApplicableToUserIdAsync(userId.Value,
PolicyType.PersonalOwnership);
if (personalOwnershipPolicyCount > 0)
var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value, PolicyType.PersonalOwnership);
if (anyPersonalOwnershipPolicies)
{
throw new BadRequestException("You cannot import items into your personal vault because you are " +
"a member of an organization which forbids it.");