diff --git a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs index cd254f615c..38cd4f838f 100644 --- a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs +++ b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs @@ -59,50 +59,11 @@ public class PolicyService : IPolicyService throw new BadRequestException("This organization cannot use policies."); } - // Handle dependent policy checks - 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()?.AutoEnrollEnabled == false) - { - await RequiredBySsoTrustedDeviceEncryptionAsync(org); - } - - if (policy.Enabled) - { - await DependsOnSingleOrgAsync(org); - } - break; - - case PolicyType.MaximumVaultTimeout: - if (policy.Enabled) - { - await DependsOnSingleOrgAsync(org); - } - break; - } + // FIXME: This method will throw a bunch of errors based on if the + // policy that is being applied requires some other policy that is + // not enabled. It may be advisable to refactor this into a domain + // object and get this kind of stuff out of the service. + await HandleDependentPoliciesAsync(policy, org); var now = DateTime.UtcNow; if (policy.Id == default(Guid)) @@ -110,62 +71,18 @@ public class PolicyService : IPolicyService policy.CreationDate = now; } - if (policy.Enabled) - { - 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; - } - } - } policy.RevisionDate = now; - await _policyRepository.UpsertAsync(policy); - await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated); + + // We can exit early for disable operations, because they are + // simpler. + if (!policy.Enabled) + { + await SetPolicyConfiguration(policy); + return; + } + + await EnablePolicy(policy, org, userService, organizationService, savingUserId); + return; } public async Task GetMasterPasswordPolicyForUserAsync(User user) @@ -285,4 +202,113 @@ public class PolicyService : IPolicyService 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()?.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); + } }