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:
parent
99b0953acd
commit
8d3fe12170
@ -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; }
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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 =>
|
||||
{
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.");
|
||||
|
@ -39,9 +39,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
private readonly ILogger _logger;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
|
||||
|
||||
public BaseRequestValidator(
|
||||
@ -76,7 +75,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
_logger = logger;
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
_policyRepository = policyRepository;
|
||||
_policyService = policyService;
|
||||
_userRepository = userRepository;
|
||||
_policyService = policyService;
|
||||
_tokenDataFactory = tokenDataFactory;
|
||||
@ -341,33 +340,11 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is user apart of any orgs? Use cache for initial checks.
|
||||
var orgs = (await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id))
|
||||
.ToList();
|
||||
if (orgs.Any())
|
||||
// Check if user belongs to any organization with an active SSO policy
|
||||
var anySsoPoliciesApplicableToUser = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.RequireSso, OrganizationUserStatusType.Confirmed);
|
||||
if (anySsoPoliciesApplicableToUser)
|
||||
{
|
||||
// Get all org abilities
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default - continue validation process
|
||||
@ -380,12 +357,6 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
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)
|
||||
{
|
||||
var deviceIdentifier = request.Raw["DeviceIdentifier"]?.ToString();
|
||||
|
@ -491,4 +491,17 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,32 +56,4 @@ public class PolicyRepository : Repository<Policy, Guid>, IPolicyRepository
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -587,4 +587,38 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,29 +48,4 @@ public class PolicyRepository : Repository<Core.Entities.Policy, Policy, Guid>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
2
src/Sql/dbo_future/Functions/PolicyApplicableToUser.sql
Normal file
2
src/Sql/dbo_future/Functions/PolicyApplicableToUser.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Created 2023-03
|
||||
-- DELETE FILE
|
@ -0,0 +1,2 @@
|
||||
-- Created 2023-03
|
||||
-- DELETE FILE
|
@ -0,0 +1,2 @@
|
||||
-- Created 2023-03
|
||||
-- DELETE FILE
|
@ -5,12 +5,14 @@ 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.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
using PolicyFixtures = Bit.Core.Test.AutoFixture.PolicyFixtures;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
@ -394,10 +396,132 @@ public class PolicyServiceTests
|
||||
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)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organizationId)
|
||||
.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 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Text.Json;
|
||||
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.Services;
|
||||
@ -33,8 +34,8 @@ public class SendServiceTests
|
||||
send.Id = default;
|
||||
send.Type = sendType;
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetCountByTypeApplicableToUserIdAsync(
|
||||
Arg.Any<Guid>(), PolicyType.DisableSend).Returns(disableSendPolicyAppliesToUser ? 1 : 0);
|
||||
sutProvider.GetDependency<IPolicyService>().AnyPoliciesApplicableToUserAsync(
|
||||
Arg.Any<Guid>(), PolicyType.DisableSend).Returns(disableSendPolicyAppliesToUser);
|
||||
}
|
||||
|
||||
// Disable Send policy check
|
||||
@ -79,10 +80,10 @@ public class SendServiceTests
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByTypeApplicableToUserIdAsync(
|
||||
Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<Policy>
|
||||
sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(
|
||||
Arg.Any<Guid>(), PolicyType.SendOptions).Returns(new List<OrganizationUserPolicyDetails>()
|
||||
{
|
||||
policy,
|
||||
new() { PolicyType = policy.Type, PolicyData = policy.Data, OrganizationId = policy.OrganizationId, PolicyEnabled = policy.Enabled }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
|
||||
@ -144,4 +150,137 @@ public class OrganizationUserRepositoryTests
|
||||
savedSqlOrgUser = await sqlOrgUserRepo.GetByIdAsync(postSqlOrgUser.Id);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
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.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Test.AutoFixture.Attributes;
|
||||
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
|
||||
@ -53,143 +48,4 @@ public class PolicyRepositoryTests
|
||||
var distinctItems = savedPolicys.Distinct(equalityComparer);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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
|
11
util/Migrator/DbScripts_future/2023-03-FutureMigration.sql
Normal file
11
util/Migrator/DbScripts_future/2023-03-FutureMigration.sql
Normal 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
|
Loading…
Reference in New Issue
Block a user