mirror of
https://github.com/bitwarden/server
synced 2025-12-30 07:03:42 +00:00
[PM-14439] Add PolicyRequirementQuery for enforcement logic (#5336)
* Add PolicyRequirementQuery, helpers and models in preparation for migrating domain code Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
|
||||
public interface IPolicyRequirementQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a policy requirement for a specific user.
|
||||
/// The policy requirement represents how one or more policy types should be enforced against the user.
|
||||
/// It will always return a value even if there are no policies that should be enforced.
|
||||
/// This should be used for all policy checks.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user that you need to enforce the policy against.</param>
|
||||
/// <typeparam name="T">The IPolicyRequirement that corresponds to the policy you want to enforce.</typeparam>
|
||||
Task<T> GetAsync<T>(Guid userId) where T : IPolicyRequirement;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
|
||||
public class PolicyRequirementQuery(
|
||||
IPolicyRepository policyRepository,
|
||||
IEnumerable<RequirementFactory<IPolicyRequirement>> factories)
|
||||
: IPolicyRequirementQuery
|
||||
{
|
||||
public async Task<T> GetAsync<T>(Guid userId) where T : IPolicyRequirement
|
||||
{
|
||||
var factory = factories.OfType<RequirementFactory<T>>().SingleOrDefault();
|
||||
if (factory is null)
|
||||
{
|
||||
throw new NotImplementedException("No Policy Requirement found for " + typeof(T));
|
||||
}
|
||||
|
||||
return factory(await GetPolicyDetails(userId));
|
||||
}
|
||||
|
||||
private Task<IEnumerable<PolicyDetails>> GetPolicyDetails(Guid userId) =>
|
||||
policyRepository.GetPolicyDetailsByUserId(userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the business requirements of how one or more enterprise policies will be enforced against a user.
|
||||
/// The implementation of this interface will depend on how the policies are enforced in the relevant domain.
|
||||
/// </summary>
|
||||
public interface IPolicyRequirement;
|
||||
|
||||
/// <summary>
|
||||
/// A factory function that takes a sequence of <see cref="PolicyDetails"/> and transforms them into a single
|
||||
/// <see cref="IPolicyRequirement"/> for consumption by the relevant domain. This will receive *all* policy types
|
||||
/// that may be enforced against a user; when implementing this delegate, you must filter out irrelevant policy types
|
||||
/// as well as policies that should not be enforced against a user (e.g. due to the user's role or status).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="PolicyRequirementHelpers"/> for extension methods to handle common requirements when implementing
|
||||
/// this delegate.
|
||||
/// </remarks>
|
||||
public delegate T RequirementFactory<out T>(IEnumerable<PolicyDetails> policyDetails)
|
||||
where T : IPolicyRequirement;
|
||||
@@ -0,0 +1,41 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
|
||||
public static class PolicyRequirementHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters the PolicyDetails by PolicyType. This is generally required to only get the PolicyDetails that your
|
||||
/// IPolicyRequirement relates to.
|
||||
/// </summary>
|
||||
public static IEnumerable<PolicyDetails> GetPolicyType(
|
||||
this IEnumerable<PolicyDetails> policyDetails,
|
||||
PolicyType type)
|
||||
=> policyDetails.Where(x => x.PolicyType == type);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the PolicyDetails to remove the specified user roles. This can be used to exempt
|
||||
/// owners and admins from policy enforcement.
|
||||
/// </summary>
|
||||
public static IEnumerable<PolicyDetails> ExemptRoles(
|
||||
this IEnumerable<PolicyDetails> policyDetails,
|
||||
IEnumerable<OrganizationUserType> roles)
|
||||
=> policyDetails.Where(x => !roles.Contains(x.OrganizationUserType));
|
||||
|
||||
/// <summary>
|
||||
/// Filters the PolicyDetails to remove organization users who are also provider users for the organization.
|
||||
/// This can be used to exempt provider users from policy enforcement.
|
||||
/// </summary>
|
||||
public static IEnumerable<PolicyDetails> ExemptProviders(this IEnumerable<PolicyDetails> policyDetails)
|
||||
=> policyDetails.Where(x => !x.IsProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the PolicyDetails to remove the specified organization user statuses. For example, this can be used
|
||||
/// to exempt users in the invited and revoked statuses from policy enforcement.
|
||||
/// </summary>
|
||||
public static IEnumerable<PolicyDetails> ExemptStatus(
|
||||
this IEnumerable<PolicyDetails> policyDetails, IEnumerable<OrganizationUserStatusType> status)
|
||||
=> policyDetails.Where(x => !status.Contains(x.OrganizationUserStatus));
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.Services.Implementations;
|
||||
@@ -12,7 +13,14 @@ public static class PolicyServiceCollectionExtensions
|
||||
{
|
||||
services.AddScoped<IPolicyService, PolicyService>();
|
||||
services.AddScoped<ISavePolicyCommand, SavePolicyCommand>();
|
||||
services.AddScoped<IPolicyRequirementQuery, PolicyRequirementQuery>();
|
||||
|
||||
services.AddPolicyValidators();
|
||||
services.AddPolicyRequirements();
|
||||
}
|
||||
|
||||
private static void AddPolicyValidators(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPolicyValidator, TwoFactorAuthenticationPolicyValidator>();
|
||||
services.AddScoped<IPolicyValidator, SingleOrgPolicyValidator>();
|
||||
services.AddScoped<IPolicyValidator, RequireSsoPolicyValidator>();
|
||||
@@ -20,4 +28,34 @@ public static class PolicyServiceCollectionExtensions
|
||||
services.AddScoped<IPolicyValidator, MaximumVaultTimeoutPolicyValidator>();
|
||||
services.AddScoped<IPolicyValidator, FreeFamiliesForEnterprisePolicyValidator>();
|
||||
}
|
||||
|
||||
private static void AddPolicyRequirements(this IServiceCollection services)
|
||||
{
|
||||
// Register policy requirement factories here
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to register simple policy requirements where its factory method implements CreateRequirement.
|
||||
/// This MUST be used rather than calling AddScoped directly, because it will ensure the factory method has
|
||||
/// the correct type to be injected and then identified by <see cref="PolicyRequirementQuery"/> at runtime.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The specific PolicyRequirement being registered.</typeparam>
|
||||
private static void AddPolicyRequirement<T>(this IServiceCollection serviceCollection, RequirementFactory<T> factory)
|
||||
where T : class, IPolicyRequirement
|
||||
=> serviceCollection.AddPolicyRequirement(_ => factory);
|
||||
|
||||
/// <summary>
|
||||
/// Used to register policy requirements where you need to access additional dependencies (usually to return a
|
||||
/// curried factory method).
|
||||
/// This MUST be used rather than calling AddScoped directly, because it will ensure the factory method has
|
||||
/// the correct type to be injected and then identified by <see cref="PolicyRequirementQuery"/> at runtime.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">
|
||||
/// A callback that takes IServiceProvider and returns a RequirementFactory for
|
||||
/// your policy requirement.
|
||||
/// </typeparam>
|
||||
private static void AddPolicyRequirement<T>(this IServiceCollection serviceCollection,
|
||||
Func<IServiceProvider, RequirementFactory<T>> factory)
|
||||
where T : class, IPolicyRequirement
|
||||
=> serviceCollection.AddScoped<RequirementFactory<IPolicyRequirement>>(factory);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user