mirror of
https://github.com/bitwarden/server
synced 2025-12-10 13:23:27 +00:00
[PM-26683] Migrate individual policy handlers/validators to the new Policy Update Events pattern (#6458)
* Implement IOnPolicyPreUpdateEvent for FreeFamiliesForEnterprisePolicyValidator and add corresponding unit tests * Implement IEnforceDependentPoliciesEvent in MaximumVaultTimeoutPolicyValidator * Rename test methods in FreeFamiliesForEnterprisePolicyValidatorTests for consistency * Implement IPolicyValidationEvent and IEnforceDependentPoliciesEvent in RequireSsoPolicyValidator and enhance unit tests * Implement IPolicyValidationEvent and IEnforceDependentPoliciesEvent in ResetPasswordPolicyValidator and add unit tests * Implement IOnPolicyPreUpdateEvent in TwoFactorAuthenticationPolicyValidator and add unit tests * Implement IPolicyValidationEvent and IOnPolicyPreUpdateEvent in SingleOrgPolicyValidator with corresponding unit tests * Implement IOnPolicyPostUpdateEvent in OrganizationDataOwnershipPolicyValidator and add unit tests for ExecutePostUpsertSideEffectAsync * Refactor policy validation logic in VNextSavePolicyCommand to simplify enabling and disabling requirements checks * Refactor VNextSavePolicyCommand to replace IEnforceDependentPoliciesEvent with IPolicyUpdateEvent and update related tests * Add AddPolicyUpdateEvents method and update service registration for policy update events
This commit is contained in:
@@ -13,25 +13,11 @@ public class VNextSavePolicyCommand(
|
|||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
IEnumerable<IEnforceDependentPoliciesEvent> policyValidationEventHandlers,
|
IEnumerable<IPolicyUpdateEvent> policyUpdateEventHandlers,
|
||||||
TimeProvider timeProvider,
|
TimeProvider timeProvider,
|
||||||
IPolicyEventHandlerFactory policyEventHandlerFactory)
|
IPolicyEventHandlerFactory policyEventHandlerFactory)
|
||||||
: IVNextSavePolicyCommand
|
: IVNextSavePolicyCommand
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyDictionary<PolicyType, IEnforceDependentPoliciesEvent> _policyValidationEvents = MapToDictionary(policyValidationEventHandlers);
|
|
||||||
|
|
||||||
private static Dictionary<PolicyType, IEnforceDependentPoliciesEvent> MapToDictionary(IEnumerable<IEnforceDependentPoliciesEvent> policyValidationEventHandlers)
|
|
||||||
{
|
|
||||||
var policyValidationEventsDict = new Dictionary<PolicyType, IEnforceDependentPoliciesEvent>();
|
|
||||||
foreach (var policyValidationEvent in policyValidationEventHandlers)
|
|
||||||
{
|
|
||||||
if (!policyValidationEventsDict.TryAdd(policyValidationEvent.Type, policyValidationEvent))
|
|
||||||
{
|
|
||||||
throw new Exception($"Duplicate PolicyValidationEvent for {policyValidationEvent.Type} policy.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return policyValidationEventsDict;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Policy> SaveAsync(SavePolicyModel policyRequest)
|
public async Task<Policy> SaveAsync(SavePolicyModel policyRequest)
|
||||||
{
|
{
|
||||||
@@ -112,32 +98,26 @@ public class VNextSavePolicyCommand(
|
|||||||
Policy? currentPolicy,
|
Policy? currentPolicy,
|
||||||
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
||||||
{
|
{
|
||||||
var result = policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(policyUpdateRequest.Type);
|
var isCurrentlyEnabled = currentPolicy?.Enabled == true;
|
||||||
|
var isBeingEnabled = policyUpdateRequest.Enabled && !isCurrentlyEnabled;
|
||||||
|
var isBeingDisabled = !policyUpdateRequest.Enabled && isCurrentlyEnabled;
|
||||||
|
|
||||||
result.Switch(
|
if (isBeingEnabled)
|
||||||
validator =>
|
{
|
||||||
{
|
ValidateEnablingRequirements(policyUpdateRequest.Type, savedPoliciesDict);
|
||||||
var isCurrentlyEnabled = currentPolicy?.Enabled == true;
|
}
|
||||||
|
else if (isBeingDisabled)
|
||||||
switch (policyUpdateRequest.Enabled)
|
{
|
||||||
{
|
ValidateDisablingRequirements(policyUpdateRequest.Type, savedPoliciesDict);
|
||||||
case true when !isCurrentlyEnabled:
|
}
|
||||||
ValidateEnablingRequirements(validator, savedPoliciesDict);
|
|
||||||
return;
|
|
||||||
case false when isCurrentlyEnabled:
|
|
||||||
ValidateDisablingRequirements(validator, policyUpdateRequest.Type, savedPoliciesDict);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => { });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateDisablingRequirements(
|
private void ValidateDisablingRequirements(
|
||||||
IEnforceDependentPoliciesEvent validator,
|
|
||||||
PolicyType policyType,
|
PolicyType policyType,
|
||||||
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
||||||
{
|
{
|
||||||
var dependentPolicyTypes = _policyValidationEvents.Values
|
var dependentPolicyTypes = policyUpdateEventHandlers
|
||||||
|
.OfType<IEnforceDependentPoliciesEvent>()
|
||||||
.Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType))
|
.Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType))
|
||||||
.Select(otherValidator => otherValidator.Type)
|
.Select(otherValidator => otherValidator.Type)
|
||||||
.Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) &&
|
.Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) &&
|
||||||
@@ -147,24 +127,31 @@ public class VNextSavePolicyCommand(
|
|||||||
switch (dependentPolicyTypes)
|
switch (dependentPolicyTypes)
|
||||||
{
|
{
|
||||||
case { Count: 1 }:
|
case { Count: 1 }:
|
||||||
throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {validator.Type.GetName()} policy.");
|
throw new BadRequestException($"Turn off the {dependentPolicyTypes.First().GetName()} policy because it requires the {policyType.GetName()} policy.");
|
||||||
case { Count: > 1 }:
|
case { Count: > 1 }:
|
||||||
throw new BadRequestException($"Turn off all of the policies that require the {validator.Type.GetName()} policy.");
|
throw new BadRequestException($"Turn off all of the policies that require the {policyType.GetName()} policy.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ValidateEnablingRequirements(
|
private void ValidateEnablingRequirements(
|
||||||
IEnforceDependentPoliciesEvent validator,
|
PolicyType policyType,
|
||||||
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
||||||
{
|
{
|
||||||
var missingRequiredPolicyTypes = validator.RequiredPolicies
|
var result = policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(policyType);
|
||||||
.Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true })
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
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 */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecutePreUpsertSideEffectAsync(
|
private async Task ExecutePreUpsertSideEffectAsync(
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ public static class PolicyServiceCollectionExtensions
|
|||||||
services.AddPolicyValidators();
|
services.AddPolicyValidators();
|
||||||
services.AddPolicyRequirements();
|
services.AddPolicyRequirements();
|
||||||
services.AddPolicySideEffects();
|
services.AddPolicySideEffects();
|
||||||
|
services.AddPolicyUpdateEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use AddPolicyUpdateEvents instead.")]
|
||||||
private static void AddPolicyValidators(this IServiceCollection services)
|
private static void AddPolicyValidators(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<IPolicyValidator, TwoFactorAuthenticationPolicyValidator>();
|
services.AddScoped<IPolicyValidator, TwoFactorAuthenticationPolicyValidator>();
|
||||||
@@ -34,11 +36,23 @@ public static class PolicyServiceCollectionExtensions
|
|||||||
services.AddScoped<IPolicyValidator, FreeFamiliesForEnterprisePolicyValidator>();
|
services.AddScoped<IPolicyValidator, FreeFamiliesForEnterprisePolicyValidator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use AddPolicyUpdateEvents instead.")]
|
||||||
private static void AddPolicySideEffects(this IServiceCollection services)
|
private static void AddPolicySideEffects(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<IPostSavePolicySideEffect, OrganizationDataOwnershipPolicyValidator>();
|
services.AddScoped<IPostSavePolicySideEffect, OrganizationDataOwnershipPolicyValidator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddPolicyUpdateEvents(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, RequireSsoPolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, TwoFactorAuthenticationPolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, SingleOrgPolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, ResetPasswordPolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, MaximumVaultTimeoutPolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, FreeFamiliesForEnterprisePolicyValidator>();
|
||||||
|
services.AddScoped<IPolicyUpdateEvent, OrganizationDataOwnershipPolicyValidator>();
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddPolicyRequirements(this IServiceCollection services)
|
private static void AddPolicyRequirements(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
@@ -12,11 +13,16 @@ public class FreeFamiliesForEnterprisePolicyValidator(
|
|||||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IOrganizationRepository organizationRepository)
|
IOrganizationRepository organizationRepository)
|
||||||
: IPolicyValidator
|
: IPolicyValidator, IOnPolicyPreUpdateEvent
|
||||||
{
|
{
|
||||||
public PolicyType Type => PolicyType.FreeFamiliesSponsorshipPolicy;
|
public PolicyType Type => PolicyType.FreeFamiliesSponsorshipPolicy;
|
||||||
public IEnumerable<PolicyType> RequiredPolicies => [];
|
public IEnumerable<PolicyType> RequiredPolicies => [];
|
||||||
|
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
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.PolicyValidators;
|
||||||
|
|
||||||
public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator
|
public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent
|
||||||
{
|
{
|
||||||
public PolicyType Type => PolicyType.MaximumVaultTimeout;
|
public PolicyType Type => PolicyType.MaximumVaultTimeout;
|
||||||
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
|
|
||||||
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.Models;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Please do not extend or expand 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.
|
|
||||||
/// </summary>
|
|
||||||
public class OrganizationDataOwnershipPolicyValidator(
|
public class OrganizationDataOwnershipPolicyValidator(
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
ICollectionRepository collectionRepository,
|
ICollectionRepository collectionRepository,
|
||||||
IEnumerable<IPolicyRequirementFactory<IPolicyRequirement>> factories,
|
IEnumerable<IPolicyRequirementFactory<IPolicyRequirement>> factories,
|
||||||
IFeatureService featureService)
|
IFeatureService featureService)
|
||||||
: OrganizationPolicyValidator(policyRepository, factories), IPostSavePolicySideEffect
|
: OrganizationPolicyValidator(policyRepository, factories), IPostSavePolicySideEffect, IOnPolicyPostUpdateEvent
|
||||||
{
|
{
|
||||||
|
public PolicyType Type => PolicyType.OrganizationDataOwnership;
|
||||||
|
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync(
|
||||||
|
SavePolicyModel policyRequest,
|
||||||
|
Policy postUpsertedPolicyState,
|
||||||
|
Policy? previousPolicyState)
|
||||||
|
{
|
||||||
|
await ExecuteSideEffectsAsync(policyRequest, postUpsertedPolicyState, previousPolicyState);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ExecuteSideEffectsAsync(
|
public async Task ExecuteSideEffectsAsync(
|
||||||
SavePolicyModel policyRequest,
|
SavePolicyModel policyRequest,
|
||||||
Policy postUpdatedPolicy,
|
Policy postUpdatedPolicy,
|
||||||
@@ -68,5 +76,4 @@ public class OrganizationDataOwnershipPolicyValidator(
|
|||||||
userOrgIds,
|
userOrgIds,
|
||||||
defaultCollectionName);
|
defaultCollectionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||||
|
|
||||||
public class RequireSsoPolicyValidator : IPolicyValidator
|
public class RequireSsoPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent
|
||||||
{
|
{
|
||||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
|
|
||||||
@@ -20,6 +21,11 @@ public class RequireSsoPolicyValidator : IPolicyValidator
|
|||||||
public PolicyType Type => PolicyType.RequireSso;
|
public PolicyType Type => PolicyType.RequireSso;
|
||||||
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
||||||
|
|
||||||
|
public async Task<string> ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (policyUpdate is not { Enabled: true })
|
if (policyUpdate is not { Enabled: true })
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||||
|
|
||||||
public class ResetPasswordPolicyValidator : IPolicyValidator
|
public class ResetPasswordPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent
|
||||||
{
|
{
|
||||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
public PolicyType Type => PolicyType.ResetPassword;
|
public PolicyType Type => PolicyType.ResetPassword;
|
||||||
@@ -20,6 +21,11 @@ public class ResetPasswordPolicyValidator : IPolicyValidator
|
|||||||
_ssoConfigRepository = ssoConfigRepository;
|
_ssoConfigRepository = ssoConfigRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (policyUpdate is not { Enabled: true } ||
|
if (policyUpdate is not { Enabled: true } ||
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@@ -17,7 +18,7 @@ using Bit.Core.Services;
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||||
|
|
||||||
public class SingleOrgPolicyValidator : IPolicyValidator
|
public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IOnPolicyPreUpdateEvent
|
||||||
{
|
{
|
||||||
public PolicyType Type => PolicyType.SingleOrg;
|
public PolicyType Type => PolicyType.SingleOrg;
|
||||||
private const string OrganizationNotFoundErrorMessage = "Organization not found.";
|
private const string OrganizationNotFoundErrorMessage = "Organization not found.";
|
||||||
@@ -57,6 +58,16 @@ public class SingleOrgPolicyValidator : IPolicyValidator
|
|||||||
|
|
||||||
public IEnumerable<PolicyType> RequiredPolicies => [];
|
public IEnumerable<PolicyType> RequiredPolicies => [];
|
||||||
|
|
||||||
|
public async Task<string> ValidateAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
return await ValidateAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Models.Data;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@@ -16,7 +17,7 @@ using Bit.Core.Services;
|
|||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||||
|
|
||||||
public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator, IOnPolicyPreUpdateEvent
|
||||||
{
|
{
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
@@ -46,6 +47,11 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
|||||||
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||||
|
{
|
||||||
|
await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||||
{
|
{
|
||||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||||
|
|||||||
@@ -72,4 +72,65 @@ public class FreeFamiliesForEnterprisePolicyValidatorTests
|
|||||||
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.Name);
|
organizationSponsorships[0].SponsoredOrganizationId.ToString(), organization.Name);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync_DoesNotNotifyUserWhenPolicyDisabled(
|
||||||
|
Organization organization,
|
||||||
|
List<OrganizationSponsorship> organizationSponsorships,
|
||||||
|
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, true)] Policy policy,
|
||||||
|
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.Enabled = true;
|
||||||
|
policyUpdate.Enabled = false;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(organization);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||||
|
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(organizationSponsorships);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(default, default, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync_DoesNotifyUserWhenPolicyEnabled(
|
||||||
|
Organization organization,
|
||||||
|
List<OrganizationSponsorship> organizationSponsorships,
|
||||||
|
[PolicyUpdate(PolicyType.FreeFamiliesSponsorshipPolicy)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.FreeFamiliesSponsorshipPolicy, false)] Policy policy,
|
||||||
|
SutProvider<FreeFamiliesForEnterprisePolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.Enabled = false;
|
||||||
|
policyUpdate.Enabled = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetByIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(organization);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
|
||||||
|
.GetManyBySponsoringOrganizationAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(organizationSponsorships);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);
|
||||||
|
|
||||||
|
var offerAcceptanceDate = organizationSponsorships[0].ValidUntil!.Value.AddDays(-7).ToString("MM/dd/yyyy");
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(
|
||||||
|
organizationSponsorships[0].FriendlyName,
|
||||||
|
offerAcceptanceDate,
|
||||||
|
organizationSponsorships[0].SponsoredOrganizationId.ToString(),
|
||||||
|
organization.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,4 +274,176 @@ public class OrganizationDataOwnershipPolicyValidatorTests
|
|||||||
return sut;
|
return sut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_FeatureFlagDisabled_DoesNothing(
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState,
|
||||||
|
SutProvider<OrganizationDataOwnershipPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertDefaultCollectionsAsync(default, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNothing(
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership, true)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, true)] Policy previousPolicyState,
|
||||||
|
SutProvider<OrganizationDataOwnershipPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
previousPolicyState.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertDefaultCollectionsAsync(default, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothing(
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, false)] Policy postUpdatedPolicy,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership)] Policy previousPolicyState,
|
||||||
|
SutProvider<OrganizationDataOwnershipPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
previousPolicyState.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertDefaultCollectionsAsync(default, default, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_WhenNoUsersExist_DoNothing(
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership, true)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState,
|
||||||
|
OrganizationDataOwnershipPolicyRequirementFactory factory)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
previousPolicyState.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var policyRepository = ArrangePolicyRepository([]);
|
||||||
|
var collectionRepository = Substitute.For<ICollectionRepository>();
|
||||||
|
|
||||||
|
var sut = ArrangeSut(factory, policyRepository, collectionRepository);
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await collectionRepository
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertDefaultCollectionsAsync(
|
||||||
|
default,
|
||||||
|
default,
|
||||||
|
default);
|
||||||
|
|
||||||
|
await policyRepository
|
||||||
|
.Received(1)
|
||||||
|
.GetPolicyDetailsByOrganizationIdAsync(
|
||||||
|
policyUpdate.OrganizationId,
|
||||||
|
PolicyType.OrganizationDataOwnership);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitMemberAutoData(nameof(ShouldUpsertDefaultCollectionsTestCases))]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsertDefaultCollections(
|
||||||
|
Policy postUpdatedPolicy,
|
||||||
|
Policy? previousPolicyState,
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate,
|
||||||
|
[OrganizationPolicyDetails(PolicyType.OrganizationDataOwnership)] IEnumerable<OrganizationPolicyDetails> orgPolicyDetails,
|
||||||
|
OrganizationDataOwnershipPolicyRequirementFactory factory)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var orgPolicyDetailsList = orgPolicyDetails.ToList();
|
||||||
|
foreach (var policyDetail in orgPolicyDetailsList)
|
||||||
|
{
|
||||||
|
policyDetail.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var policyRepository = ArrangePolicyRepository(orgPolicyDetailsList);
|
||||||
|
var collectionRepository = Substitute.For<ICollectionRepository>();
|
||||||
|
|
||||||
|
var sut = ArrangeSut(factory, policyRepository, collectionRepository);
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, new OrganizationModelOwnershipPolicyModel(_defaultUserCollectionName));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await collectionRepository
|
||||||
|
.Received(1)
|
||||||
|
.UpsertDefaultCollectionsAsync(
|
||||||
|
policyUpdate.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(ids => ids.Count() == 3),
|
||||||
|
_defaultUserCollectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitMemberAutoData(nameof(WhenDefaultCollectionsDoesNotExistTestCases))]
|
||||||
|
public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsInvalid_DoesNothing(
|
||||||
|
IPolicyMetadataModel metadata,
|
||||||
|
[PolicyUpdate(PolicyType.OrganizationDataOwnership)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, true)] Policy postUpdatedPolicy,
|
||||||
|
[Policy(PolicyType.OrganizationDataOwnership, false)] Policy previousPolicyState,
|
||||||
|
SutProvider<OrganizationDataOwnershipPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
postUpdatedPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
previousPolicyState.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
policyUpdate.Enabled = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var policyRequest = new SavePolicyModel(policyUpdate, null, metadata);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.UpsertDefaultCollectionsAsync(default, default, default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,4 +72,66 @@ public class RequireSsoPolicyValidatorTests
|
|||||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||||
Assert.True(string.IsNullOrEmpty(result));
|
Assert.True(string.IsNullOrEmpty(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError(
|
||||||
|
[PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.RequireSso)] Policy policy,
|
||||||
|
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = true };
|
||||||
|
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_ValidationError(
|
||||||
|
[PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.RequireSso)] Policy policy,
|
||||||
|
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = true };
|
||||||
|
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_DecryptionOptionsNotEnabled_Success(
|
||||||
|
[PolicyUpdate(PolicyType.RequireSso, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.RequireSso)] Policy policy,
|
||||||
|
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = false };
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.True(string.IsNullOrEmpty(result));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,4 +68,59 @@ public class ResetPasswordPolicyValidatorTests
|
|||||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||||
Assert.True(string.IsNullOrEmpty(result));
|
Assert.True(string.IsNullOrEmpty(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(true, false)]
|
||||||
|
[BitAutoData(false, true)]
|
||||||
|
[BitAutoData(false, false)]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeEnabled_ValidationError(
|
||||||
|
bool policyEnabled,
|
||||||
|
bool autoEnrollEnabled,
|
||||||
|
[PolicyUpdate(PolicyType.ResetPassword)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||||
|
SutProvider<ResetPasswordPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policyUpdate.Enabled = policyEnabled;
|
||||||
|
policyUpdate.SetDataModel(new ResetPasswordDataModel
|
||||||
|
{
|
||||||
|
AutoEnrollEnabled = autoEnrollEnabled
|
||||||
|
});
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = true };
|
||||||
|
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_TdeNotEnabled_Success(
|
||||||
|
[PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||||
|
SutProvider<ResetPasswordPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policyUpdate.SetDataModel(new ResetPasswordDataModel
|
||||||
|
{
|
||||||
|
AutoEnrollEnabled = false
|
||||||
|
});
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = false };
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.True(string.IsNullOrEmpty(result));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
@@ -145,4 +146,135 @@ public class SingleOrgPolicyValidatorTests
|
|||||||
.Received(1)
|
.Received(1)
|
||||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorEnabled_ValidationError(
|
||||||
|
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.SingleOrg)] Policy policy,
|
||||||
|
SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = true };
|
||||||
|
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_KeyConnectorNotEnabled_Success(
|
||||||
|
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.SingleOrg)] Policy policy,
|
||||||
|
SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var ssoConfig = new SsoConfig { Enabled = false };
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||||
|
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(ssoConfig);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||||
|
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, policy);
|
||||||
|
Assert.True(string.IsNullOrEmpty(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync_RevokesNonCompliantUsers(
|
||||||
|
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||||
|
Guid savingUserId,
|
||||||
|
Guid nonCompliantUserId,
|
||||||
|
Organization organization,
|
||||||
|
SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
var compliantUser1 = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
UserId = new Guid(),
|
||||||
|
Email = "user1@example.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
var compliantUser2 = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
UserId = new Guid(),
|
||||||
|
Email = "user2@example.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
var nonCompliantUser = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
UserId = nonCompliantUserId,
|
||||||
|
Email = "user3@example.com"
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||||
|
|
||||||
|
var otherOrganizationUser = new OrganizationUser
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
OrganizationId = new Guid(),
|
||||||
|
UserId = nonCompliantUserId,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||||
|
.Returns([otherOrganizationUser]);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||||
|
.Returns(new CommandResult());
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
|
Arg.Is<RevokeOrganizationUsersRequest>(r =>
|
||||||
|
r.OrganizationId == organization.Id &&
|
||||||
|
r.OrganizationUsers.Count() == 1 &&
|
||||||
|
r.OrganizationUsers.First().Id == nonCompliantUser.Id));
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.DidNotReceive()
|
||||||
|
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email);
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.DidNotReceive()
|
||||||
|
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email);
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,4 +136,124 @@ public class TwoFactorAuthenticationPolicyValidatorTests
|
|||||||
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(),
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(),
|
||||||
compliantUser.Email);
|
compliantUser.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync_GivenNonCompliantUsersWithoutMasterPassword_Throws(
|
||||||
|
Organization organization,
|
||||||
|
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||||
|
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
|
||||||
|
var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Email = "user3@test.com",
|
||||||
|
Name = "TEST",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
HasMasterPassword = false
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns([orgUserDetailUserWithout2Fa]);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||||
|
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||||
|
{
|
||||||
|
(orgUserDetailUserWithout2Fa, false),
|
||||||
|
});
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
|
sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy));
|
||||||
|
|
||||||
|
Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ExecutePreUpsertSideEffectAsync_RevokesOnlyNonCompliantUsers(
|
||||||
|
Organization organization,
|
||||||
|
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||||
|
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
organization.Id = policyUpdate.OrganizationId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||||
|
|
||||||
|
var nonCompliantUser = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Email = "user3@test.com",
|
||||||
|
Name = "TEST",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
HasMasterPassword = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var compliantUser = new OrganizationUserUserDetails
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
Type = OrganizationUserType.User,
|
||||||
|
Email = "user4@test.com",
|
||||||
|
Name = "TEST",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
HasMasterPassword = true
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns([nonCompliantUser, compliantUser]);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||||
|
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||||
|
{
|
||||||
|
(nonCompliantUser, false),
|
||||||
|
(compliantUser, true)
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||||
|
.Returns(new CommandResult());
|
||||||
|
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.ExecutePreUpsertSideEffectAsync(savePolicyModel, policy);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(Arg.Is<RevokeOrganizationUsersRequest>(req =>
|
||||||
|
req.OrganizationId == policyUpdate.OrganizationId &&
|
||||||
|
req.OrganizationUsers.SequenceEqual(new[] { nonCompliantUser })
|
||||||
|
));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(),
|
||||||
|
nonCompliantUser.Email);
|
||||||
|
|
||||||
|
// Did not send out an email for compliantUser
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(0)
|
||||||
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(),
|
||||||
|
compliantUser.Email);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ public class VNextSavePolicyCommandTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
||||||
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
||||||
var sutProvider = SutProviderFactory(
|
var sutProvider = SutProviderFactory([
|
||||||
[new FakeSingleOrgDependencyEvent()],
|
new FakeSingleOrgDependencyEvent(),
|
||||||
[fakePolicyValidationEvent]);
|
fakePolicyValidationEvent
|
||||||
|
]);
|
||||||
|
|
||||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
@@ -71,9 +72,10 @@ public class VNextSavePolicyCommandTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
||||||
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
||||||
var sutProvider = SutProviderFactory(
|
var sutProvider = SutProviderFactory([
|
||||||
[new FakeSingleOrgDependencyEvent()],
|
new FakeSingleOrgDependencyEvent(),
|
||||||
[fakePolicyValidationEvent]);
|
fakePolicyValidationEvent
|
||||||
|
]);
|
||||||
|
|
||||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
@@ -110,23 +112,6 @@ public class VNextSavePolicyCommandTests
|
|||||||
p.RevisionDate == revisionDate));
|
p.RevisionDate == revisionDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Constructor_DuplicatePolicyDependencyEvents_Throws()
|
|
||||||
{
|
|
||||||
// Arrange & Act
|
|
||||||
var exception = Assert.Throws<Exception>(() =>
|
|
||||||
new VNextSavePolicyCommand(
|
|
||||||
Substitute.For<IApplicationCacheService>(),
|
|
||||||
Substitute.For<IEventService>(),
|
|
||||||
Substitute.For<IPolicyRepository>(),
|
|
||||||
[new FakeSingleOrgDependencyEvent(), new FakeSingleOrgDependencyEvent()],
|
|
||||||
Substitute.For<TimeProvider>(),
|
|
||||||
Substitute.For<IPolicyEventHandlerFactory>()));
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Contains("Duplicate PolicyValidationEvent for SingleOrg policy", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
||||||
{
|
{
|
||||||
@@ -366,9 +351,10 @@ public class VNextSavePolicyCommandTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
||||||
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("Validation error!");
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("Validation error!");
|
||||||
var sutProvider = SutProviderFactory(
|
var sutProvider = SutProviderFactory([
|
||||||
[new FakeSingleOrgDependencyEvent()],
|
new FakeSingleOrgDependencyEvent(),
|
||||||
[fakePolicyValidationEvent]);
|
fakePolicyValidationEvent
|
||||||
|
]);
|
||||||
|
|
||||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||||
|
|
||||||
@@ -392,20 +378,20 @@ public class VNextSavePolicyCommandTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a new SutProvider with the PolicyDependencyEvents registered in the Sut.
|
/// Returns a new SutProvider with the PolicyUpdateEvents registered in the Sut.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static SutProvider<VNextSavePolicyCommand> SutProviderFactory(
|
private static SutProvider<VNextSavePolicyCommand> SutProviderFactory(
|
||||||
IEnumerable<IEnforceDependentPoliciesEvent>? policyDependencyEvents = null,
|
IEnumerable<IPolicyUpdateEvent>? policyUpdateEvents = null)
|
||||||
IEnumerable<IPolicyValidationEvent>? policyValidationEvents = null)
|
|
||||||
{
|
{
|
||||||
var policyEventHandlerFactory = Substitute.For<IPolicyEventHandlerFactory>();
|
var policyEventHandlerFactory = Substitute.For<IPolicyEventHandlerFactory>();
|
||||||
|
var handlers = policyUpdateEvents ?? [];
|
||||||
|
|
||||||
// Setup factory to return handlers based on type
|
// Setup factory to return handlers based on type
|
||||||
policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(Arg.Any<PolicyType>())
|
policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(Arg.Any<PolicyType>())
|
||||||
.Returns(callInfo =>
|
.Returns(callInfo =>
|
||||||
{
|
{
|
||||||
var policyType = callInfo.Arg<PolicyType>();
|
var policyType = callInfo.Arg<PolicyType>();
|
||||||
var handler = policyDependencyEvents?.FirstOrDefault(e => e.Type == policyType);
|
var handler = handlers.OfType<IEnforceDependentPoliciesEvent>().FirstOrDefault(e => e.Type == policyType);
|
||||||
return handler != null ? OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT0(handler) : OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT1(new None());
|
return handler != null ? OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT0(handler) : OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT1(new None());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -413,7 +399,7 @@ public class VNextSavePolicyCommandTests
|
|||||||
.Returns(callInfo =>
|
.Returns(callInfo =>
|
||||||
{
|
{
|
||||||
var policyType = callInfo.Arg<PolicyType>();
|
var policyType = callInfo.Arg<PolicyType>();
|
||||||
var handler = policyValidationEvents?.FirstOrDefault(e => e.Type == policyType);
|
var handler = handlers.OfType<IPolicyValidationEvent>().FirstOrDefault(e => e.Type == policyType);
|
||||||
return handler != null ? OneOf.OneOf<IPolicyValidationEvent, None>.FromT0(handler) : OneOf.OneOf<IPolicyValidationEvent, None>.FromT1(new None());
|
return handler != null ? OneOf.OneOf<IPolicyValidationEvent, None>.FromT0(handler) : OneOf.OneOf<IPolicyValidationEvent, None>.FromT1(new None());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -425,7 +411,7 @@ public class VNextSavePolicyCommandTests
|
|||||||
|
|
||||||
return new SutProvider<VNextSavePolicyCommand>()
|
return new SutProvider<VNextSavePolicyCommand>()
|
||||||
.WithFakeTimeProvider()
|
.WithFakeTimeProvider()
|
||||||
.SetDependency(policyDependencyEvents ?? [])
|
.SetDependency(handlers)
|
||||||
.SetDependency(policyEventHandlerFactory)
|
.SetDependency(policyEventHandlerFactory)
|
||||||
.Create();
|
.Create();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user