diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyDefinition.cs index e1c759ae5..5b1229ff7 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyDefinition.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyDefinition.cs @@ -15,7 +15,7 @@ public interface IPolicyDefinition /// /// PolicyTypes that must be enabled before this policy can be enabled, if any. /// - public IEnumerable RequiredPolicies { get; } + public IEnumerable RequiredPolicies => []; /// /// Validates a policy before saving it. diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ActivateAutofillPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ActivateAutofillPolicyDefinition.cs new file mode 100644 index 000000000..5044b1c3d --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ActivateAutofillPolicyDefinition.cs @@ -0,0 +1,8 @@ +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class ActivateAutofillPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.ActivateAutofill; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/AutomaticAppLogInPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/AutomaticAppLogInPolicyDefinition.cs new file mode 100644 index 000000000..ed8ddd34b --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/AutomaticAppLogInPolicyDefinition.cs @@ -0,0 +1,8 @@ +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class AutomaticAppLogInPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.AutomaticAppLogIn; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisablePersonalVaultExportPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisablePersonalVaultExportPolicyDefinition.cs new file mode 100644 index 000000000..bd758aa32 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisablePersonalVaultExportPolicyDefinition.cs @@ -0,0 +1,8 @@ +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class DisablePersonalVaultExportPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.DisablePersonalVaultExport; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisableSendPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisableSendPolicyDefinition.cs new file mode 100644 index 000000000..bd58aca06 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/DisableSendPolicyDefinition.cs @@ -0,0 +1,10 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class DisableSendPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.DisableSend; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MasterPasswordPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MasterPasswordPolicyDefinition.cs new file mode 100644 index 000000000..380b82777 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MasterPasswordPolicyDefinition.cs @@ -0,0 +1,10 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class MasterPasswordPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.MasterPassword; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MaximumVaultTimeoutPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MaximumVaultTimeoutPolicyDefinition.cs new file mode 100644 index 000000000..5b851c3e1 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/MaximumVaultTimeoutPolicyDefinition.cs @@ -0,0 +1,9 @@ +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class MaximumVaultTimeoutPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.MaximumVaultTimeout; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PasswordGeneratorPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PasswordGeneratorPolicyDefinition.cs new file mode 100644 index 000000000..47200293f --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PasswordGeneratorPolicyDefinition.cs @@ -0,0 +1,10 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class PasswordGeneratorPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.PasswordGenerator; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PersonalOwnershipPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PersonalOwnershipPolicyDefinition.cs new file mode 100644 index 000000000..b874f16cf --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PersonalOwnershipPolicyDefinition.cs @@ -0,0 +1,10 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class PersonalOwnershipPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.PersonalOwnership; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/RequireSsoPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/RequireSsoPolicyDefinition.cs new file mode 100644 index 000000000..02bfd7d4c --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/RequireSsoPolicyDefinition.cs @@ -0,0 +1,11 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class RequireSsoPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.RequireSso; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ResetPasswordPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ResetPasswordPolicyDefinition.cs new file mode 100644 index 000000000..7f900e638 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/ResetPasswordPolicyDefinition.cs @@ -0,0 +1,36 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Repositories; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class ResetPasswordPolicyDefinition : IPolicyDefinition +{ + private readonly ISsoConfigRepository _ssoConfigRepository; + public PolicyType Type => PolicyType.ResetPassword; + public IEnumerable RequiredPolicies => [PolicyType.SingleOrg]; + + ResetPasswordPolicyDefinition(ISsoConfigRepository ssoConfigRepository) + { + _ssoConfigRepository = ssoConfigRepository; + } + + public async Task ValidateAsync(Policy? currentPolicy, Policy modifiedPolicy) + { + if (modifiedPolicy is not { Enabled:true } || + modifiedPolicy.GetDataModel()?.AutoEnrollEnabled == false) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(modifiedPolicy.OrganizationId); + if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption) + { + return "Trusted device encryption is on and requires this policy."; + } + } + + return null; + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SendOptionsPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SendOptionsPolicyDefinition.cs new file mode 100644 index 000000000..73cd247bb --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SendOptionsPolicyDefinition.cs @@ -0,0 +1,10 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class SendOptionsPolicyDefinition : IPolicyDefinition +{ + public PolicyType Type => PolicyType.SendOptions; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SingleOrgPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SingleOrgPolicyDefinition.cs index a77926ede..289638875 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SingleOrgPolicyDefinition.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/SingleOrgPolicyDefinition.cs @@ -15,7 +15,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; public class SingleOrgPolicyDefinition : IPolicyDefinition { public PolicyType Type => PolicyType.SingleOrg; - public IEnumerable RequiredPolicies => Array.Empty(); private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IMailService _mailService; @@ -39,7 +38,7 @@ public class SingleOrgPolicyDefinition : IPolicyDefinition public async Task OnSaveSideEffectsAsync(Policy? currentPolicy, Policy modifiedPolicy) { - if (currentPolicy is null or { Enabled: false } && modifiedPolicy is { Enabled: true }) + if (currentPolicy is not { Enabled: true } && modifiedPolicy is { Enabled: true }) { await RemoveNonCompliantUsersAsync(modifiedPolicy.OrganizationId); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/TwoFactorAuthenticationPolicyDefinition.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/TwoFactorAuthenticationPolicyDefinition.cs new file mode 100644 index 000000000..59399aaf0 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/TwoFactorAuthenticationPolicyDefinition.cs @@ -0,0 +1,82 @@ +#nullable enable + +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class TwoFactorAuthenticationPolicyDefinition : IPolicyDefinition +{ + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IMailService _mailService; + private readonly IOrganizationRepository _organizationRepository; + private readonly ICurrentContext _currentContext; + private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; + private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; + + public PolicyType Type => PolicyType.TwoFactorAuthentication; + + public TwoFactorAuthenticationPolicyDefinition( + IOrganizationUserRepository organizationUserRepository, + IMailService mailService, + IOrganizationRepository organizationRepository, + ICurrentContext currentContext, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IRemoveOrganizationUserCommand removeOrganizationUserCommand) + { + _organizationUserRepository = organizationUserRepository; + _mailService = mailService; + _organizationRepository = organizationRepository; + _currentContext = currentContext; + _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; + _removeOrganizationUserCommand = removeOrganizationUserCommand; + } + + public async Task OnSaveSideEffectsAsync(Policy? currentPolicy, Policy modifiedPolicy) + { + if (currentPolicy is not { Enabled: true } && modifiedPolicy is { Enabled: true }) + { + await RemoveNonCompliantUsersAsync(modifiedPolicy.OrganizationId); + } + } + + private async Task RemoveNonCompliantUsersAsync(Guid organizationId) + { + var org = await _organizationRepository.GetByIdAsync(organizationId); + var savingUserId = _currentContext.UserId; + + var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId); + var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers); + var removableOrgUsers = orgUsers.Where(ou => + ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked && + ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin && + ou.UserId != savingUserId); + + // Reorder by HasMasterPassword to prioritize checking users without a master if they have 2FA enabled + foreach (var orgUser in removableOrgUsers.OrderBy(ou => ou.HasMasterPassword)) + { + var userTwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == orgUser.Id) + .twoFactorIsEnabled; + if (!userTwoFactorEnabled) + { + 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 _removeOrganizationUserCommand.RemoveUserAsync(organizationId, orgUser.Id, + savingUserId); + await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync( + org!.DisplayName(), orgUser.Email); + } + } + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 5c3ec7f03..fe02bf25e 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -11,6 +11,19 @@ public static class PolicyServiceCollectionExtensions { services.AddScoped(); services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } }