diff --git a/src/Api/AdminConsole/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs index bb77b5b3d375..72374a897f9c 100644 --- a/src/Api/AdminConsole/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -10,7 +10,6 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Context; @@ -32,7 +31,7 @@ public class PoliciesController : Controller private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory; private readonly IPolicyRepository _policyRepository; - private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand; + private readonly ISavePolicyCommand _savePolicyCommand; private readonly IPolicyQuery _policyQuery; public PoliciesController(IPolicyRepository policyRepository, @@ -41,7 +40,7 @@ public PoliciesController(IPolicyRepository policyRepository, IDataProtectorTokenFactory orgUserInviteTokenDataFactory, IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery, IOrganizationRepository organizationRepository, - IVNextSavePolicyCommand vNextSavePolicyCommand, + ISavePolicyCommand savePolicyCommand, IPolicyQuery policyQuery) { _policyRepository = policyRepository; @@ -50,7 +49,7 @@ public PoliciesController(IPolicyRepository policyRepository, _organizationRepository = organizationRepository; _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; - _vNextSavePolicyCommand = vNextSavePolicyCommand; + _savePolicyCommand = savePolicyCommand; _policyQuery = policyQuery; } @@ -146,7 +145,7 @@ public async Task PutVNext(Guid orgId, PolicyType type, [Fr { var savePolicyRequest = await model.ToSavePolicyModelAsync(orgId, type, _currentContext); - var policy = await _vNextSavePolicyCommand.SaveAsync(savePolicyRequest); + var policy = await _savePolicyCommand.SaveAsync(savePolicyRequest); return new PolicyResponseModel(policy); } diff --git a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index cf8da813be72..4d2be624403a 100644 --- a/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -6,7 +6,7 @@ using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Microsoft.AspNetCore.Authorization; @@ -20,16 +20,16 @@ public class PoliciesController : Controller { private readonly IPolicyRepository _policyRepository; private readonly ICurrentContext _currentContext; - private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand; + private readonly ISavePolicyCommand _savePolicyCommand; public PoliciesController( IPolicyRepository policyRepository, ICurrentContext currentContext, - IVNextSavePolicyCommand vNextSavePolicyCommand) + ISavePolicyCommand savePolicyCommand) { _policyRepository = policyRepository; _currentContext = currentContext; - _vNextSavePolicyCommand = vNextSavePolicyCommand; + _savePolicyCommand = savePolicyCommand; } /// @@ -84,7 +84,7 @@ public async Task List() public async Task Put(PolicyType type, [FromBody] PolicyUpdateRequestModel model) { var savePolicyModel = model.ToSavePolicyModel(_currentContext.OrganizationId!.Value, type); - var policy = await _vNextSavePolicyCommand.SaveAsync(savePolicyModel); + var policy = await _savePolicyCommand.SaveAsync(savePolicyModel); var response = new PolicyResponseModel(policy); return new JsonResult(response); diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs index aec6380ce2b9..43fb09e3ca83 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs @@ -4,8 +4,8 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -24,7 +24,7 @@ public class VerifyOrganizationDomainCommand( IEventService eventService, IGlobalSettings globalSettings, ICurrentContext currentContext, - IVNextSavePolicyCommand vNextSavePolicyCommand, + ISavePolicyCommand savePolicyCommand, IMailService mailService, IOrganizationUserRepository organizationUserRepository, IOrganizationRepository organizationRepository, @@ -142,7 +142,7 @@ private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IAct }; var savePolicyModel = new SavePolicyModel(policyUpdate, actingUser); - await vNextSavePolicyCommand.SaveAsync(savePolicyModel); + await savePolicyCommand.SaveAsync(savePolicyModel); } private async Task SendVerifiedDomainUserEmailAsync(OrganizationDomain domain) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyValidator.cs deleted file mode 100644 index d3df63b6ac49..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyValidator.cs +++ /dev/null @@ -1,47 +0,0 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies; - -/// -/// Defines behavior and functionality for a given PolicyType. -/// -/// -/// All methods defined in this interface are for the PolicyService#SavePolicy method. This needs to be supported until -/// we successfully refactor policy validators over to policy validation handlers -/// -public interface IPolicyValidator -{ - /// - /// The PolicyType that this definition relates to. - /// - public PolicyType Type { get; } - - /// - /// PolicyTypes that must be enabled before this policy can be enabled, if any. - /// These dependencies will be checked when this policy is enabled and when any required policy is disabled. - /// - public IEnumerable RequiredPolicies { get; } - - /// - /// Validates a policy before saving it. - /// Do not use this for simple dependencies between different policies - see instead. - /// Implementation is optional; by default it will not perform any validation. - /// - /// The policy update request - /// The current policy, if any - /// A validation error if validation was unsuccessful, otherwise an empty string - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy); - - /// - /// Performs side effects after a policy is validated but before it is saved. - /// For example, this can be used to remove non-compliant users from the organization. - /// Implementation is optional; by default it will not perform any side effects. - /// - /// The policy update request - /// The current policy, if any - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy); -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPostSavePolicySideEffect.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPostSavePolicySideEffect.cs deleted file mode 100644 index e90945d12d10..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPostSavePolicySideEffect.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies; - -public interface IPostSavePolicySideEffect -{ - public Task ExecuteSideEffectsAsync(SavePolicyModel policyRequest, Policy postUpdatedPolicy, - Policy? previousPolicyState); -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/ISavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/ISavePolicyCommand.cs index 73278d77d2f3..31cf5caffd78 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/ISavePolicyCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/ISavePolicyCommand.cs @@ -1,15 +1,35 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; +using Bit.Core.Exceptions; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies; +/// +/// Handles creating or updating organization policies with validation and side effect execution. +/// +/// +/// Workflow: +/// 1. Validates organization can use policies +/// 2. Validates required and dependent policies +/// 3. Runs policy-specific validation () +/// 4. Executes pre-save logic () +/// 5. Saves the policy +/// 6. Logs the event +/// 7. Executes post-save logic () +/// public interface ISavePolicyCommand { - Task SaveAsync(PolicyUpdate policy); - /// - /// FIXME: this is a first pass at implementing side effects after the policy has been saved, which was not supported by the validator pattern. - /// However, this needs to be implemented in a policy-agnostic way rather than building out switch statements in the command itself. + /// Performs the necessary validations, saves the policy and any side effects /// - Task VNextSaveAsync(SavePolicyModel policyRequest); + /// Policy data, acting user, and metadata. + /// The saved policy with updated revision and applied changes. + /// + /// Thrown if: + /// - The organization can’t use policies + /// - Dependent policies are missing or block changes + /// - Custom validation fails + /// + Task SaveAsync(SavePolicyModel policyRequest); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs index cc234c1361b6..4c682d802578 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SavePolicyCommand.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -11,47 +12,46 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; -public class SavePolicyCommand : ISavePolicyCommand +public class SavePolicyCommand( + IOrganizationRepository organizationRepository, + IEventService eventService, + IPolicyRepository policyRepository, + IEnumerable policyUpdateEventHandlers, + TimeProvider timeProvider, + IPolicyEventHandlerFactory policyEventHandlerFactory, + IPushNotificationService pushNotificationService) + : ISavePolicyCommand { - private readonly IOrganizationRepository _organizationRepository; - private readonly IEventService _eventService; - private readonly IPolicyRepository _policyRepository; - private readonly IReadOnlyDictionary _policyValidators; - private readonly TimeProvider _timeProvider; - private readonly IPostSavePolicySideEffect _postSavePolicySideEffect; - private readonly IPushNotificationService _pushNotificationService; - - public SavePolicyCommand( - IOrganizationRepository organizationRepository, - IEventService eventService, - IPolicyRepository policyRepository, - IEnumerable policyValidators, - TimeProvider timeProvider, - IPostSavePolicySideEffect postSavePolicySideEffect, - IPushNotificationService pushNotificationService) + + public async Task SaveAsync(SavePolicyModel policyRequest) { - _organizationRepository = organizationRepository; - _eventService = eventService; - _policyRepository = policyRepository; - _timeProvider = timeProvider; - _postSavePolicySideEffect = postSavePolicySideEffect; - _pushNotificationService = pushNotificationService; - - var policyValidatorsDict = new Dictionary(); - foreach (var policyValidator in policyValidators) - { - if (!policyValidatorsDict.TryAdd(policyValidator.Type, policyValidator)) - { - throw new Exception($"Duplicate PolicyValidator for {policyValidator.Type} policy."); - } - } + var policyUpdateRequest = policyRequest.PolicyUpdate; + var organizationId = policyUpdateRequest.OrganizationId; + + await EnsureOrganizationCanUsePolicyAsync(organizationId); + + var savedPoliciesDict = await GetCurrentPolicyStateAsync(organizationId); + + var currentPolicy = savedPoliciesDict.GetValueOrDefault(policyUpdateRequest.Type); + + ValidatePolicyDependencies(policyUpdateRequest, currentPolicy, savedPoliciesDict); + + await ValidateTargetedPolicyAsync(policyRequest, currentPolicy); + + await ExecutePreUpsertSideEffectAsync(policyRequest, currentPolicy); + + var upsertedPolicy = await UpsertPolicyAsync(policyUpdateRequest); - _policyValidators = policyValidatorsDict; + await eventService.LogPolicyEventAsync(upsertedPolicy, EventType.Policy_Updated); + + await ExecutePostUpsertSideEffectAsync(policyRequest, upsertedPolicy, currentPolicy); + + return upsertedPolicy; } - public async Task SaveAsync(PolicyUpdate policyUpdate) + private async Task EnsureOrganizationCanUsePolicyAsync(Guid organizationId) { - var org = await _organizationRepository.GetByIdAsync(policyUpdate.OrganizationId); + var org = await organizationRepository.GetByIdAsync(organizationId); if (org == null) { throw new BadRequestException("Organization not found"); @@ -61,108 +61,143 @@ public async Task SaveAsync(PolicyUpdate policyUpdate) { throw new BadRequestException("This organization cannot use policies."); } + } - if (_policyValidators.TryGetValue(policyUpdate.Type, out var validator)) - { - await RunValidatorAsync(validator, policyUpdate); - } - - var policy = await _policyRepository.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) + private async Task UpsertPolicyAsync(PolicyUpdate policyUpdateRequest) + { + var policy = await policyRepository.GetByOrganizationIdTypeAsync(policyUpdateRequest.OrganizationId, policyUpdateRequest.Type) ?? new Policy { - OrganizationId = policyUpdate.OrganizationId, - Type = policyUpdate.Type, - CreationDate = _timeProvider.GetUtcNow().UtcDateTime + OrganizationId = policyUpdateRequest.OrganizationId, + Type = policyUpdateRequest.Type, + CreationDate = timeProvider.GetUtcNow().UtcDateTime }; - policy.Enabled = policyUpdate.Enabled; - policy.Data = policyUpdate.Data; - policy.RevisionDate = _timeProvider.GetUtcNow().UtcDateTime; - - await _policyRepository.UpsertAsync(policy); - await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated); - - await PushPolicyUpdateToClients(policy.OrganizationId, policy); + policy.Enabled = policyUpdateRequest.Enabled; + policy.Data = policyUpdateRequest.Data; + policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; + await policyRepository.UpsertAsync(policy); + await PushPolicyUpdateToClients(policyUpdateRequest.OrganizationId, policy); return policy; } - public async Task VNextSaveAsync(SavePolicyModel policyRequest) + private async Task ValidateTargetedPolicyAsync(SavePolicyModel policyRequest, + Policy? currentPolicy) { - var (_, currentPolicy) = await GetCurrentPolicyStateAsync(policyRequest.PolicyUpdate); - - var policy = await SaveAsync(policyRequest.PolicyUpdate); + await ExecutePolicyEventAsync( + policyRequest.PolicyUpdate.Type, + async validator => + { + var validationError = await validator.ValidateAsync(policyRequest, currentPolicy); + if (!string.IsNullOrEmpty(validationError)) + { + throw new BadRequestException(validationError); + } + }); + } - await ExecutePostPolicySaveSideEffectsForSupportedPoliciesAsync(policyRequest, policy, currentPolicy); + private void ValidatePolicyDependencies( + PolicyUpdate policyUpdateRequest, + Policy? currentPolicy, + Dictionary savedPoliciesDict) + { + var isCurrentlyEnabled = currentPolicy?.Enabled == true; + var isBeingEnabled = policyUpdateRequest.Enabled && !isCurrentlyEnabled; + var isBeingDisabled = !policyUpdateRequest.Enabled && isCurrentlyEnabled; - return policy; + if (isBeingEnabled) + { + ValidateEnablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); + } + else if (isBeingDisabled) + { + ValidateDisablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); + } } - private async Task ExecutePostPolicySaveSideEffectsForSupportedPoliciesAsync(SavePolicyModel policyRequest, Policy postUpdatedPolicy, Policy? previousPolicyState) + private void ValidateDisablingRequirements( + PolicyType policyType, + Dictionary savedPoliciesDict) { - if (postUpdatedPolicy.Type == PolicyType.OrganizationDataOwnership) + var dependentPolicyTypes = policyUpdateEventHandlers + .OfType() + .Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType)) + .Select(otherValidator => otherValidator.Type) + .Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) && + savedPolicy.Enabled) + .ToList(); + + switch (dependentPolicyTypes) { - await _postSavePolicySideEffect.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + case { Count: 1 }: + throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {policyType.GetName()} policy."); + case { Count: > 1 }: + throw new BadRequestException($"Turn off all of the policies that require the {policyType.GetName()} policy."); } } - private async Task RunValidatorAsync(IPolicyValidator validator, PolicyUpdate policyUpdate) + private void ValidateEnablingRequirements( + PolicyType policyType, + Dictionary savedPoliciesDict) { - var (savedPoliciesDict, currentPolicy) = await GetCurrentPolicyStateAsync(policyUpdate); - - // If enabling this policy - check that all policy requirements are satisfied - if (currentPolicy is not { Enabled: true } && policyUpdate.Enabled) - { - var missingRequiredPolicyTypes = validator.RequiredPolicies - .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) - .ToList(); + var result = policyEventHandlerFactory.GetHandler(policyType); - if (missingRequiredPolicyTypes.Count != 0) + result.Switch( + validator => { - throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {validator.Type.GetName()} policy."); - } - } + var missingRequiredPolicyTypes = validator.RequiredPolicies + .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) + .ToList(); + + if (missingRequiredPolicyTypes.Count != 0) + { + throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {policyType.GetName()} policy."); + } + }, + _ => { /* Policy has no required dependencies */ }); + } - // If disabling this policy - ensure it's not required by any other policy - if (currentPolicy is { Enabled: true } && !policyUpdate.Enabled) - { - var dependentPolicyTypes = _policyValidators.Values - .Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyUpdate.Type)) - .Select(otherValidator => otherValidator.Type) - .Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) && - savedPolicy.Enabled) - .ToList(); - - switch (dependentPolicyTypes) - { - case { Count: 1 }: - throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {validator.Type.GetName()} policy."); - case { Count: > 1 }: - throw new BadRequestException($"Turn off all of the policies that require the {validator.Type.GetName()} policy."); - } - } + private async Task ExecutePreUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy? currentPolicy) + { + await ExecutePolicyEventAsync( + policyRequest.PolicyUpdate.Type, + handler => handler.ExecutePreUpsertSideEffectAsync(policyRequest, currentPolicy)); + } + private async Task ExecutePostUpsertSideEffectAsync( + SavePolicyModel policyRequest, + Policy postUpsertedPolicyState, + Policy? previousPolicyState) + { + await ExecutePolicyEventAsync( + policyRequest.PolicyUpdate.Type, + handler => handler.ExecutePostUpsertSideEffectAsync( + policyRequest, + postUpsertedPolicyState, + previousPolicyState)); + } - // Run other validation - var validationError = await validator.ValidateAsync(policyUpdate, currentPolicy); - if (!string.IsNullOrEmpty(validationError)) - { - throw new BadRequestException(validationError); - } + private async Task ExecutePolicyEventAsync(PolicyType type, Func func) where T : IPolicyUpdateEvent + { + var handler = policyEventHandlerFactory.GetHandler(type); - // Run side effects - await validator.OnSaveSideEffectsAsync(policyUpdate, currentPolicy); + await handler.Match( + async h => await func(h), + _ => Task.CompletedTask + ); } - private async Task<(Dictionary savedPoliciesDict, Policy? currentPolicy)> GetCurrentPolicyStateAsync(PolicyUpdate policyUpdate) + private async Task> GetCurrentPolicyStateAsync(Guid organizationId) { - var savedPolicies = await _policyRepository.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId); + var savedPolicies = await policyRepository.GetManyByOrganizationIdAsync(organizationId); // Note: policies may be missing from this dict if they have never been enabled var savedPoliciesDict = savedPolicies.ToDictionary(p => p.Type); - var currentPolicy = savedPoliciesDict.GetValueOrDefault(policyUpdate.Type); - return (savedPoliciesDict, currentPolicy); + return savedPoliciesDict; } - Task PushPolicyUpdateToClients(Guid organizationId, Policy policy) => this._pushNotificationService.PushAsync(new PushNotification + Task PushPolicyUpdateToClients(Guid organizationId, Policy policy) => pushNotificationService.PushAsync(new PushNotification { Type = PushType.PolicyChanged, Target = NotificationTarget.Organization, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs deleted file mode 100644 index dd53d86e6b10..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/VNextSavePolicyCommand.cs +++ /dev/null @@ -1,212 +0,0 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Enums; -using Bit.Core.Exceptions; -using Bit.Core.Models; -using Bit.Core.Platform.Push; -using Bit.Core.Repositories; -using Bit.Core.Services; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; - -public class VNextSavePolicyCommand( - IOrganizationRepository organizationRepository, - IEventService eventService, - IPolicyRepository policyRepository, - IEnumerable policyUpdateEventHandlers, - TimeProvider timeProvider, - IPolicyEventHandlerFactory policyEventHandlerFactory, - IPushNotificationService pushNotificationService) - : IVNextSavePolicyCommand -{ - - public async Task SaveAsync(SavePolicyModel policyRequest) - { - var policyUpdateRequest = policyRequest.PolicyUpdate; - var organizationId = policyUpdateRequest.OrganizationId; - - await EnsureOrganizationCanUsePolicyAsync(organizationId); - - var savedPoliciesDict = await GetCurrentPolicyStateAsync(organizationId); - - var currentPolicy = savedPoliciesDict.GetValueOrDefault(policyUpdateRequest.Type); - - ValidatePolicyDependencies(policyUpdateRequest, currentPolicy, savedPoliciesDict); - - await ValidateTargetedPolicyAsync(policyRequest, currentPolicy); - - await ExecutePreUpsertSideEffectAsync(policyRequest, currentPolicy); - - var upsertedPolicy = await UpsertPolicyAsync(policyUpdateRequest); - - await eventService.LogPolicyEventAsync(upsertedPolicy, EventType.Policy_Updated); - - await ExecutePostUpsertSideEffectAsync(policyRequest, upsertedPolicy, currentPolicy); - - return upsertedPolicy; - } - - private async Task EnsureOrganizationCanUsePolicyAsync(Guid organizationId) - { - var org = await organizationRepository.GetByIdAsync(organizationId); - if (org == null) - { - throw new BadRequestException("Organization not found"); - } - - if (!org.UsePolicies) - { - throw new BadRequestException("This organization cannot use policies."); - } - } - - private async Task UpsertPolicyAsync(PolicyUpdate policyUpdateRequest) - { - var policy = await policyRepository.GetByOrganizationIdTypeAsync(policyUpdateRequest.OrganizationId, policyUpdateRequest.Type) - ?? new Policy - { - OrganizationId = policyUpdateRequest.OrganizationId, - Type = policyUpdateRequest.Type, - CreationDate = timeProvider.GetUtcNow().UtcDateTime - }; - - policy.Enabled = policyUpdateRequest.Enabled; - policy.Data = policyUpdateRequest.Data; - policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime; - - await policyRepository.UpsertAsync(policy); - await PushPolicyUpdateToClients(policyUpdateRequest.OrganizationId, policy); - return policy; - } - - private async Task ValidateTargetedPolicyAsync(SavePolicyModel policyRequest, - Policy? currentPolicy) - { - await ExecutePolicyEventAsync( - policyRequest.PolicyUpdate.Type, - async validator => - { - var validationError = await validator.ValidateAsync(policyRequest, currentPolicy); - if (!string.IsNullOrEmpty(validationError)) - { - throw new BadRequestException(validationError); - } - }); - } - - private void ValidatePolicyDependencies( - PolicyUpdate policyUpdateRequest, - Policy? currentPolicy, - Dictionary savedPoliciesDict) - { - var isCurrentlyEnabled = currentPolicy?.Enabled == true; - var isBeingEnabled = policyUpdateRequest.Enabled && !isCurrentlyEnabled; - var isBeingDisabled = !policyUpdateRequest.Enabled && isCurrentlyEnabled; - - if (isBeingEnabled) - { - ValidateEnablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); - } - else if (isBeingDisabled) - { - ValidateDisablingRequirements(policyUpdateRequest.Type, savedPoliciesDict); - } - } - - private void ValidateDisablingRequirements( - PolicyType policyType, - Dictionary savedPoliciesDict) - { - var dependentPolicyTypes = policyUpdateEventHandlers - .OfType() - .Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType)) - .Select(otherValidator => otherValidator.Type) - .Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) && - savedPolicy.Enabled) - .ToList(); - - switch (dependentPolicyTypes) - { - case { Count: 1 }: - throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {policyType.GetName()} policy."); - case { Count: > 1 }: - throw new BadRequestException($"Turn off all of the policies that require the {policyType.GetName()} policy."); - } - } - - private void ValidateEnablingRequirements( - PolicyType policyType, - Dictionary savedPoliciesDict) - { - var result = policyEventHandlerFactory.GetHandler(policyType); - - result.Switch( - validator => - { - var missingRequiredPolicyTypes = validator.RequiredPolicies - .Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true }) - .ToList(); - - if (missingRequiredPolicyTypes.Count != 0) - { - throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {policyType.GetName()} policy."); - } - }, - _ => { /* Policy has no required dependencies */ }); - } - - private async Task ExecutePreUpsertSideEffectAsync( - SavePolicyModel policyRequest, - Policy? currentPolicy) - { - await ExecutePolicyEventAsync( - policyRequest.PolicyUpdate.Type, - handler => handler.ExecutePreUpsertSideEffectAsync(policyRequest, currentPolicy)); - } - private async Task ExecutePostUpsertSideEffectAsync( - SavePolicyModel policyRequest, - Policy postUpsertedPolicyState, - Policy? previousPolicyState) - { - await ExecutePolicyEventAsync( - policyRequest.PolicyUpdate.Type, - handler => handler.ExecutePostUpsertSideEffectAsync( - policyRequest, - postUpsertedPolicyState, - previousPolicyState)); - } - - private async Task ExecutePolicyEventAsync(PolicyType type, Func func) where T : IPolicyUpdateEvent - { - var handler = policyEventHandlerFactory.GetHandler(type); - - await handler.Match( - async h => await func(h), - _ => Task.CompletedTask - ); - } - - private async Task> GetCurrentPolicyStateAsync(Guid organizationId) - { - var savedPolicies = await policyRepository.GetManyByOrganizationIdAsync(organizationId); - // Note: policies may be missing from this dict if they have never been enabled - var savedPoliciesDict = savedPolicies.ToDictionary(p => p.Type); - return savedPoliciesDict; - } - - Task PushPolicyUpdateToClients(Guid organizationId, Policy policy) => pushNotificationService.PushAsync(new PushNotification - { - Type = PushType.PolicyChanged, - Target = NotificationTarget.Organization, - TargetId = organizationId, - ExcludeCurrentContext = false, - Payload = new SyncPolicyPushNotification - { - Policy = policy, - OrganizationId = organizationId - } - }); -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandler.cs similarity index 83% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandler.cs index 0c1a5877c056..07aa30b63d24 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandler.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandler.cs @@ -6,7 +6,7 @@ using Bit.Core.Auth.UserFeatures.EmergencyAccess.Interfaces; using Bit.Core.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; /// /// Represents an event handler for the Automatic User Confirmation policy. @@ -22,14 +22,16 @@ public class AutomaticUserConfirmationPolicyEventHandler( IAutomaticUserConfirmationOrganizationPolicyComplianceValidator validator, IOrganizationUserRepository organizationUserRepository, IDeleteEmergencyAccessCommand deleteEmergencyAccessCommand) - : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent, IOnPolicyPreUpdateEvent + : IPolicyValidationEvent, IEnforceDependentPoliciesEvent, IOnPolicyPreUpdateEvent { public PolicyType Type => PolicyType.AutomaticUserConfirmation; public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) + public async Task ValidateAsync(SavePolicyModel savePolicyModel, Policy? currentPolicy) { + var policyUpdate = savePolicyModel.PolicyUpdate; + var isNotEnablingPolicy = policyUpdate is not { Enabled: true }; var policyAlreadyEnabled = currentPolicy is { Enabled: true }; if (isNotEnablingPolicy || policyAlreadyEnabled) @@ -44,16 +46,10 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre _ => string.Empty); } - public async Task ValidateAsync(SavePolicyModel savePolicyModel, Policy? currentPolicy) => - await ValidateAsync(savePolicyModel.PolicyUpdate, currentPolicy); - public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { var isNotEnablingPolicy = policyUpdate is not { Enabled: true }; var policyAlreadyEnabled = currentPolicy is { Enabled: true }; if (isNotEnablingPolicy || policyAlreadyEnabled) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandler.cs similarity index 66% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandler.cs index 36634ae2ba71..12ceb871ee86 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandler.cs @@ -1,18 +1,16 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class BlockClaimedDomainAccountCreationPolicyValidator : IPolicyValidator, IPolicyValidationEvent +public class BlockClaimedDomainAccountCreationPolicyEventHandler : IPolicyValidationEvent { private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery; - public BlockClaimedDomainAccountCreationPolicyValidator( + public BlockClaimedDomainAccountCreationPolicyEventHandler( IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery) { _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; @@ -20,16 +18,10 @@ public BlockClaimedDomainAccountCreationPolicyValidator( public PolicyType Type => PolicyType.BlockClaimedDomainAccountCreation; - // No prerequisites - this policy stands alone - public IEnumerable RequiredPolicies => []; - public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { // Only validate when trying to ENABLE the policy if (policyUpdate is { Enabled: true }) { @@ -43,7 +35,4 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre // Disabling the policy is always allowed return string.Empty; } - - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - => Task.CompletedTask; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEvent.cs similarity index 99% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEvent.cs index c12b5702c747..4c425fd4beac 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEvent.cs @@ -6,7 +6,7 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.Utilities; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; /// /// Syncs changes to the DisableSend policy into the SendControls policy row. diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandler.cs similarity index 75% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandler.cs index 52a7e3e8805b..9a5dc01c01b5 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandler.cs @@ -1,30 +1,24 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class FreeFamiliesForEnterprisePolicyValidator( +public class FreeFamiliesForEnterprisePolicyEventHandler( IOrganizationSponsorshipRepository organizationSponsorshipRepository, IMailService mailService, IOrganizationRepository organizationRepository) - : IPolicyValidator, IOnPolicyPreUpdateEvent + : IOnPolicyPreUpdateEvent { public PolicyType Type => PolicyType.FreeFamiliesSponsorshipPolicy; - public IEnumerable RequiredPolicies => []; public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) { await NotifiesUserWithApplicablePoliciesAsync(policyUpdate); @@ -48,5 +42,4 @@ await mailService.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(org.Frie } } - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/MaximumVaultTimeoutPolicyEventHandler.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/MaximumVaultTimeoutPolicyEventHandler.cs new file mode 100644 index 000000000000..0a69ce74c9d1 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/MaximumVaultTimeoutPolicyEventHandler.cs @@ -0,0 +1,10 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; + +public class MaximumVaultTimeoutPolicyEventHandler : IEnforceDependentPoliciesEvent +{ + public PolicyType Type => PolicyType.MaximumVaultTimeout; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandler.cs similarity index 81% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandler.cs index 6e92d53d4a38..60026e96ea53 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandler.cs @@ -1,5 +1,4 @@ - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; @@ -7,14 +6,14 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class OrganizationDataOwnershipPolicyValidator( +public class OrganizationDataOwnershipPolicyEventHandler( IPolicyRepository policyRepository, ICollectionRepository collectionRepository, IOrganizationRepository organizationRepository, IEnumerable> factories) - : OrganizationPolicyValidator(policyRepository, factories), IPostSavePolicySideEffect, IOnPolicyPostUpdateEvent + : OrganizationPolicyEventHandler(policyRepository, factories), IOnPolicyPostUpdateEvent { public PolicyType Type => PolicyType.OrganizationDataOwnership; @@ -22,14 +21,6 @@ public async Task ExecutePostUpsertSideEffectAsync( SavePolicyModel policyRequest, Policy postUpsertedPolicyState, Policy? previousPolicyState) - { - await ExecuteSideEffectsAsync(policyRequest, postUpsertedPolicyState, previousPolicyState); - } - - public async Task ExecuteSideEffectsAsync( - SavePolicyModel policyRequest, - Policy postUpdatedPolicy, - Policy? previousPolicyState) { if (policyRequest.Metadata is not OrganizationModelOwnershipPolicyModel metadata) { @@ -41,9 +32,9 @@ public async Task ExecuteSideEffectsAsync( return; } - var isFirstTimeEnabled = postUpdatedPolicy.Enabled && previousPolicyState == null; + var isFirstTimeEnabled = postUpsertedPolicyState.Enabled && previousPolicyState == null; var reEnabled = previousPolicyState?.Enabled == false - && postUpdatedPolicy.Enabled; + && postUpsertedPolicyState.Enabled; if (isFirstTimeEnabled || reEnabled) { diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandler.cs similarity index 89% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandler.cs index 15a0b4bb5497..360e440ed2e5 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandler.cs @@ -2,14 +2,14 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; /// /// Please do not use this validator. We're currently in the process of refactoring our policy validator pattern. /// This is a stop-gap solution for post-policy-save side effects, but it is not the long-term solution. /// -public abstract class OrganizationPolicyValidator(IPolicyRepository policyRepository, IEnumerable> factories) +public abstract class OrganizationPolicyEventHandler(IPolicyRepository policyRepository, IEnumerable> factories) { protected async Task> GetUserPolicyRequirementsByOrganizationIdAsync(Guid organizationId, PolicyType policyType) where T : IPolicyRequirement { diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandler.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandler.cs new file mode 100644 index 000000000000..b981639788cf --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandler.cs @@ -0,0 +1,10 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; + +public class OrganizationUserNotificationPolicyEventHandler : IEnforceDependentPoliciesEvent +{ + public PolicyType Type => PolicyType.OrganizationUserNotification; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/PolicyValidatorHelpers.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/PolicyEventHandlerHelpers.cs similarity index 95% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/PolicyValidatorHelpers.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/PolicyEventHandlerHelpers.cs index 1bbaf1aa1e84..a8219569ccd8 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/PolicyValidatorHelpers.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/PolicyEventHandlerHelpers.cs @@ -3,9 +3,9 @@ using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public static class PolicyValidatorHelpers +public static class PolicyEventHandlerHelpers { /// /// Validate that given Member Decryption Options are not enabled. diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandler.cs similarity index 65% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandler.cs index adc2a3865afa..b90f3faf7f35 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandler.cs @@ -1,19 +1,17 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class RequireSsoPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent +public class RequireSsoPolicyEventHandler : IPolicyValidationEvent, IEnforceDependentPoliciesEvent { private readonly ISsoConfigRepository _ssoConfigRepository; - public RequireSsoPolicyValidator(ISsoConfigRepository ssoConfigRepository) + public RequireSsoPolicyEventHandler(ISsoConfigRepository ssoConfigRepository) { _ssoConfigRepository = ssoConfigRepository; } @@ -23,11 +21,8 @@ public RequireSsoPolicyValidator(ISsoConfigRepository ssoConfigRepository) public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { if (policyUpdate is not { Enabled: true }) { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(policyUpdate.OrganizationId); @@ -39,6 +34,4 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre return ""; } - - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandler.cs similarity index 67% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandler.cs index 9033a38ad06b..1302953c8966 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandler.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; @@ -8,26 +6,23 @@ using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class ResetPasswordPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent +public class ResetPasswordPolicyEventHandler : IPolicyValidationEvent, IEnforceDependentPoliciesEvent { private readonly ISsoConfigRepository _ssoConfigRepository; public PolicyType Type => PolicyType.ResetPassword; public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public ResetPasswordPolicyValidator(ISsoConfigRepository ssoConfigRepository) + public ResetPasswordPolicyEventHandler(ISsoConfigRepository ssoConfigRepository) { _ssoConfigRepository = ssoConfigRepository; } public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { if (policyUpdate is not { Enabled: true } || policyUpdate.GetDataModel().AutoEnrollEnabled == false) { @@ -37,6 +32,4 @@ public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? curre return ""; } - - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs similarity index 99% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs index bf85929a9903..b2dab79c81ff 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEvent.cs @@ -5,7 +5,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; /// /// When the pm-31885-send-controls flag is active, syncs changes to the SendControls policy diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEvent.cs similarity index 99% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEvent.cs index f82aa540f407..00e6bc43c8a7 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEvent.cs @@ -6,7 +6,7 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.Utilities; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; /// /// Syncs changes to the SendOptions policy into the SendControls policy row. diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandler.cs similarity index 90% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandler.cs index d24c61e2582b..874fb4015778 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandler.cs @@ -14,9 +14,9 @@ using Bit.Core.Repositories; using Bit.Core.Services; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IOnPolicyPreUpdateEvent +public class SingleOrgPolicyEventHandler : IPolicyValidationEvent, IOnPolicyPreUpdateEvent { public PolicyType Type => PolicyType.SingleOrg; private const string OrganizationNotFoundErrorMessage = "Organization not found."; @@ -30,7 +30,7 @@ public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery; private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand; - public SingleOrgPolicyValidator( + public SingleOrgPolicyEventHandler( IOrganizationUserRepository organizationUserRepository, IMailService mailService, IOrganizationRepository organizationRepository, @@ -48,20 +48,34 @@ public SingleOrgPolicyValidator( _revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand; } - public IEnumerable RequiredPolicies => []; - public async Task ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy); + var policyUpdate = policyRequest.PolicyUpdate; + + if (policyUpdate is not { Enabled: true }) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(policyUpdate.OrganizationId); + + var validateDecryptionErrorMessage = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]); + + if (!string.IsNullOrWhiteSpace(validateDecryptionErrorMessage)) + { + return validateDecryptionErrorMessage; + } + + if (await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)) + { + return ClaimedDomainSingleOrganizationRequiredErrorMessage; + } + } + + return string.Empty; } public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) { var currentUser = _currentContext.UserId ?? Guid.Empty; @@ -112,25 +126,4 @@ await Task.WhenAll(usersToRevoke.Select(x => _mailService.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), x.Email))); } - public async Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { - if (policyUpdate is not { Enabled: true }) - { - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(policyUpdate.OrganizationId); - - var validateDecryptionErrorMessage = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]); - - if (!string.IsNullOrWhiteSpace(validateDecryptionErrorMessage)) - { - return validateDecryptionErrorMessage; - } - - if (await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)) - { - return ClaimedDomainSingleOrganizationRequiredErrorMessage; - } - } - - return string.Empty; - } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandler.cs similarity index 90% rename from src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs rename to src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandler.cs index 7f3ebcccfb4f..77a09fef87c0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandler.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; @@ -15,9 +13,9 @@ using Bit.Core.Repositories; using Bit.Core.Services; -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator, IOnPolicyPreUpdateEvent +public class TwoFactorAuthenticationPolicyEventHandler : IOnPolicyPreUpdateEvent { private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IMailService _mailService; @@ -29,9 +27,8 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator, IOnPolic public const string NonCompliantMembersWillLoseAccessMessage = "Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page."; public PolicyType Type => PolicyType.TwoFactorAuthentication; - public IEnumerable RequiredPolicies => []; - public TwoFactorAuthenticationPolicyValidator( + public TwoFactorAuthenticationPolicyEventHandler( IOrganizationUserRepository organizationUserRepository, IMailService mailService, IOrganizationRepository organizationRepository, @@ -49,11 +46,8 @@ public TwoFactorAuthenticationPolicyValidator( public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy) { - await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy); - } + var policyUpdate = policyRequest.PolicyUpdate; - public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) { var currentUser = _currentContext.UserId ?? Guid.Empty; @@ -121,5 +115,4 @@ private static bool MembersWithNoMasterPasswordWillLoseAccess( !x.HasMasterPassword && !organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == x.Id) .isTwoFactorEnabled); - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandler.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandler.cs new file mode 100644 index 000000000000..6995025473fa --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandler.cs @@ -0,0 +1,10 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; + +public class UriMatchDefaultPolicyEventHandler : IEnforceDependentPoliciesEvent +{ + public PolicyType Type => PolicyType.UriMatchDefaults; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 6ab275b702de..c9cbb09dbd00 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -1,9 +1,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services.Implementations; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +16,6 @@ public static void AddPolicyServices(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -24,50 +23,26 @@ public static void AddPolicyServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); - services.AddPolicyValidators(); services.AddPolicyRequirements(); - services.AddPolicySideEffects(); services.AddPolicyUpdateEvents(); - - } - - [Obsolete("Use AddPolicyUpdateEvents instead.")] - private static void AddPolicyValidators(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - } - - [Obsolete("Use AddPolicyUpdateEvents instead.")] - private static void AddPolicySideEffects(this IServiceCollection services) - { - services.AddScoped(); } private static void AddPolicyUpdateEvents(this IServiceCollection services) { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); } private static void AddPolicyRequirements(this IServiceCollection services) diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyEventHandlerFactory.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyEventHandlerFactory.cs index f44ae867dd40..f769ebf81adb 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyEventHandlerFactory.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyEventHandlerFactory.cs @@ -1,13 +1,11 @@ -#nullable enable - -using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Enums; using OneOf; using OneOf.Types; namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; /// -/// Provides policy-specific event handlers used during the save workflow in . +/// Provides policy-specific event handlers used during the save workflow in . /// /// /// Supported handlers: diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyUpdateEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyUpdateEvent.cs index a568658d4d5d..3097c857f0ad 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyUpdateEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyUpdateEvent.cs @@ -6,7 +6,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents /// Represents the policy to be upserted. /// /// -/// This is used for the VNextSavePolicyCommand. All policy handlers should implement this interface. +/// This is used for the SavePolicyCommand. All policy handlers should implement this interface. /// public interface IPolicyUpdateEvent { diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyValidationEvent.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyValidationEvent.cs index ee401ef813b1..8d48976149a0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyValidationEvent.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IPolicyValidationEvent.cs @@ -7,7 +7,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents /// Represents all validations that need to be run to enable or disable the given policy. /// /// -/// This is used for the VNextSavePolicyCommand. This optional but should be implemented for all policies that have +/// This is used for the SavePolicyCommand. This optional but should be implemented for all policies that have /// certain requirements for the given organization. /// public interface IPolicyValidationEvent : IPolicyUpdateEvent diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IVNextSavePolicyCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IVNextSavePolicyCommand.cs deleted file mode 100644 index 93414539bb7e..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyUpdateEvents/Interfaces/IVNextSavePolicyCommand.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Microsoft.Azure.NotificationHubs.Messaging; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; - -/// -/// Handles creating or updating organization policies with validation and side effect execution. -/// -/// -/// Workflow: -/// 1. Validates organization can use policies -/// 2. Validates required and dependent policies -/// 3. Runs policy-specific validation () -/// 4. Executes pre-save logic () -/// 5. Saves the policy -/// 6. Logs the event -/// 7. Executes post-save logic () -/// -public interface IVNextSavePolicyCommand -{ - /// - /// Performs the necessary validations, saves the policy and any side effects - /// - /// Policy data, acting user, and metadata. - /// The saved policy with updated revision and applied changes. - /// - /// Thrown if: - /// - The organization can’t use policies - /// - Dependent policies are missing or block changes - /// - Custom validation fails - /// - Task SaveAsync(SavePolicyModel policyRequest); -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs deleted file mode 100644 index 796ed286d864..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/MaximumVaultTimeoutPolicyValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; - -public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent -{ - public PolicyType Type => PolicyType.MaximumVaultTimeout; - public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0); -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidator.cs deleted file mode 100644 index ffc33609be3d..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; - -public class OrganizationUserNotificationPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent -{ - public PolicyType Type => PolicyType.OrganizationUserNotification; - public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(string.Empty); - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.CompletedTask; -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidator.cs deleted file mode 100644 index 5bffd944c92b..000000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; - -public class UriMatchDefaultPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent -{ - public PolicyType Type => PolicyType.UriMatchDefaults; - public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.CompletedTask; -} diff --git a/src/Core/Auth/Services/Implementations/SsoConfigService.cs b/src/Core/Auth/Services/Implementations/SsoConfigService.cs index 3c4f1ef85da7..62368c0c9064 100644 --- a/src/Core/Auth/Services/Implementations/SsoConfigService.cs +++ b/src/Core/Auth/Services/Implementations/SsoConfigService.cs @@ -7,7 +7,6 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; @@ -25,7 +24,7 @@ public class SsoConfigService : ISsoConfigService private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IEventService _eventService; - private readonly IVNextSavePolicyCommand _vNextSavePolicyCommand; + private readonly ISavePolicyCommand _savePolicyCommand; public SsoConfigService( ISsoConfigRepository ssoConfigRepository, @@ -33,14 +32,14 @@ public SsoConfigService( IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IEventService eventService, - IVNextSavePolicyCommand vNextSavePolicyCommand) + ISavePolicyCommand savePolicyCommand) { _ssoConfigRepository = ssoConfigRepository; _policyQuery = policyQuery; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _eventService = eventService; - _vNextSavePolicyCommand = vNextSavePolicyCommand; + _savePolicyCommand = savePolicyCommand; } public async Task SaveAsync(SsoConfig config, Organization organization) @@ -91,9 +90,9 @@ public async Task SaveAsync(SsoConfig config, Organization organization) }; var performedBy = new SystemUser(EventSystemUser.Unknown); - await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(singleOrgPolicy, performedBy)); - await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(resetPasswordPolicy, performedBy)); - await _vNextSavePolicyCommand.SaveAsync(new SavePolicyModel(requireSsoPolicy, performedBy)); + await _savePolicyCommand.SaveAsync(new SavePolicyModel(singleOrgPolicy, performedBy)); + await _savePolicyCommand.SaveAsync(new SavePolicyModel(resetPasswordPolicy, performedBy)); + await _savePolicyCommand.SaveAsync(new SavePolicyModel(requireSsoPolicy, performedBy)); } await LogEventsAsync(config, oldConfig); diff --git a/test/Api.Test/AdminConsole/Controllers/PoliciesControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/PoliciesControllerTests.cs index f38d3e8506cd..d263654aa736 100644 --- a/test/Api.Test/AdminConsole/Controllers/PoliciesControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/PoliciesControllerTests.cs @@ -8,7 +8,6 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Context; @@ -395,7 +394,7 @@ Organization organization [Theory] [BitAutoData] - public async Task Put_UsesVNextSavePolicyCommand( + public async Task Put_SavesPolicyWithCorrectArguments( SutProvider sutProvider, Guid orgId, SavePolicyRequest model, Policy policy, Guid userId) { @@ -410,7 +409,7 @@ public async Task Put_UsesVNextSavePolicyCommand( .OrganizationOwner(orgId) .Returns(true); - sutProvider.GetDependency() + sutProvider.GetDependency() .SaveAsync(Arg.Any()) .Returns(policy); @@ -418,7 +417,7 @@ public async Task Put_UsesVNextSavePolicyCommand( var result = await sutProvider.Sut.Put(orgId, policy.Type, model.Policy); // Assert - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.OrganizationId == orgId && m.PolicyUpdate.Type == policy.Type && @@ -433,7 +432,7 @@ await sutProvider.GetDependency() [Theory] [BitAutoData] - public async Task PutVNext_UsesVNextSavePolicyCommand( + public async Task PutVNext_SavesPolicyWithCorrectArguments( SutProvider sutProvider, Guid orgId, SavePolicyRequest model, Policy policy, Guid userId) { @@ -448,7 +447,7 @@ public async Task PutVNext_UsesVNextSavePolicyCommand( .OrganizationOwner(orgId) .Returns(true); - sutProvider.GetDependency() + sutProvider.GetDependency() .SaveAsync(Arg.Any()) .Returns(policy); @@ -456,7 +455,7 @@ public async Task PutVNext_UsesVNextSavePolicyCommand( var result = await sutProvider.Sut.PutVNext(orgId, policy.Type, model); // Assert - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.OrganizationId == orgId && m.PolicyUpdate.Type == policy.Type && diff --git a/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs b/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs index bd10eab6172a..15ea90ecf810 100644 --- a/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs +++ b/test/Api.Test/AdminConsole/Public/Controllers/PoliciesControllerTests.cs @@ -3,8 +3,8 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Context; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -19,7 +19,7 @@ public class PoliciesControllerTests { [Theory] [BitAutoData] - public async Task Put_UsesVNextSavePolicyCommand( + public async Task Put_SavesPolicyWithCorrectArguments( Guid organizationId, PolicyType policyType, PolicyUpdateRequestModel model, @@ -30,7 +30,7 @@ public async Task Put_UsesVNextSavePolicyCommand( policy.Data = null; sutProvider.GetDependency() .OrganizationId.Returns(organizationId); - sutProvider.GetDependency() + sutProvider.GetDependency() .SaveAsync(Arg.Any()) .Returns(policy); @@ -38,7 +38,7 @@ public async Task Put_UsesVNextSavePolicyCommand( await sutProvider.Sut.Put(policyType, model); // Assert - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.OrganizationId == organizationId && diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs index 730489a9fcc9..63f32fbd6274 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs @@ -2,8 +2,8 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -182,7 +182,7 @@ public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsVerified_ThenSin _ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(x => x.PolicyUpdate.Type == PolicyType.SingleOrg && x.PolicyUpdate.OrganizationId == domain.OrganizationId && @@ -192,7 +192,7 @@ x.PerformedBy is StandardUser && } [Theory, BitAutoData] - public async Task UserVerifyOrganizationDomainAsync_UsesVNextSavePolicyCommand( + public async Task UserVerifyOrganizationDomainAsync_SavesPolicy( OrganizationDomain domain, Guid userId, SutProvider sutProvider) { sutProvider.GetDependency() @@ -208,7 +208,7 @@ public async Task UserVerifyOrganizationDomainAsync_UsesVNextSavePolicyCommand( _ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.Type == PolicyType.SingleOrg && @@ -235,7 +235,7 @@ public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsNotVerified_Then _ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .DidNotReceive() .SaveAsync(Arg.Any()); } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorHelpersTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlerHelpersTests.cs similarity index 97% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorHelpersTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlerHelpersTests.cs index 99f99706fa86..c0c88852b901 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorHelpersTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlerHelpersTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; @@ -6,7 +6,7 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies; -public class PolicyValidatorHelpersTests +public class PolicyEventHandlerHelpersTests { [Fact] public void ValidateDecryptionOptionsNotEnabled_RequiredByKeyConnector_ValidationError() diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandlerTests.cs similarity index 94% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandlerTests.cs index b8b1c4915002..4dfa3b7bd745 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/AutomaticUserConfirmationPolicyEventHandlerTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/AutomaticUserConfirmationPolicyEventHandlerTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Auth.UserFeatures.EmergencyAccess.Interfaces; using Bit.Core.Entities; using Bit.Core.Repositories; @@ -13,7 +13,7 @@ using Xunit; using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] public class AutomaticUserConfirmationPolicyEventHandlerTests @@ -42,7 +42,7 @@ public async Task ValidateAsync_EnablingPolicy_UsersNotCompliantWithSingleOrg_Re .Returns(Invalid(request, new UserNotCompliantWithSingleOrganization())); // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.Contains("compliant with the Single organization policy", result, StringComparison.OrdinalIgnoreCase); @@ -61,7 +61,7 @@ public async Task ValidateAsync_EnablingPolicy_ProviderUsersExist_ReturnsError( .Returns(Invalid(request, new ProviderExistsInOrganization())); // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.Contains("Provider user type", result, StringComparison.OrdinalIgnoreCase); @@ -80,7 +80,7 @@ public async Task ValidateAsync_EnablingPolicy_AllValidationsPassed_ReturnsEmpty .Returns(Valid(request)); // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.True(string.IsNullOrEmpty(result)); @@ -96,7 +96,7 @@ public async Task ValidateAsync_PolicyAlreadyEnabled_ReturnsEmptyString( currentPolicy.OrganizationId = policyUpdate.OrganizationId; // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, currentPolicy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), currentPolicy); // Assert Assert.True(string.IsNullOrEmpty(result)); @@ -116,7 +116,7 @@ public async Task ValidateAsync_DisablingPolicy_ReturnsEmptyString( currentPolicy.OrganizationId = policyUpdate.OrganizationId; // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, currentPolicy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), currentPolicy); // Assert Assert.True(string.IsNullOrEmpty(result)); @@ -138,7 +138,7 @@ public async Task ValidateAsync_EnablingPolicy_PassesCorrectOrganizationId( .Returns(Valid(request)); // Act - await sutProvider.Sut.ValidateAsync(policyUpdate, null); + await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert await sutProvider.GetDependency() diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandlerTests.cs similarity index 78% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandlerTests.cs index 2b277c6ae6c8..a6886491e53e 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/BlockClaimedDomainAccountCreationPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/BlockClaimedDomainAccountCreationPolicyEventHandlerTests.cs @@ -1,9 +1,9 @@ -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -11,12 +11,12 @@ using Xunit; [SutProviderCustomize] -public class BlockClaimedDomainAccountCreationPolicyValidatorTests +public class BlockClaimedDomainAccountCreationPolicyEventHandlerTests { [Theory, BitAutoData] public async Task ValidateAsync_EnablingPolicy_NoVerifiedDomains_ValidationError( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange sutProvider.GetDependency() @@ -24,7 +24,7 @@ public async Task ValidateAsync_EnablingPolicy_NoVerifiedDomains_ValidationError .Returns(false); // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.Equal("You must claim at least one domain to turn on this policy", result); @@ -33,7 +33,7 @@ public async Task ValidateAsync_EnablingPolicy_NoVerifiedDomains_ValidationError [Theory, BitAutoData] public async Task ValidateAsync_EnablingPolicy_HasVerifiedDomains_Success( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange sutProvider.GetDependency() @@ -41,7 +41,7 @@ public async Task ValidateAsync_EnablingPolicy_HasVerifiedDomains_Success( .Returns(true); // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.True(string.IsNullOrEmpty(result)); @@ -50,10 +50,10 @@ public async Task ValidateAsync_EnablingPolicy_HasVerifiedDomains_Success( [Theory, BitAutoData] public async Task ValidateAsync_DisablingPolicy_NoValidation( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Act - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), null); // Assert Assert.True(string.IsNullOrEmpty(result)); @@ -65,7 +65,7 @@ await sutProvider.GetDependency() [Theory, BitAutoData] public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_NoVerifiedDomains_ValidationError( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange sutProvider.GetDependency() @@ -84,7 +84,7 @@ public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_NoVerifiedDom [Theory, BitAutoData] public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_HasVerifiedDomains_Success( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange sutProvider.GetDependency() @@ -103,7 +103,7 @@ public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_HasVerifiedDo [Theory, BitAutoData] public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_NoValidation( [PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel()); @@ -122,22 +122,10 @@ await sutProvider.GetDependency() public void Type_ReturnsBlockClaimedDomainAccountCreation() { // Arrange - var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null); + var validator = new BlockClaimedDomainAccountCreationPolicyEventHandler(null); // Act & Assert Assert.Equal(PolicyType.BlockClaimedDomainAccountCreation, validator.Type); } - [Fact] - public void RequiredPolicies_ReturnsEmpty() - { - // Arrange - var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null); - - // Act - var requiredPolicies = validator.RequiredPolicies.ToList(); - - // Assert - Assert.Empty(requiredPolicies); - } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEventTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEventTests.cs index aea974a60d8f..87ab5b02eddd 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/DisableSendSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/DisableSendSyncPolicyEventTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Test.Common.AutoFixture; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] public class DisableSendSyncPolicyEventTests diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandlerTests.cs similarity index 89% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandlerTests.cs index 525169a1fbb4..ffe8d5e62e7c 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/FreeFamiliesForEnterprisePolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/FreeFamiliesForEnterprisePolicyEventHandlerTests.cs @@ -1,7 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Entities; using Bit.Core.Repositories; using Bit.Core.Services; @@ -11,10 +11,10 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class FreeFamiliesForEnterprisePolicyValidatorTests +public class FreeFamiliesForEnterprisePolicyEventHandlerTests { [Theory, BitAutoData] public async Task OnSaveSideEffectsAsync_DoesNotNotifyUserWhenPolicyDisabled( @@ -22,7 +22,7 @@ public async Task OnSaveSideEffectsAsync_DoesNotNotifyUserWhenPolicyDisabled( List organizationSponsorships, [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.Enabled = true; @@ -36,7 +36,7 @@ public async Task OnSaveSideEffectsAsync_DoesNotNotifyUserWhenPolicyDisabled( .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) .Returns(organizationSponsorships); - await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy); + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(new SavePolicyModel(policyUpdate), policy); await sutProvider.GetDependency().DidNotReceive() .SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(organizationSponsorships[0].FriendlyName, organizationSponsorships[0].ValidUntil.ToString(), @@ -49,7 +49,7 @@ public async Task OnSaveSideEffectsAsync_DoesNotifyUserWhenPolicyDisabled( List organizationSponsorships, [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.Enabled = false; @@ -63,7 +63,7 @@ public async Task OnSaveSideEffectsAsync_DoesNotifyUserWhenPolicyDisabled( .GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId) .Returns(organizationSponsorships); // Act - await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy); + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(new SavePolicyModel(policyUpdate), policy); // Assert var offerAcceptanceDate = organizationSponsorships[0].ValidUntil!.Value.AddDays(-7).ToString("MM/dd/yyyy"); @@ -79,7 +79,7 @@ public async Task ExecutePreUpsertSideEffectAsync_DoesNotNotifyUserWhenPolicyDis List organizationSponsorships, [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.Enabled = true; policyUpdate.Enabled = false; @@ -107,7 +107,7 @@ public async Task ExecutePreUpsertSideEffectAsync_DoesNotifyUserWhenPolicyEnable List organizationSponsorships, [PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate, [Policy(PolicyType.FreeFamiliesSponsorshipPolicy, false)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.Enabled = false; policyUpdate.Enabled = true; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandlerTests.cs similarity index 92% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandlerTests.cs index 95c0e2054293..c7cc23518e65 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationDataOwnershipPolicyEventHandlerTests.cs @@ -2,8 +2,8 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; @@ -12,10 +12,10 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class OrganizationDataOwnershipPolicyValidatorTests +public class OrganizationDataOwnershipPolicyEventHandlerTests { private const string _defaultUserCollectionName = "Default"; @@ -25,7 +25,7 @@ public async Task ExecuteSideEffectsAsync_PolicyAlreadyEnabled_DoesNothing( [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership, true)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -39,7 +39,7 @@ public async Task ExecuteSideEffectsAsync_PolicyAlreadyEnabled_DoesNothing( var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act - await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert await sutProvider.GetDependency() @@ -53,7 +53,7 @@ public async Task ExecuteSideEffectsAsync_PolicyBeingDisabled_DoesNothing( [Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange previousPolicyState.OrganizationId = policyUpdate.OrganizationId; @@ -67,7 +67,7 @@ public async Task ExecuteSideEffectsAsync_PolicyBeingDisabled_DoesNothing( var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act - await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert await sutProvider.GetDependency() @@ -93,7 +93,7 @@ public async Task ExecuteSideEffectsAsync_WhenNoUsersExist_DoNothing( var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act - await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert await collectionRepository @@ -182,7 +182,7 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act - await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert - Should call with all user IDs (repository does internal filtering) await collectionRepository @@ -208,7 +208,7 @@ public async Task ExecuteSideEffectsAsync_WhenDefaultCollectionNameIsInvalid_Doe [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -223,7 +223,7 @@ public async Task ExecuteSideEffectsAsync_WhenDefaultCollectionNameIsInvalid_Doe var policyRequest = new SavePolicyModel(policyUpdate, metadata); // Act - await sutProvider.Sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert await sutProvider.GetDependency() @@ -241,7 +241,7 @@ private static IPolicyRepository ArrangePolicyRepository(IEnumerable(), UseMyItems = useMyItems }); - var sut = new OrganizationDataOwnershipPolicyValidator(policyRepository, collectionRepository, organizationRepository, [factory]); + var sut = new OrganizationDataOwnershipPolicyEventHandler(policyRepository, collectionRepository, organizationRepository, [factory]); return sut; } @@ -265,7 +265,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNoth [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership, true)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -293,7 +293,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothi [Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange previousPolicyState.OrganizationId = policyUpdate.OrganizationId; @@ -392,7 +392,7 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsIn [Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy, [Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId; @@ -438,12 +438,12 @@ public async Task ExecuteSideEffectsAsync_OrganizationNotFound_ThrowsInvalidOper // Return null to simulate organization not found organizationRepository.GetByIdAsync(Arg.Any()).Returns((Organization?)null); - var sut = new OrganizationDataOwnershipPolicyValidator(policyRepository, collectionRepository, organizationRepository, [factory]); + var sut = new OrganizationDataOwnershipPolicyEventHandler(policyRepository, collectionRepository, organizationRepository, [factory]); var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act & Assert await Assert.ThrowsAsync(() => - sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState)); + sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState)); } [Theory] @@ -469,7 +469,7 @@ public async Task ExecuteSideEffectsAsync_UseMyItemsDisabled_DoesNotCreateCollec var policyRequest = new SavePolicyModel(policyUpdate, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName)); // Act - await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState); + await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState); // Assert - Should NOT create collections when UseMyItems is disabled await collectionRepository diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandlerTests.cs similarity index 87% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandlerTests.cs index bda927f18495..b9969ebb3f58 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationPolicyEventHandlerTests.cs @@ -1,7 +1,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; using Bit.Test.Common.AutoFixture; @@ -9,18 +9,18 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class OrganizationPolicyValidatorTests +public class OrganizationPolicyEventHandlerTests { [Theory, BitAutoData] public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithNoFactory_ThrowsNotImplementedException( Guid organizationId, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange - var sut = new TestOrganizationPolicyValidator(sutProvider.GetDependency(), []); + var sut = new TestOrganizationPolicyEventHandler(sutProvider.GetDependency(), []); // Act & Assert var exception = await Assert.ThrowsAsync(() => @@ -35,7 +35,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithMultipleUse Guid organizationId, Guid userId1, Guid userId2, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange var policyDetails = new List @@ -54,7 +54,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithMultipleUse .Returns(policyDetails); var factories = new List> { factory }; - var sut = new TestOrganizationPolicyValidator(sutProvider.GetDependency(), factories); + var sut = new TestOrganizationPolicyEventHandler(sutProvider.GetDependency(), factories); // Act var result = await sut.TestGetUserPolicyRequirementsByOrganizationIdAsync( @@ -74,7 +74,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithMultipleUse public async Task GetUserPolicyRequirementsByOrganizationIdAsync_ShouldEnforceFilters( Guid organizationId, Guid userId, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange var adminUser = new OrganizationPolicyDetails() @@ -108,7 +108,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_ShouldEnforceFi .Returns(false); var factories = new List> { factory }; - var sut = new TestOrganizationPolicyValidator(sutProvider.GetDependency(), factories); + var sut = new TestOrganizationPolicyEventHandler(sutProvider.GetDependency(), factories); // Act var result = await sut.TestGetUserPolicyRequirementsByOrganizationIdAsync( @@ -128,7 +128,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_ShouldEnforceFi [Theory, BitAutoData] public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithEmptyPolicyDetails_ReturnsEmptyCollection( Guid organizationId, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange var factory = Substitute.For>(); @@ -138,7 +138,7 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithEmptyPolicy .Returns(new List()); var factories = new List> { factory }; - var sut = new TestOrganizationPolicyValidator(sutProvider.GetDependency(), factories); + var sut = new TestOrganizationPolicyEventHandler(sutProvider.GetDependency(), factories); // Act var result = await sut.TestGetUserPolicyRequirementsByOrganizationIdAsync( @@ -150,9 +150,9 @@ public async Task GetUserPolicyRequirementsByOrganizationIdAsync_WithEmptyPolicy } } -public class TestOrganizationPolicyValidator : OrganizationPolicyValidator +public class TestOrganizationPolicyEventHandler : OrganizationPolicyEventHandler { - public TestOrganizationPolicyValidator( + public TestOrganizationPolicyEventHandler( IPolicyRepository policyRepository, IEnumerable>? factories = null) : base(policyRepository, factories ?? []) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandlerTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandlerTests.cs new file mode 100644 index 000000000000..108643ccc720 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/OrganizationUserNotificationPolicyEventHandlerTests.cs @@ -0,0 +1,24 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; + +public class OrganizationUserNotificationPolicyEventHandlerTests +{ + [Fact] + public void Type_ReturnsOrganizationUserNotificationPolicy() + { + var validator = new OrganizationUserNotificationPolicyEventHandler(); + + Assert.Equal(PolicyType.OrganizationUserNotification, validator.Type); + } + + [Fact] + public void RequiredPolicies_ReturnsSingleOrg() + { + var validator = new OrganizationUserNotificationPolicyEventHandler(); + + Assert.Equal([PolicyType.SingleOrg], validator.RequiredPolicies); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandlerTests.cs similarity index 87% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandlerTests.cs index 6fc6b8566828..97b8dbfd587b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/RequireSsoPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/RequireSsoPolicyEventHandlerTests.cs @@ -1,9 +1,9 @@ -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; @@ -15,13 +15,13 @@ using Xunit; [SutProviderCustomize] -public class RequireSsoPolicyValidatorTests +public class RequireSsoPolicyEventHandlerTests { [Theory, BitAutoData] public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationError( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -32,7 +32,7 @@ public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationEr .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); } @@ -40,7 +40,7 @@ public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationEr public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -51,7 +51,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase); } @@ -59,7 +59,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( public async Task ValidateAsync_DisablingPolicy_DecryptionOptionsNotEnabled_Success( [PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -69,7 +69,7 @@ public async Task ValidateAsync_DisablingPolicy_DecryptionOptionsNotEnabled_Succ .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.True(string.IsNullOrEmpty(result)); } @@ -77,7 +77,7 @@ public async Task ValidateAsync_DisablingPolicy_DecryptionOptionsNotEnabled_Succ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError( [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.RequireSso)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -98,7 +98,7 @@ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnector public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_ValidationError( [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.RequireSso)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -119,7 +119,7 @@ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_V public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_DecryptionOptionsNotEnabled_Success( [PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.RequireSso)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandlerTests.cs similarity index 89% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandlerTests.cs index b3d328c5abb5..5585246dd7c1 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/ResetPasswordPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/ResetPasswordPolicyEventHandlerTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; @@ -13,10 +13,10 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class ResetPasswordPolicyValidatorTests +public class ResetPasswordPolicyEventHandlerTests { [Theory] [BitAutoData(true, false)] @@ -27,7 +27,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( bool autoEnrollEnabled, [PolicyUpdate(PolicyType.ResetPassword)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policyUpdate.Enabled = policyEnabled; policyUpdate.SetDataModel(new ResetPasswordDataModel @@ -43,7 +43,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase); } @@ -51,7 +51,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError( public async Task ValidateAsync_DisablingPolicy_TdeNotEnabled_Success( [PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policyUpdate.SetDataModel(new ResetPasswordDataModel { @@ -65,7 +65,7 @@ public async Task ValidateAsync_DisablingPolicy_TdeNotEnabled_Success( .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.True(string.IsNullOrEmpty(result)); } @@ -78,7 +78,7 @@ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_V bool autoEnrollEnabled, [PolicyUpdate(PolicyType.ResetPassword)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policyUpdate.Enabled = policyEnabled; policyUpdate.SetDataModel(new ResetPasswordDataModel @@ -104,7 +104,7 @@ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_V public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeNotEnabled_Success( [PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policyUpdate.SetDataModel(new ResetPasswordDataModel { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs index b10a2f28c1d4..852090204035 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendControlsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendControlsSyncPolicyEventTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Test.Common.AutoFixture; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] public class SendControlsSyncPolicyEventTests diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEventTests.cs similarity index 99% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEventTests.cs index 7b71a2b09097..8fdf1331e953 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SendOptionsSyncPolicyEventTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SendOptionsSyncPolicyEventTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Test.AdminConsole.AutoFixture; using Bit.Test.Common.AutoFixture; @@ -10,7 +10,7 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] public class SendOptionsSyncPolicyEventTests diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandlerTests.cs similarity index 93% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandlerTests.cs index 7c58d4663674..320c03d38f6f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/SingleOrgPolicyEventHandlerTests.cs @@ -4,7 +4,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Utilities.Commands; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; @@ -22,16 +22,16 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class SingleOrgPolicyValidatorTests +public class SingleOrgPolicyEventHandlerTests { [Theory, BitAutoData] public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationError( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -42,7 +42,7 @@ public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationEr .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase); } @@ -50,7 +50,7 @@ public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationEr public async Task ValidateAsync_DisablingPolicy_KeyConnectorNotEnabled_Success( [PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.ResetPassword)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -60,7 +60,7 @@ public async Task ValidateAsync_DisablingPolicy_KeyConnectorNotEnabled_Success( .GetByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns(ssoConfig); - var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy); + var result = await sutProvider.Sut.ValidateAsync(new SavePolicyModel(policyUpdate), policy); Assert.True(string.IsNullOrEmpty(result)); } @@ -70,7 +70,7 @@ public async Task OnSaveSideEffectsAsync_RevokesNonCompliantUsers( [Policy(PolicyType.SingleOrg, false)] Policy policy, Guid savingUserId, Guid nonCompliantUserId, - Organization organization, SutProvider sutProvider) + Organization organization, SutProvider sutProvider) { policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; @@ -127,7 +127,7 @@ public async Task OnSaveSideEffectsAsync_RevokesNonCompliantUsers( .RevokeNonCompliantOrganizationUsersAsync(Arg.Any()) .Returns(new CommandResult()); - await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy); + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(new SavePolicyModel(policyUpdate), policy); await sutProvider.GetDependency() .Received(1) @@ -151,7 +151,7 @@ await sutProvider.GetDependency() public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -172,7 +172,7 @@ public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnector public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorNotEnabled_Success( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = policyUpdate.OrganizationId; @@ -199,7 +199,7 @@ public async Task ExecutePreUpsertSideEffectAsync_RevokesNonCompliantUsers( Guid savingUserId, Guid nonCompliantUserId, Organization organization, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandlerTests.cs similarity index 92% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandlerTests.cs index 7d5aaf8d2142..f2009946fb38 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/TwoFactorAuthenticationPolicyEventHandlerTests.cs @@ -3,7 +3,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Bit.Core.AdminConsole.Utilities.Commands; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Enums; @@ -17,17 +17,17 @@ using NSubstitute; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; [SutProviderCustomize] -public class TwoFactorAuthenticationPolicyValidatorTests +public class TwoFactorAuthenticationPolicyEventHandlerTests { [Theory, BitAutoData] public async Task OnSaveSideEffectsAsync_GivenNonCompliantUsersWithoutMasterPassword_Throws( Organization organization, [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); @@ -54,9 +54,9 @@ public async Task OnSaveSideEffectsAsync_GivenNonCompliantUsersWithoutMasterPass (orgUserDetailUserWithout2Fa, false), }); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy)); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ExecutePreUpsertSideEffectAsync(new SavePolicyModel(policyUpdate), policy)); - Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message); + Assert.Equal(TwoFactorAuthenticationPolicyEventHandler.NonCompliantMembersWillLoseAccessMessage, exception.Message); } [Theory, BitAutoData] @@ -64,7 +64,7 @@ public async Task OnSaveSideEffectsAsync_RevokesOnlyNonCompliantUsers( Organization organization, [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange policy.OrganizationId = policyUpdate.OrganizationId; @@ -111,7 +111,7 @@ public async Task OnSaveSideEffectsAsync_RevokesOnlyNonCompliantUsers( .Returns(new CommandResult()); // Act - await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy); + await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(new SavePolicyModel(policyUpdate), policy); // Assert await sutProvider.GetDependency() @@ -142,7 +142,7 @@ public async Task ExecutePreUpsertSideEffectAsync_GivenNonCompliantUsersWithoutM Organization organization, [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { policy.OrganizationId = organization.Id = policyUpdate.OrganizationId; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); @@ -174,7 +174,7 @@ public async Task ExecutePreUpsertSideEffectAsync_GivenNonCompliantUsersWithoutM var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy)); - Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message); + Assert.Equal(TwoFactorAuthenticationPolicyEventHandler.NonCompliantMembersWillLoseAccessMessage, exception.Message); } [Theory, BitAutoData] @@ -182,7 +182,7 @@ public async Task ExecutePreUpsertSideEffectAsync_RevokesOnlyNonCompliantUsers( Organization organization, [PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate, [Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy, - SutProvider sutProvider) + SutProvider sutProvider) { // Arrange policy.OrganizationId = policyUpdate.OrganizationId; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandlerTests.cs similarity index 83% rename from test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidatorTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandlerTests.cs index 7059305ac851..53abe10e91f9 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/UriMatchDefaultPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyEventHandlers/UriMatchDefaultPolicyEventHandlerTests.cs @@ -1,12 +1,12 @@ using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; using Xunit; -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyEventHandlers; -public class UriMatchDefaultPolicyValidatorTests +public class UriMatchDefaultPolicyEventHandlerTests { - private readonly UriMatchDefaultPolicyValidator _validator = new(); + private readonly UriMatchDefaultPolicyEventHandler _validator = new(); [Fact] // Test that the Type property returns the correct PolicyType for this validator diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorFixtures.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorFixtures.cs deleted file mode 100644 index ba4741d8bdf9..000000000000 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidatorFixtures.cs +++ /dev/null @@ -1,43 +0,0 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using NSubstitute; - -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies; - -public class FakeSingleOrgPolicyValidator : IPolicyValidator -{ - public PolicyType Type => PolicyType.SingleOrg; - public IEnumerable RequiredPolicies => Array.Empty(); - - public readonly Func> ValidateAsyncMock = Substitute.For>>(); - public readonly Action OnSaveSideEffectsAsyncMock = Substitute.For>(); - - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { - return ValidateAsyncMock(policyUpdate, currentPolicy); - } - - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) - { - OnSaveSideEffectsAsyncMock(policyUpdate, currentPolicy); - return Task.FromResult(0); - } -} -public class FakeRequireSsoPolicyValidator : IPolicyValidator -{ - public PolicyType Type => PolicyType.RequireSso; - public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0); -} -public class FakeVaultTimeoutPolicyValidator : IPolicyValidator -{ - public PolicyType Type => PolicyType.MaximumVaultTimeout; - public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; - public Task ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(""); - public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0); -} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidatorTests.cs deleted file mode 100644 index 8c03b3c7e3e3..000000000000 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationUserNotificationPolicyValidatorTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; -using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using Xunit; - -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; - -public class OrganizationUserNotificationPolicyValidatorTests -{ - [Fact] - public void Type_ReturnsOrganizationUserNotificationPolicy() - { - var validator = new OrganizationUserNotificationPolicyValidator(); - - Assert.Equal(PolicyType.OrganizationUserNotification, validator.Type); - } - - [Fact] - public void RequiredPolicies_ReturnsSingleOrg() - { - var validator = new OrganizationUserNotificationPolicyValidator(); - - Assert.Equal([PolicyType.SingleOrg], validator.RequiredPolicies); - } - - [Theory, BitAutoData] - public async Task ValidateAsync_EnablingPolicy_ReturnsNoError( - [PolicyUpdate(PolicyType.OrganizationUserNotification, true)] PolicyUpdate policyUpdate) - { - var validator = new OrganizationUserNotificationPolicyValidator(); - - var result = await validator.ValidateAsync(policyUpdate, null); - - Assert.True(string.IsNullOrEmpty(result)); - } - - [Theory, BitAutoData] - public async Task ValidateAsync_DisablingPolicy_ReturnsNoError( - [PolicyUpdate(PolicyType.OrganizationUserNotification, false)] PolicyUpdate policyUpdate) - { - var validator = new OrganizationUserNotificationPolicyValidator(); - - var result = await validator.ValidateAsync(policyUpdate, null); - - Assert.True(string.IsNullOrEmpty(result)); - } -} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs index 22c18c4daed3..3603b05a564c 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs @@ -2,14 +2,11 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models; -using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AdminConsole.AutoFixture; @@ -17,6 +14,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Time.Testing; using NSubstitute; +using OneOf.Types; using Xunit; using EventType = Bit.Core.Enums.EventType; @@ -27,24 +25,43 @@ public class SavePolicyCommandTests [Theory, BitAutoData] public async Task SaveAsync_NewPolicy_Success([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) { - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns(""); - var sutProvider = SutProviderFactory([fakePolicyValidator]); + // Arrange + var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); + fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); + + var newPolicy = new Policy + { + Type = policyUpdate.Type, + OrganizationId = policyUpdate.OrganizationId, + Enabled = false + }; ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]); + sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([newPolicy]); var creationDate = sutProvider.GetDependency().Start; - await sutProvider.Sut.SaveAsync(policyUpdate); + // Act + await sutProvider.Sut.SaveAsync(savePolicyModel); - await fakePolicyValidator.ValidateAsyncMock.Received(1).Invoke(policyUpdate, null); - fakePolicyValidator.OnSaveSideEffectsAsyncMock.Received(1).Invoke(policyUpdate, null); + // Assert + await fakePolicyValidationEvent.ValidateAsyncMock + .Received(1) + .Invoke(Arg.Any(), Arg.Any()); await AssertPolicySavedAsync(sutProvider, policyUpdate); - await sutProvider.GetDependency().Received(1).UpsertAsync(Arg.Is(p => - p.CreationDate == creationDate && - p.RevisionDate == creationDate)); + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.CreationDate == creationDate && + p.RevisionDate == creationDate)); } [Theory, BitAutoData] @@ -52,9 +69,15 @@ public async Task SaveAsync_ExistingPolicy_Success( [PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg, false)] Policy currentPolicy) { - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns(""); - var sutProvider = SutProviderFactory([fakePolicyValidator]); + // Arrange + var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); + fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); currentPolicy.OrganizationId = policyUpdate.OrganizationId; sutProvider.GetDependency() @@ -66,54 +89,45 @@ public async Task SaveAsync_ExistingPolicy_Success( .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns([currentPolicy]); - // Store mutable properties separately to assert later - var id = currentPolicy.Id; - var organizationId = currentPolicy.OrganizationId; - var type = currentPolicy.Type; - var creationDate = currentPolicy.CreationDate; - var revisionDate = sutProvider.GetDependency().Start; - - await sutProvider.Sut.SaveAsync(policyUpdate); + // Act + await sutProvider.Sut.SaveAsync(savePolicyModel); - await fakePolicyValidator.ValidateAsyncMock.Received(1).Invoke(policyUpdate, currentPolicy); - fakePolicyValidator.OnSaveSideEffectsAsyncMock.Received(1).Invoke(policyUpdate, currentPolicy); + // Assert + await fakePolicyValidationEvent.ValidateAsyncMock + .Received(1) + .Invoke(Arg.Any(), currentPolicy); await AssertPolicySavedAsync(sutProvider, policyUpdate); - // Additional assertions to ensure certain properties have or have not been updated - await sutProvider.GetDependency().Received(1).UpsertAsync(Arg.Is(p => - p.Id == id && - p.OrganizationId == organizationId && - p.Type == type && - p.CreationDate == creationDate && - p.RevisionDate == revisionDate)); - } - [Fact] - public void Constructor_DuplicatePolicyValidators_Throws() - { - var exception = Assert.Throws(() => - new SavePolicyCommand( - Substitute.For(), - Substitute.For(), - Substitute.For(), - [new FakeSingleOrgPolicyValidator(), new FakeSingleOrgPolicyValidator()], - Substitute.For(), - Substitute.For(), - Substitute.For())); - Assert.Contains("Duplicate PolicyValidator for SingleOrg policy", exception.Message); + + var revisionDate = sutProvider.GetDependency().Start; + + await sutProvider.GetDependency() + .Received(1) + .UpsertAsync(Arg.Is(p => + p.Id == currentPolicy.Id && + p.OrganizationId == currentPolicy.OrganizationId && + p.Type == currentPolicy.Type && + p.CreationDate == currentPolicy.CreationDate && + p.RevisionDate == revisionDate)); } [Theory, BitAutoData] public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate) { + // Arrange var sutProvider = SutProviderFactory(); + var savePolicyModel = new SavePolicyModel(policyUpdate); + sutProvider.GetDependency() .GetByIdAsync(policyUpdate.OrganizationId) .Returns(Task.FromResult(null)); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -121,7 +135,10 @@ public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpd [Theory, BitAutoData] public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate) { + // Arrange var sutProvider = SutProviderFactory(); + var savePolicyModel = new SavePolicyModel(policyUpdate); + sutProvider.GetDependency() .GetByIdAsync(policyUpdate.OrganizationId) .Returns(new Organization @@ -130,9 +147,11 @@ public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([Poli UsePolicies = false }); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("cannot use policies", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -141,19 +160,32 @@ public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([Poli public async Task SaveAsync_RequiredPolicyIsNull_Throws( [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate) { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator() - ]); + // Arrange + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent() + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); + + var requireSsoPolicy = new Policy + { + Type = PolicyType.RequireSso, + OrganizationId = policyUpdate.OrganizationId, + Enabled = false + }; ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([]); + .Returns([requireSsoPolicy]); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -163,19 +195,32 @@ public async Task SaveAsync_RequiredPolicyNotEnabled_Throws( [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg, false)] Policy singleOrgPolicy) { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator() - ]); + // Arrange + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent() + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); + + var requireSsoPolicy = new Policy + { + Type = PolicyType.RequireSso, + OrganizationId = policyUpdate.OrganizationId, + Enabled = false + }; ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([singleOrgPolicy]); + .Returns([singleOrgPolicy, requireSsoPolicy]); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -185,17 +230,31 @@ public async Task SaveAsync_RequiredPolicyEnabled_Success( [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy) { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator() - ]); + // Arrange + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent() + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); + + var requireSsoPolicy = new Policy + { + Type = PolicyType.RequireSso, + OrganizationId = policyUpdate.OrganizationId, + Enabled = false + }; ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([singleOrgPolicy]); + .Returns([singleOrgPolicy, requireSsoPolicy]); - await sutProvider.Sut.SaveAsync(policyUpdate); + // Act + await sutProvider.Sut.SaveAsync(savePolicyModel); + + // Assert await AssertPolicySavedAsync(sutProvider, policyUpdate); } @@ -203,21 +262,27 @@ public async Task SaveAsync_RequiredPolicyEnabled_Success( public async Task SaveAsync_DependentPolicyIsEnabled_Throws( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy) // depends on Single Org + [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy) { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator() - ]); + // Arrange + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent() + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns([currentPolicy, requireSsoPolicy]); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("Turn off the Require single sign-on authentication policy because it requires the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -226,23 +291,29 @@ public async Task SaveAsync_DependentPolicyIsEnabled_Throws( public async Task SaveAsync_MultipleDependentPoliciesAreEnabled_Throws( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy, // depends on Single Org - [Policy(PolicyType.MaximumVaultTimeout)] Policy vaultTimeoutPolicy) // depends on Single Org + [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy, + [Policy(PolicyType.MaximumVaultTimeout)] Policy vaultTimeoutPolicy) { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator(), - new FakeVaultTimeoutPolicyValidator() - ]); + // Arrange + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent(), + new FakeVaultTimeoutDependencyEvent() + ]); + + var savePolicyModel = new SavePolicyModel(policyUpdate); ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) .Returns([currentPolicy, requireSsoPolicy, vaultTimeoutPolicy]); + // Act var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); + () => sutProvider.Sut.SaveAsync(savePolicyModel)); + // Assert Assert.Contains("Turn off all of the policies that require the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await AssertPolicyNotSavedAsync(sutProvider); } @@ -251,225 +322,97 @@ public async Task SaveAsync_MultipleDependentPoliciesAreEnabled_Throws( public async Task SaveAsync_DependentPolicyNotEnabled_Success( [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso, false)] Policy requireSsoPolicy) // depends on Single Org but is not enabled - { - var sutProvider = SutProviderFactory([ - new FakeRequireSsoPolicyValidator(), - new FakeSingleOrgPolicyValidator() - ]); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy, requireSsoPolicy]); - - await sutProvider.Sut.SaveAsync(policyUpdate); - - await AssertPolicySavedAsync(sutProvider, policyUpdate); - } - - [Theory, BitAutoData] - public async Task SaveAsync_ThrowsOnValidationError([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) - { - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("Validation error!"); - var sutProvider = SutProviderFactory([fakePolicyValidator]); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]); - - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(policyUpdate)); - - Assert.Contains("Validation error!", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task VNextSaveAsync_OrganizationDataOwnershipPolicy_ExecutesPostSaveSideEffects( - [PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate, - [Policy(PolicyType.OrganizationDataOwnership, false)] Policy currentPolicy) - { - // Arrange - var sutProvider = SutProviderFactory(); - var savePolicyModel = new SavePolicyModel(policyUpdate); - - currentPolicy.OrganizationId = policyUpdate.OrganizationId; - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) - .Returns(currentPolicy); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy]); - - // Act - var result = await sutProvider.Sut.VNextSaveAsync(savePolicyModel); - - // Assert - await sutProvider.GetDependency() - .Received(1) - .UpsertAsync(result); - - await sutProvider.GetDependency() - .Received(1) - .LogPolicyEventAsync(result, EventType.Policy_Updated); - - await sutProvider.GetDependency() - .Received(1) - .ExecuteSideEffectsAsync(savePolicyModel, result, currentPolicy); - } - - [Theory] - [BitAutoData(PolicyType.SingleOrg)] - [BitAutoData(PolicyType.TwoFactorAuthentication)] - public async Task VNextSaveAsync_NonOrganizationDataOwnershipPolicy_DoesNotExecutePostSaveSideEffects( - PolicyType policyType, - Policy currentPolicy, - [PolicyUpdate] PolicyUpdate policyUpdate) + [Policy(PolicyType.RequireSso, false)] Policy requireSsoPolicy) { // Arrange - policyUpdate.Type = policyType; - currentPolicy.Type = policyType; - currentPolicy.OrganizationId = policyUpdate.OrganizationId; - + var sutProvider = SutProviderFactory( + [ + new FakeRequireSsoDependencyEvent(), + new FakeSingleOrgDependencyEvent() + ]); - var sutProvider = SutProviderFactory(); var savePolicyModel = new SavePolicyModel(policyUpdate); - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) - .Returns(currentPolicy); - ArrangeOrganization(sutProvider, policyUpdate); sutProvider.GetDependency() .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy]); + .Returns([currentPolicy, requireSsoPolicy]); // Act - var result = await sutProvider.Sut.VNextSaveAsync(savePolicyModel); + await sutProvider.Sut.SaveAsync(savePolicyModel); // Assert - await sutProvider.GetDependency() - .Received(1) - .UpsertAsync(result); - - await sutProvider.GetDependency() - .Received(1) - .LogPolicyEventAsync(result, EventType.Policy_Updated); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .ExecuteSideEffectsAsync(default!, default!, default!); + await AssertPolicySavedAsync(sutProvider, policyUpdate); } [Theory, BitAutoData] - public async Task VNextSaveAsync_SendsPushNotification( - [PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg, false)] Policy currentPolicy) + public async Task SaveAsync_ThrowsOnValidationError([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) { // Arrange - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns(""); - var sutProvider = SutProviderFactory([fakePolicyValidator]); + var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); + fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns("Validation error!"); + var sutProvider = SutProviderFactory([ + new FakeSingleOrgDependencyEvent(), + fakePolicyValidationEvent + ]); + var savePolicyModel = new SavePolicyModel(policyUpdate); - currentPolicy.OrganizationId = policyUpdate.OrganizationId; - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) - .Returns(currentPolicy); + var singleOrgPolicy = new Policy + { + Type = PolicyType.SingleOrg, + OrganizationId = policyUpdate.OrganizationId, + Enabled = false + }; ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy]); + sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([singleOrgPolicy]); // Act - var result = await sutProvider.Sut.VNextSaveAsync(savePolicyModel); + var badRequestException = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(savePolicyModel)); // Assert - await sutProvider.GetDependency().Received(1) - .PushAsync(Arg.Is>(p => - p.Type == PushType.PolicyChanged && - p.Target == NotificationTarget.Organization && - p.TargetId == policyUpdate.OrganizationId && - p.ExcludeCurrentContext == false && - p.Payload.OrganizationId == policyUpdate.OrganizationId && - p.Payload.Policy.Id == result.Id && - p.Payload.Policy.Type == policyUpdate.Type && - p.Payload.Policy.Enabled == policyUpdate.Enabled && - p.Payload.Policy.Data == policyUpdate.Data)); + Assert.Contains("Validation error!", badRequestException.Message, StringComparison.OrdinalIgnoreCase); + await AssertPolicyNotSavedAsync(sutProvider); } - [Theory, BitAutoData] - public async Task SaveAsync_SendsPushNotification([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) + /// + /// Returns a new SutProvider with the PolicyUpdateEvents registered in the Sut. + /// + private static SutProvider SutProviderFactory( + IEnumerable? policyUpdateEvents = null) { - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns(""); - var sutProvider = SutProviderFactory([fakePolicyValidator]); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]); - - var result = await sutProvider.Sut.SaveAsync(policyUpdate); - - await sutProvider.GetDependency().Received(1) - .PushAsync(Arg.Is>(p => - p.Type == PushType.PolicyChanged && - p.Target == NotificationTarget.Organization && - p.TargetId == policyUpdate.OrganizationId && - p.ExcludeCurrentContext == false && - p.Payload.OrganizationId == policyUpdate.OrganizationId && - p.Payload.Policy.Id == result.Id && - p.Payload.Policy.Type == policyUpdate.Type && - p.Payload.Policy.Enabled == policyUpdate.Enabled && - p.Payload.Policy.Data == policyUpdate.Data)); - } + var policyEventHandlerFactory = Substitute.For(); + var handlers = policyUpdateEvents ?? []; - [Theory, BitAutoData] - public async Task SaveAsync_ExistingPolicy_SendsPushNotificationWithUpdatedPolicy( - [PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg, false)] Policy currentPolicy) - { - var fakePolicyValidator = new FakeSingleOrgPolicyValidator(); - fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns(""); - var sutProvider = SutProviderFactory([fakePolicyValidator]); + // Setup factory to return handlers based on type + policyEventHandlerFactory.GetHandler(Arg.Any()) + .Returns(callInfo => + { + var policyType = callInfo.Arg(); + var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); + return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); + }); - currentPolicy.OrganizationId = policyUpdate.OrganizationId; - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) - .Returns(currentPolicy); + policyEventHandlerFactory.GetHandler(Arg.Any()) + .Returns(callInfo => + { + var policyType = callInfo.Arg(); + var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); + return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); + }); - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy]); + policyEventHandlerFactory.GetHandler(Arg.Any()) + .Returns(new None()); - var result = await sutProvider.Sut.SaveAsync(policyUpdate); - - await sutProvider.GetDependency().Received(1) - .PushAsync(Arg.Is>(p => - p.Type == PushType.PolicyChanged && - p.Target == NotificationTarget.Organization && - p.TargetId == policyUpdate.OrganizationId && - p.ExcludeCurrentContext == false && - p.Payload.OrganizationId == policyUpdate.OrganizationId && - p.Payload.Policy.Id == result.Id && - p.Payload.Policy.Type == policyUpdate.Type && - p.Payload.Policy.Enabled == policyUpdate.Enabled && - p.Payload.Policy.Data == policyUpdate.Data)); - } + policyEventHandlerFactory.GetHandler(Arg.Any()) + .Returns(new None()); - /// - /// Returns a new SutProvider with the PolicyValidators registered in the Sut. - /// - private static SutProvider SutProviderFactory(IEnumerable? policyValidators = null) - { return new SutProvider() .WithFakeTimeProvider() - .SetDependency(policyValidators ?? []) - .SetDependency(Substitute.For()) + .SetDependency(handlers) + .SetDependency(policyEventHandlerFactory) .Create(); } @@ -497,15 +440,18 @@ await sutProvider.GetDependency() private static async Task AssertPolicySavedAsync(SutProvider sutProvider, PolicyUpdate policyUpdate) { - var expectedPolicy = () => Arg.Is(p => - p.Type == policyUpdate.Type && - p.OrganizationId == policyUpdate.OrganizationId && - p.Enabled == policyUpdate.Enabled && - p.Data == policyUpdate.Data); - - await sutProvider.GetDependency().Received(1).UpsertAsync(expectedPolicy()); + await sutProvider.GetDependency().Received(1).UpsertAsync(ExpectedPolicy()); await sutProvider.GetDependency().Received(1) - .LogPolicyEventAsync(expectedPolicy(), EventType.Policy_Updated); + .LogPolicyEventAsync(ExpectedPolicy(), EventType.Policy_Updated); + + return; + + Policy ExpectedPolicy() => Arg.Is( + p => + p.Type == policyUpdate.Type + && p.OrganizationId == policyUpdate.OrganizationId + && p.Enabled == policyUpdate.Enabled + && p.Data == policyUpdate.Data); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs deleted file mode 100644 index c509a575cedf..000000000000 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/VNextSavePolicyCommandTests.cs +++ /dev/null @@ -1,457 +0,0 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using Microsoft.Extensions.Time.Testing; -using NSubstitute; -using OneOf.Types; -using Xunit; -using EventType = Bit.Core.Enums.EventType; - -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies; - -public class VNextSavePolicyCommandTests -{ - [Theory, BitAutoData] - public async Task SaveAsync_NewPolicy_Success([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) - { - // Arrange - var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); - fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); - var sutProvider = SutProviderFactory([ - new FakeSingleOrgDependencyEvent(), - fakePolicyValidationEvent - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - var newPolicy = new Policy - { - Type = policyUpdate.Type, - OrganizationId = policyUpdate.OrganizationId, - Enabled = false - }; - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([newPolicy]); - - var creationDate = sutProvider.GetDependency().Start; - - // Act - await sutProvider.Sut.SaveAsync(savePolicyModel); - - // Assert - await fakePolicyValidationEvent.ValidateAsyncMock - .Received(1) - .Invoke(Arg.Any(), Arg.Any()); - - await AssertPolicySavedAsync(sutProvider, policyUpdate); - - await sutProvider.GetDependency() - .Received(1) - .UpsertAsync(Arg.Is(p => - p.CreationDate == creationDate && - p.RevisionDate == creationDate)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_ExistingPolicy_Success( - [PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg, false)] Policy currentPolicy) - { - // Arrange - var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); - fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns(""); - var sutProvider = SutProviderFactory([ - new FakeSingleOrgDependencyEvent(), - fakePolicyValidationEvent - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - currentPolicy.OrganizationId = policyUpdate.OrganizationId; - sutProvider.GetDependency() - .GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type) - .Returns(currentPolicy); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy]); - - // Act - await sutProvider.Sut.SaveAsync(savePolicyModel); - - // Assert - await fakePolicyValidationEvent.ValidateAsyncMock - .Received(1) - .Invoke(Arg.Any(), currentPolicy); - - await AssertPolicySavedAsync(sutProvider, policyUpdate); - - - var revisionDate = sutProvider.GetDependency().Start; - - await sutProvider.GetDependency() - .Received(1) - .UpsertAsync(Arg.Is(p => - p.Id == currentPolicy.Id && - p.OrganizationId == currentPolicy.OrganizationId && - p.Type == currentPolicy.Type && - p.CreationDate == currentPolicy.CreationDate && - p.RevisionDate == revisionDate)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate) - { - // Arrange - var sutProvider = SutProviderFactory(); - var savePolicyModel = new SavePolicyModel(policyUpdate); - - sutProvider.GetDependency() - .GetByIdAsync(policyUpdate.OrganizationId) - .Returns(Task.FromResult(null)); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate) - { - // Arrange - var sutProvider = SutProviderFactory(); - var savePolicyModel = new SavePolicyModel(policyUpdate); - - sutProvider.GetDependency() - .GetByIdAsync(policyUpdate.OrganizationId) - .Returns(new Organization - { - Id = policyUpdate.OrganizationId, - UsePolicies = false - }); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("cannot use policies", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_RequiredPolicyIsNull_Throws( - [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - var requireSsoPolicy = new Policy - { - Type = PolicyType.RequireSso, - OrganizationId = policyUpdate.OrganizationId, - Enabled = false - }; - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([requireSsoPolicy]); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_RequiredPolicyNotEnabled_Throws( - [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg, false)] Policy singleOrgPolicy) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - var requireSsoPolicy = new Policy - { - Type = PolicyType.RequireSso, - OrganizationId = policyUpdate.OrganizationId, - Enabled = false - }; - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([singleOrgPolicy, requireSsoPolicy]); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_RequiredPolicyEnabled_Success( - [PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - var requireSsoPolicy = new Policy - { - Type = PolicyType.RequireSso, - OrganizationId = policyUpdate.OrganizationId, - Enabled = false - }; - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([singleOrgPolicy, requireSsoPolicy]); - - // Act - await sutProvider.Sut.SaveAsync(savePolicyModel); - - // Assert - await AssertPolicySavedAsync(sutProvider, policyUpdate); - } - - [Theory, BitAutoData] - public async Task SaveAsync_DependentPolicyIsEnabled_Throws( - [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy, requireSsoPolicy]); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Turn off the Require single sign-on authentication policy because it requires the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_MultipleDependentPoliciesAreEnabled_Throws( - [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso)] Policy requireSsoPolicy, - [Policy(PolicyType.MaximumVaultTimeout)] Policy vaultTimeoutPolicy) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent(), - new FakeVaultTimeoutDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy, requireSsoPolicy, vaultTimeoutPolicy]); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Turn off all of the policies that require the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - [Theory, BitAutoData] - public async Task SaveAsync_DependentPolicyNotEnabled_Success( - [PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate, - [Policy(PolicyType.SingleOrg)] Policy currentPolicy, - [Policy(PolicyType.RequireSso, false)] Policy requireSsoPolicy) - { - // Arrange - var sutProvider = SutProviderFactory( - [ - new FakeRequireSsoDependencyEvent(), - new FakeSingleOrgDependencyEvent() - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(policyUpdate.OrganizationId) - .Returns([currentPolicy, requireSsoPolicy]); - - // Act - await sutProvider.Sut.SaveAsync(savePolicyModel); - - // Assert - await AssertPolicySavedAsync(sutProvider, policyUpdate); - } - - [Theory, BitAutoData] - public async Task SaveAsync_ThrowsOnValidationError([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate) - { - // Arrange - var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent(); - fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any(), Arg.Any()).Returns("Validation error!"); - var sutProvider = SutProviderFactory([ - new FakeSingleOrgDependencyEvent(), - fakePolicyValidationEvent - ]); - - var savePolicyModel = new SavePolicyModel(policyUpdate); - - var singleOrgPolicy = new Policy - { - Type = PolicyType.SingleOrg, - OrganizationId = policyUpdate.OrganizationId, - Enabled = false - }; - - ArrangeOrganization(sutProvider, policyUpdate); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([singleOrgPolicy]); - - // Act - var badRequestException = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(savePolicyModel)); - - // Assert - Assert.Contains("Validation error!", badRequestException.Message, StringComparison.OrdinalIgnoreCase); - await AssertPolicyNotSavedAsync(sutProvider); - } - - /// - /// Returns a new SutProvider with the PolicyUpdateEvents registered in the Sut. - /// - private static SutProvider SutProviderFactory( - IEnumerable? policyUpdateEvents = null) - { - var policyEventHandlerFactory = Substitute.For(); - var handlers = policyUpdateEvents ?? []; - - // Setup factory to return handlers based on type - policyEventHandlerFactory.GetHandler(Arg.Any()) - .Returns(callInfo => - { - var policyType = callInfo.Arg(); - var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); - return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); - }); - - policyEventHandlerFactory.GetHandler(Arg.Any()) - .Returns(callInfo => - { - var policyType = callInfo.Arg(); - var handler = handlers.OfType().FirstOrDefault(e => e.Type == policyType); - return handler != null ? OneOf.OneOf.FromT0(handler) : OneOf.OneOf.FromT1(new None()); - }); - - policyEventHandlerFactory.GetHandler(Arg.Any()) - .Returns(new None()); - - policyEventHandlerFactory.GetHandler(Arg.Any()) - .Returns(new None()); - - return new SutProvider() - .WithFakeTimeProvider() - .SetDependency(handlers) - .SetDependency(policyEventHandlerFactory) - .Create(); - } - - private static void ArrangeOrganization(SutProvider sutProvider, PolicyUpdate policyUpdate) - { - sutProvider.GetDependency() - .GetByIdAsync(policyUpdate.OrganizationId) - .Returns(new Organization - { - Id = policyUpdate.OrganizationId, - UsePolicies = true - }); - } - - private static async Task AssertPolicyNotSavedAsync(SutProvider sutProvider) - { - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .UpsertAsync(default!); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .LogPolicyEventAsync(default, default); - } - - private static async Task AssertPolicySavedAsync(SutProvider sutProvider, PolicyUpdate policyUpdate) - { - await sutProvider.GetDependency().Received(1).UpsertAsync(ExpectedPolicy()); - - await sutProvider.GetDependency().Received(1) - .LogPolicyEventAsync(ExpectedPolicy(), EventType.Policy_Updated); - - return; - - Policy ExpectedPolicy() => Arg.Is( - p => - p.Type == policyUpdate.Type - && p.OrganizationId == policyUpdate.OrganizationId - && p.Enabled == policyUpdate.Enabled - && p.Data == policyUpdate.Data); - } -} diff --git a/test/Core.Test/Auth/Services/SsoConfigServiceTests.cs b/test/Core.Test/Auth/Services/SsoConfigServiceTests.cs index ca4378e6ec67..23366caba817 100644 --- a/test/Core.Test/Auth/Services/SsoConfigServiceTests.cs +++ b/test/Core.Test/Auth/Services/SsoConfigServiceTests.cs @@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; @@ -340,14 +339,14 @@ public async Task SaveAsync_Tde_Enable_Required_Policies(SutProvider().Received(1) + await sutProvider.GetDependency().Received(1) .SaveAsync( Arg.Is(t => t.PolicyUpdate.Type == PolicyType.SingleOrg && t.PolicyUpdate.OrganizationId == organization.Id && t.PolicyUpdate.Enabled) ); - await sutProvider.GetDependency().Received(1) + await sutProvider.GetDependency().Received(1) .SaveAsync( Arg.Is(t => t.PolicyUpdate.Type == PolicyType.ResetPassword && t.PolicyUpdate.GetDataModel().AutoEnrollEnabled && @@ -355,7 +354,7 @@ await sutProvider.GetDependency().Received(1) t.PolicyUpdate.Enabled) ); - await sutProvider.GetDependency().Received(1) + await sutProvider.GetDependency().Received(1) .SaveAsync( Arg.Is(t => t.PolicyUpdate.Type == PolicyType.RequireSso && t.PolicyUpdate.OrganizationId == organization.Id && @@ -367,7 +366,7 @@ await sutProvider.GetDependency().ReceivedWithAnyArgs() } [Theory, BitAutoData] - public async Task SaveAsync_Tde_UsesVNextSavePolicyCommand( + public async Task SaveAsync_Tde_SavesRequiredPolicies( SutProvider sutProvider, Organization organization) { var ssoConfig = new SsoConfig @@ -383,7 +382,7 @@ public async Task SaveAsync_Tde_UsesVNextSavePolicyCommand( await sutProvider.Sut.SaveAsync(ssoConfig, organization); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.Type == PolicyType.SingleOrg && @@ -391,7 +390,7 @@ await sutProvider.GetDependency() m.PolicyUpdate.Enabled && m.PerformedBy is SystemUser)); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.Type == PolicyType.ResetPassword && @@ -400,7 +399,7 @@ await sutProvider.GetDependency() m.PolicyUpdate.Enabled && m.PerformedBy is SystemUser)); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Is(m => m.PolicyUpdate.Type == PolicyType.RequireSso &&