mirror of
https://github.com/bitwarden/server
synced 2025-12-27 21:53:24 +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,
|
||||
IEventService eventService,
|
||||
IPolicyRepository policyRepository,
|
||||
IEnumerable<IEnforceDependentPoliciesEvent> policyValidationEventHandlers,
|
||||
IEnumerable<IPolicyUpdateEvent> policyUpdateEventHandlers,
|
||||
TimeProvider timeProvider,
|
||||
IPolicyEventHandlerFactory policyEventHandlerFactory)
|
||||
: 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)
|
||||
{
|
||||
@@ -112,32 +98,26 @@ public class VNextSavePolicyCommand(
|
||||
Policy? currentPolicy,
|
||||
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(
|
||||
validator =>
|
||||
{
|
||||
var isCurrentlyEnabled = currentPolicy?.Enabled == true;
|
||||
|
||||
switch (policyUpdateRequest.Enabled)
|
||||
{
|
||||
case true when !isCurrentlyEnabled:
|
||||
ValidateEnablingRequirements(validator, savedPoliciesDict);
|
||||
return;
|
||||
case false when isCurrentlyEnabled:
|
||||
ValidateDisablingRequirements(validator, policyUpdateRequest.Type, savedPoliciesDict);
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => { });
|
||||
if (isBeingEnabled)
|
||||
{
|
||||
ValidateEnablingRequirements(policyUpdateRequest.Type, savedPoliciesDict);
|
||||
}
|
||||
else if (isBeingDisabled)
|
||||
{
|
||||
ValidateDisablingRequirements(policyUpdateRequest.Type, savedPoliciesDict);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateDisablingRequirements(
|
||||
IEnforceDependentPoliciesEvent validator,
|
||||
PolicyType policyType,
|
||||
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
||||
{
|
||||
var dependentPolicyTypes = _policyValidationEvents.Values
|
||||
var dependentPolicyTypes = policyUpdateEventHandlers
|
||||
.OfType<IEnforceDependentPoliciesEvent>()
|
||||
.Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyType))
|
||||
.Select(otherValidator => otherValidator.Type)
|
||||
.Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) &&
|
||||
@@ -147,24 +127,31 @@ public class VNextSavePolicyCommand(
|
||||
switch (dependentPolicyTypes)
|
||||
{
|
||||
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 }:
|
||||
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(
|
||||
IEnforceDependentPoliciesEvent validator,
|
||||
private void ValidateEnablingRequirements(
|
||||
PolicyType policyType,
|
||||
Dictionary<PolicyType, Policy> savedPoliciesDict)
|
||||
{
|
||||
var missingRequiredPolicyTypes = validator.RequiredPolicies
|
||||
.Where(requiredPolicyType => savedPoliciesDict.GetValueOrDefault(requiredPolicyType) is not { Enabled: true })
|
||||
.ToList();
|
||||
var result = policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(policyType);
|
||||
|
||||
if (missingRequiredPolicyTypes.Count != 0)
|
||||
{
|
||||
throw new BadRequestException($"Turn on the {missingRequiredPolicyTypes.First().GetName()} policy because it is required for the {validator.Type.GetName()} policy.");
|
||||
}
|
||||
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(
|
||||
|
||||
@@ -22,8 +22,10 @@ public static class PolicyServiceCollectionExtensions
|
||||
services.AddPolicyValidators();
|
||||
services.AddPolicyRequirements();
|
||||
services.AddPolicySideEffects();
|
||||
services.AddPolicyUpdateEvents();
|
||||
}
|
||||
|
||||
[Obsolete("Use AddPolicyUpdateEvents instead.")]
|
||||
private static void AddPolicyValidators(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPolicyValidator, TwoFactorAuthenticationPolicyValidator>();
|
||||
@@ -34,11 +36,23 @@ public static class PolicyServiceCollectionExtensions
|
||||
services.AddScoped<IPolicyValidator, FreeFamiliesForEnterprisePolicyValidator>();
|
||||
}
|
||||
|
||||
[Obsolete("Use AddPolicyUpdateEvents instead.")]
|
||||
private static void AddPolicySideEffects(this IServiceCollection services)
|
||||
{
|
||||
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)
|
||||
{
|
||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
|
||||
|
||||
@@ -3,6 +3,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.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
@@ -12,11 +13,16 @@ public class FreeFamiliesForEnterprisePolicyValidator(
|
||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||
IMailService mailService,
|
||||
IOrganizationRepository organizationRepository)
|
||||
: IPolicyValidator
|
||||
: IPolicyValidator, IOnPolicyPreUpdateEvent
|
||||
{
|
||||
public PolicyType Type => PolicyType.FreeFamiliesSponsorshipPolicy;
|
||||
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)
|
||||
{
|
||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
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
|
||||
public class MaximumVaultTimeoutPolicyValidator : IPolicyValidator, IEnforceDependentPoliciesEvent
|
||||
{
|
||||
public PolicyType Type => PolicyType.MaximumVaultTimeout;
|
||||
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
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(
|
||||
IPolicyRepository policyRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
IEnumerable<IPolicyRequirementFactory<IPolicyRequirement>> factories,
|
||||
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(
|
||||
SavePolicyModel policyRequest,
|
||||
Policy postUpdatedPolicy,
|
||||
@@ -68,5 +76,4 @@ public class OrganizationDataOwnershipPolicyValidator(
|
||||
userOrgIds,
|
||||
defaultCollectionName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
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;
|
||||
|
||||
public class RequireSsoPolicyValidator : IPolicyValidator
|
||||
public class RequireSsoPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent
|
||||
{
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
|
||||
@@ -20,6 +21,11 @@ public class RequireSsoPolicyValidator : IPolicyValidator
|
||||
public PolicyType Type => PolicyType.RequireSso;
|
||||
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)
|
||||
{
|
||||
if (policyUpdate is not { Enabled: true })
|
||||
|
||||
@@ -4,12 +4,13 @@ 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;
|
||||
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;
|
||||
|
||||
public class ResetPasswordPolicyValidator : IPolicyValidator
|
||||
public class ResetPasswordPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IEnforceDependentPoliciesEvent
|
||||
{
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
public PolicyType Type => PolicyType.ResetPassword;
|
||||
@@ -20,6 +21,11 @@ public class ResetPasswordPolicyValidator : IPolicyValidator
|
||||
_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)
|
||||
{
|
||||
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.Requests;
|
||||
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;
|
||||
using Bit.Core.Context;
|
||||
@@ -17,7 +18,7 @@ using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
public class SingleOrgPolicyValidator : IPolicyValidator
|
||||
public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent, IOnPolicyPreUpdateEvent
|
||||
{
|
||||
public PolicyType Type => PolicyType.SingleOrg;
|
||||
private const string OrganizationNotFoundErrorMessage = "Organization not found.";
|
||||
@@ -57,6 +58,16 @@ public class SingleOrgPolicyValidator : IPolicyValidator
|
||||
|
||||
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)
|
||||
{
|
||||
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.Requests;
|
||||
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.Context;
|
||||
using Bit.Core.Enums;
|
||||
@@ -16,7 +17,7 @@ using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
||||
public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator, IOnPolicyPreUpdateEvent
|
||||
{
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IMailService _mailService;
|
||||
@@ -46,6 +47,11 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
|
||||
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
||||
}
|
||||
|
||||
public async Task ExecutePreUpsertSideEffectAsync(SavePolicyModel policyRequest, Policy? currentPolicy)
|
||||
{
|
||||
await OnSaveSideEffectsAsync(policyRequest.PolicyUpdate, currentPolicy);
|
||||
}
|
||||
|
||||
public async Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||
{
|
||||
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
|
||||
|
||||
Reference in New Issue
Block a user