mirror of
https://github.com/bitwarden/server.git
synced 2025-05-16 17:15:40 -05:00
Refactor PolicyService.SaveAsync()
(#4001)
* Move dependent policy checks to a dedicated function * Invert conditional * Extract enable logic
This commit is contained in:
parent
821f7620b6
commit
87f710803d
@ -59,50 +59,11 @@ public class PolicyService : IPolicyService
|
|||||||
throw new BadRequestException("This organization cannot use policies.");
|
throw new BadRequestException("This organization cannot use policies.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle dependent policy checks
|
// FIXME: This method will throw a bunch of errors based on if the
|
||||||
switch (policy.Type)
|
// policy that is being applied requires some other policy that is
|
||||||
{
|
// not enabled. It may be advisable to refactor this into a domain
|
||||||
case PolicyType.SingleOrg:
|
// object and get this kind of stuff out of the service.
|
||||||
if (!policy.Enabled)
|
await HandleDependentPoliciesAsync(policy, org);
|
||||||
{
|
|
||||||
await RequiredBySsoAsync(org);
|
|
||||||
await RequiredByVaultTimeoutAsync(org);
|
|
||||||
await RequiredByKeyConnectorAsync(org);
|
|
||||||
await RequiredByAccountRecoveryAsync(org);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PolicyType.RequireSso:
|
|
||||||
if (policy.Enabled)
|
|
||||||
{
|
|
||||||
await DependsOnSingleOrgAsync(org);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await RequiredByKeyConnectorAsync(org);
|
|
||||||
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PolicyType.ResetPassword:
|
|
||||||
if (!policy.Enabled || policy.GetDataModel<ResetPasswordDataModel>()?.AutoEnrollEnabled == false)
|
|
||||||
{
|
|
||||||
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policy.Enabled)
|
|
||||||
{
|
|
||||||
await DependsOnSingleOrgAsync(org);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PolicyType.MaximumVaultTimeout:
|
|
||||||
if (policy.Enabled)
|
|
||||||
{
|
|
||||||
await DependsOnSingleOrgAsync(org);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
if (policy.Id == default(Guid))
|
if (policy.Id == default(Guid))
|
||||||
@ -110,62 +71,18 @@ public class PolicyService : IPolicyService
|
|||||||
policy.CreationDate = now;
|
policy.CreationDate = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (policy.Enabled)
|
policy.RevisionDate = now;
|
||||||
|
|
||||||
|
// We can exit early for disable operations, because they are
|
||||||
|
// simpler.
|
||||||
|
if (!policy.Enabled)
|
||||||
{
|
{
|
||||||
var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id);
|
await SetPolicyConfiguration(policy);
|
||||||
if (!currentPolicy?.Enabled ?? true)
|
return;
|
||||||
{
|
|
||||||
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(
|
|
||||||
policy.OrganizationId);
|
|
||||||
var removableOrgUsers = orgUsers.Where(ou =>
|
|
||||||
ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked &&
|
|
||||||
ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin &&
|
|
||||||
ou.UserId != savingUserId);
|
|
||||||
switch (policy.Type)
|
|
||||||
{
|
|
||||||
case PolicyType.TwoFactorAuthentication:
|
|
||||||
// Reorder by HasMasterPassword to prioritize checking users without a master if they have 2FA enabled
|
|
||||||
foreach (var orgUser in removableOrgUsers.OrderBy(ou => ou.HasMasterPassword))
|
|
||||||
{
|
|
||||||
if (!await userService.TwoFactorIsEnabledAsync(orgUser))
|
|
||||||
{
|
|
||||||
if (!orgUser.HasMasterPassword)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(
|
|
||||||
"Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
|
await EnablePolicy(policy, org, userService, organizationService, savingUserId);
|
||||||
savingUserId);
|
return;
|
||||||
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
|
|
||||||
org.DisplayName(), orgUser.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PolicyType.SingleOrg:
|
|
||||||
var userOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(
|
|
||||||
removableOrgUsers.Select(ou => ou.UserId.Value));
|
|
||||||
foreach (var orgUser in removableOrgUsers)
|
|
||||||
{
|
|
||||||
if (userOrgs.Any(ou => ou.UserId == orgUser.UserId
|
|
||||||
&& ou.OrganizationId != org.Id
|
|
||||||
&& ou.Status != OrganizationUserStatusType.Invited))
|
|
||||||
{
|
|
||||||
await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
|
|
||||||
savingUserId);
|
|
||||||
await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(
|
|
||||||
org.DisplayName(), orgUser.Email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
policy.RevisionDate = now;
|
|
||||||
await _policyRepository.UpsertAsync(policy);
|
|
||||||
await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MasterPasswordPolicyData> GetMasterPasswordPolicyForUserAsync(User user)
|
public async Task<MasterPasswordPolicyData> GetMasterPasswordPolicyForUserAsync(User user)
|
||||||
@ -285,4 +202,113 @@ public class PolicyService : IPolicyService
|
|||||||
throw new BadRequestException("Trusted device encryption is on and requires this policy.");
|
throw new BadRequestException("Trusted device encryption is on and requires this policy.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleDependentPoliciesAsync(Policy policy, Organization org)
|
||||||
|
{
|
||||||
|
switch (policy.Type)
|
||||||
|
{
|
||||||
|
case PolicyType.SingleOrg:
|
||||||
|
if (!policy.Enabled)
|
||||||
|
{
|
||||||
|
await RequiredBySsoAsync(org);
|
||||||
|
await RequiredByVaultTimeoutAsync(org);
|
||||||
|
await RequiredByKeyConnectorAsync(org);
|
||||||
|
await RequiredByAccountRecoveryAsync(org);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PolicyType.RequireSso:
|
||||||
|
if (policy.Enabled)
|
||||||
|
{
|
||||||
|
await DependsOnSingleOrgAsync(org);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await RequiredByKeyConnectorAsync(org);
|
||||||
|
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PolicyType.ResetPassword:
|
||||||
|
if (!policy.Enabled || policy.GetDataModel<ResetPasswordDataModel>()?.AutoEnrollEnabled == false)
|
||||||
|
{
|
||||||
|
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (policy.Enabled)
|
||||||
|
{
|
||||||
|
await DependsOnSingleOrgAsync(org);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PolicyType.MaximumVaultTimeout:
|
||||||
|
if (policy.Enabled)
|
||||||
|
{
|
||||||
|
await DependsOnSingleOrgAsync(org);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetPolicyConfiguration(Policy policy)
|
||||||
|
{
|
||||||
|
await _policyRepository.UpsertAsync(policy);
|
||||||
|
await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnablePolicy(Policy policy, Organization org, IUserService userService, IOrganizationService organizationService, Guid? savingUserId)
|
||||||
|
{
|
||||||
|
var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id);
|
||||||
|
if (!currentPolicy?.Enabled ?? true)
|
||||||
|
{
|
||||||
|
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(
|
||||||
|
policy.OrganizationId);
|
||||||
|
var removableOrgUsers = orgUsers.Where(ou =>
|
||||||
|
ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked &&
|
||||||
|
ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin &&
|
||||||
|
ou.UserId != savingUserId);
|
||||||
|
switch (policy.Type)
|
||||||
|
{
|
||||||
|
case PolicyType.TwoFactorAuthentication:
|
||||||
|
// Reorder by HasMasterPassword to prioritize checking users without a master if they have 2FA enabled
|
||||||
|
foreach (var orgUser in removableOrgUsers.OrderBy(ou => ou.HasMasterPassword))
|
||||||
|
{
|
||||||
|
if (!await userService.TwoFactorIsEnabledAsync(orgUser))
|
||||||
|
{
|
||||||
|
if (!orgUser.HasMasterPassword)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(
|
||||||
|
"Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
|
||||||
|
savingUserId);
|
||||||
|
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
|
||||||
|
org.DisplayName(), orgUser.Email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PolicyType.SingleOrg:
|
||||||
|
var userOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(
|
||||||
|
removableOrgUsers.Select(ou => ou.UserId.Value));
|
||||||
|
foreach (var orgUser in removableOrgUsers)
|
||||||
|
{
|
||||||
|
if (userOrgs.Any(ou => ou.UserId == orgUser.UserId
|
||||||
|
&& ou.OrganizationId != org.Id
|
||||||
|
&& ou.Status != OrganizationUserStatusType.Invited))
|
||||||
|
{
|
||||||
|
await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
|
||||||
|
savingUserId);
|
||||||
|
await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(
|
||||||
|
org.DisplayName(), orgUser.Email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await SetPolicyConfiguration(policy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user