1
0
mirror of https://github.com/bitwarden/server synced 2025-12-23 03:33:35 +00:00

[PM-18239] Master password policy requirement (#5936)

* wip

* initial implementation

* add tests

* more tests, fix policy Enabled

* remove exempt statuses

* test EnforcedOptions is populated

* clean up, add test

* fix test, add json attributes for deserialization

* fix attribute casing

* fix test

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
Brandon Treston
2025-07-25 10:14:16 -04:00
committed by GitHub
parent c503ecbefc
commit 571111e897
6 changed files with 207 additions and 10 deletions

View File

@@ -1,20 +1,28 @@
namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using System.Text.Json.Serialization;
namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
public class MasterPasswordPolicyData : IPolicyDataModel
{
[JsonPropertyName("minComplexity")]
public int? MinComplexity { get; set; }
[JsonPropertyName("minLength")]
public int? MinLength { get; set; }
[JsonPropertyName("requireLower")]
public bool? RequireLower { get; set; }
[JsonPropertyName("requireUpper")]
public bool? RequireUpper { get; set; }
[JsonPropertyName("requireNumbers")]
public bool? RequireNumbers { get; set; }
[JsonPropertyName("requireSpecial")]
public bool? RequireSpecial { get; set; }
[JsonPropertyName("enforceOnLogin")]
public bool? EnforceOnLogin { get; set; }
/// <summary>
/// Combine the other policy data with this instance, taking the most secure options
/// </summary>
/// <param name="other">The other policy instance to combine with this</param>
public void CombineWith(MasterPasswordPolicyData other)
public void CombineWith(MasterPasswordPolicyData? other)
{
if (other == null)
{

View File

@@ -0,0 +1,55 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
/// <summary>
/// Policy requirements for the Master Password Requirements policy.
/// </summary>
public class MasterPasswordPolicyRequirement : IPolicyRequirement
{
/// <summary>
/// Indicates whether MasterPassword requirements are enabled for the user.
/// </summary>
public bool Enabled { get; init; }
/// <summary>
/// Master Password Policy data model associated with this Policy
/// </summary>
public MasterPasswordPolicyData? EnforcedOptions { get; init; }
}
public class MasterPasswordPolicyRequirementFactory : BasePolicyRequirementFactory<MasterPasswordPolicyRequirement>
{
public override PolicyType PolicyType => PolicyType.MasterPassword;
protected override bool ExemptProviders => false;
protected override IEnumerable<OrganizationUserType> ExemptRoles => [];
protected override IEnumerable<OrganizationUserStatusType> ExemptStatuses =>
[OrganizationUserStatusType.Accepted,
OrganizationUserStatusType.Invited,
OrganizationUserStatusType.Revoked,
];
public override MasterPasswordPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails)
{
var result = policyDetails
.Select(p => p.GetDataModel<MasterPasswordPolicyData>())
.Aggregate(
new MasterPasswordPolicyRequirement(),
(result, data) =>
{
data.CombineWith(result.EnforcedOptions);
return new MasterPasswordPolicyRequirement
{
Enabled = true,
EnforcedOptions = data
};
});
return result;
}
}

View File

@@ -3,6 +3,8 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -19,21 +21,39 @@ public class PolicyService : IPolicyService
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IPolicyRepository _policyRepository;
private readonly GlobalSettings _globalSettings;
private readonly IFeatureService _featureService;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
public PolicyService(
IApplicationCacheService applicationCacheService,
IOrganizationUserRepository organizationUserRepository,
IPolicyRepository policyRepository,
GlobalSettings globalSettings)
GlobalSettings globalSettings,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery)
{
_applicationCacheService = applicationCacheService;
_organizationUserRepository = organizationUserRepository;
_policyRepository = policyRepository;
_globalSettings = globalSettings;
_featureService = featureService;
_policyRequirementQuery = policyRequirementQuery;
}
public async Task<MasterPasswordPolicyData> GetMasterPasswordPolicyForUserAsync(User user)
{
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
{
var masterPaswordPolicy = (await _policyRequirementQuery.GetAsync<MasterPasswordPolicyRequirement>(user.Id));
if (!masterPaswordPolicy.Enabled)
{
return null;
}
return masterPaswordPolicy.EnforcedOptions;
}
var policies = (await _policyRepository.GetManyByUserIdAsync(user.Id))
.Where(p => p.Type == PolicyType.MasterPassword && p.Enabled)
.ToList();
@@ -51,6 +71,7 @@ public class PolicyService : IPolicyService
}
return enforcedOptions;
}
public async Task<ICollection<OrganizationUserPolicyDetails>> GetPoliciesApplicableToUserAsync(Guid userId, PolicyType policyType, OrganizationUserStatusType minStatus = OrganizationUserStatusType.Accepted)