2023-11-23 07:07:37 +10:00
using Bit.Core.AdminConsole.Entities ;
using Bit.Core.AdminConsole.Enums ;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies ;
using Bit.Core.AdminConsole.Repositories ;
using Bit.Core.Auth.Enums ;
2023-05-10 12:52:08 -07:00
using Bit.Core.Auth.Repositories ;
2024-08-08 15:43:45 +01:00
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces ;
2023-04-14 13:25:56 -04:00
using Bit.Core.Entities ;
2020-10-20 02:48:10 -04:00
using Bit.Core.Enums ;
2020-01-15 09:19:28 -05:00
using Bit.Core.Exceptions ;
2023-05-12 08:22:19 +01:00
using Bit.Core.Models.Data.Organizations.OrganizationUsers ;
2020-01-15 09:19:28 -05:00
using Bit.Core.Repositories ;
2023-11-23 07:07:37 +10:00
using Bit.Core.Services ;
2023-05-12 08:22:19 +01:00
using Bit.Core.Settings ;
2020-01-15 09:19:28 -05:00
2023-11-23 07:07:37 +10:00
namespace Bit.Core.AdminConsole.Services.Implementations ;
2022-08-29 16:06:55 -04:00
2020-01-15 09:19:28 -05:00
public class PolicyService : IPolicyService
{
2023-08-25 11:22:16 +01:00
private readonly IApplicationCacheService _applicationCacheService ;
2020-01-15 09:19:28 -05:00
private readonly IEventService _eventService ;
private readonly IOrganizationRepository _organizationRepository ;
private readonly IOrganizationUserRepository _organizationUserRepository ;
private readonly IPolicyRepository _policyRepository ;
private readonly ISsoConfigRepository _ssoConfigRepository ;
2020-02-27 09:30:04 -05:00
private readonly IMailService _mailService ;
2023-05-12 08:22:19 +01:00
private readonly GlobalSettings _globalSettings ;
2024-08-08 15:43:45 +01:00
private readonly IFeatureService _featureService ;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery ;
2022-08-29 16:06:55 -04:00
2020-01-15 09:19:28 -05:00
public PolicyService (
2023-08-25 11:22:16 +01:00
IApplicationCacheService applicationCacheService ,
2020-01-15 09:19:28 -05:00
IEventService eventService ,
2020-02-27 09:30:04 -05:00
IOrganizationRepository organizationRepository ,
IOrganizationUserRepository organizationUserRepository ,
IPolicyRepository policyRepository ,
ISsoConfigRepository ssoConfigRepository ,
2023-05-12 08:22:19 +01:00
IMailService mailService ,
2024-08-08 15:43:45 +01:00
GlobalSettings globalSettings ,
IFeatureService featureService ,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery )
2020-01-15 09:19:28 -05:00
{
2023-08-25 11:22:16 +01:00
_applicationCacheService = applicationCacheService ;
2020-01-15 09:19:28 -05:00
_eventService = eventService ;
_organizationRepository = organizationRepository ;
_organizationUserRepository = organizationUserRepository ;
_policyRepository = policyRepository ;
2021-11-09 16:37:32 +01:00
_ssoConfigRepository = ssoConfigRepository ;
2020-02-27 09:30:04 -05:00
_mailService = mailService ;
2023-05-12 08:22:19 +01:00
_globalSettings = globalSettings ;
2024-08-08 15:43:45 +01:00
_featureService = featureService ;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery ;
2022-08-29 16:06:55 -04:00
}
2020-01-15 09:19:28 -05:00
2020-02-27 09:30:04 -05:00
public async Task SaveAsync ( Policy policy , IUserService userService , IOrganizationService organizationService ,
2020-02-19 14:56:16 -05:00
Guid ? savingUserId )
2022-08-29 16:06:55 -04:00
{
2020-02-27 09:30:04 -05:00
var org = await _organizationRepository . GetByIdAsync ( policy . OrganizationId ) ;
if ( org = = null )
2020-01-15 09:19:28 -05:00
{
throw new BadRequestException ( "Organization not found" ) ;
}
2020-02-19 14:56:16 -05:00
if ( ! org . UsePolicies )
2020-01-15 09:19:28 -05:00
{
2021-11-15 19:25:10 +10:00
throw new BadRequestException ( "This organization cannot use policies." ) ;
2020-12-16 16:02:54 -06:00
}
2021-12-16 15:35:09 +01:00
2024-04-19 10:53:24 -05:00
// 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 ) ;
2022-04-22 08:13:02 +10:00
2020-01-20 09:02:41 -05:00
var now = DateTime . UtcNow ;
2020-03-27 14:36:37 -04:00
if ( policy . Id = = default ( Guid ) )
2022-08-29 15:53:48 -04:00
{
2020-01-20 09:02:41 -05:00
policy . CreationDate = now ;
2022-08-29 15:53:48 -04:00
}
2024-04-19 10:53:24 -05:00
policy . RevisionDate = now ;
// We can exit early for disable operations, because they are
// simpler.
if ( ! policy . Enabled )
2022-08-29 16:06:55 -04:00
{
2024-04-19 10:53:24 -05:00
await SetPolicyConfiguration ( policy ) ;
return ;
2020-01-15 09:19:28 -05:00
}
2024-04-19 10:53:24 -05:00
2024-08-08 15:43:45 +01:00
if ( _featureService . IsEnabled ( FeatureFlagKeys . MembersTwoFAQueryOptimization ) )
{
await EnablePolicy_vNext ( policy , org , organizationService , savingUserId ) ;
return ;
}
2024-04-19 10:53:24 -05:00
await EnablePolicy ( policy , org , userService , organizationService , savingUserId ) ;
return ;
2022-08-29 16:06:55 -04:00
}
2021-11-15 19:25:10 +10:00
2023-04-17 07:35:47 -07:00
public async Task < MasterPasswordPolicyData > GetMasterPasswordPolicyForUserAsync ( User user )
{
var policies = ( await _policyRepository . GetManyByUserIdAsync ( user . Id ) )
. Where ( p = > p . Type = = PolicyType . MasterPassword & & p . Enabled )
. ToList ( ) ;
if ( ! policies . Any ( ) )
{
return null ;
}
var enforcedOptions = new MasterPasswordPolicyData ( ) ;
foreach ( var policy in policies )
{
enforcedOptions . CombineWith ( policy . GetDataModel < MasterPasswordPolicyData > ( ) ) ;
}
return enforcedOptions ;
}
2023-05-12 08:22:19 +01:00
public async Task < ICollection < OrganizationUserPolicyDetails > > GetPoliciesApplicableToUserAsync ( Guid userId , PolicyType policyType , OrganizationUserStatusType minStatus = OrganizationUserStatusType . Accepted )
{
var result = await QueryOrganizationUserPolicyDetailsAsync ( userId , policyType , minStatus ) ;
return result . ToList ( ) ;
}
public async Task < bool > AnyPoliciesApplicableToUserAsync ( Guid userId , PolicyType policyType , OrganizationUserStatusType minStatus = OrganizationUserStatusType . Accepted )
{
var result = await QueryOrganizationUserPolicyDetailsAsync ( userId , policyType , minStatus ) ;
return result . Any ( ) ;
}
2023-08-16 13:42:09 +10:00
private async Task < IEnumerable < OrganizationUserPolicyDetails > > QueryOrganizationUserPolicyDetailsAsync ( Guid userId , PolicyType policyType , OrganizationUserStatusType minStatus = OrganizationUserStatusType . Accepted )
2023-05-12 08:22:19 +01:00
{
2023-08-16 13:42:09 +10:00
var organizationUserPolicyDetails = await _organizationUserRepository . GetByUserIdWithPolicyDetailsAsync ( userId , policyType ) ;
2023-05-12 08:22:19 +01:00
var excludedUserTypes = GetUserTypesExcludedFromPolicy ( policyType ) ;
2023-08-25 11:22:16 +01:00
var orgAbilities = await _applicationCacheService . GetOrganizationAbilitiesAsync ( ) ;
2023-08-16 13:42:09 +10:00
return organizationUserPolicyDetails . Where ( o = >
2023-08-25 11:22:16 +01:00
( ! orgAbilities . ContainsKey ( o . OrganizationId ) | | orgAbilities [ o . OrganizationId ] . UsePolicies ) & &
2023-05-12 08:22:19 +01:00
o . PolicyEnabled & &
! excludedUserTypes . Contains ( o . OrganizationUserType ) & &
o . OrganizationUserStatus > = minStatus & &
! o . IsProvider ) ;
}
2023-08-16 13:42:09 +10:00
private OrganizationUserType [ ] GetUserTypesExcludedFromPolicy ( PolicyType policyType )
2023-05-12 08:22:19 +01:00
{
switch ( policyType )
{
case PolicyType . MasterPassword :
return Array . Empty < OrganizationUserType > ( ) ;
case PolicyType . RequireSso :
// If 'EnforceSsoPolicyForAllUsers' is set to true then SSO policy applies to all user types otherwise it does not apply to Owner or Admin
if ( _globalSettings . Sso . EnforceSsoPolicyForAllUsers )
{
return Array . Empty < OrganizationUserType > ( ) ;
}
break ;
}
return new [ ] { OrganizationUserType . Owner , OrganizationUserType . Admin } ;
}
2021-11-15 19:25:10 +10:00
private async Task DependsOnSingleOrgAsync ( Organization org )
2022-08-29 16:06:55 -04:00
{
2021-11-15 19:25:10 +10:00
var singleOrg = await _policyRepository . GetByOrganizationIdTypeAsync ( org . Id , PolicyType . SingleOrg ) ;
if ( singleOrg ? . Enabled ! = true )
{
throw new BadRequestException ( "Single Organization policy not enabled." ) ;
}
2022-08-29 16:06:55 -04:00
}
2021-11-15 19:25:10 +10:00
private async Task RequiredBySsoAsync ( Organization org )
2022-08-29 16:06:55 -04:00
{
2021-11-15 19:25:10 +10:00
var requireSso = await _policyRepository . GetByOrganizationIdTypeAsync ( org . Id , PolicyType . RequireSso ) ;
if ( requireSso ? . Enabled = = true )
{
throw new BadRequestException ( "Single Sign-On Authentication policy is enabled." ) ;
}
2022-08-29 16:06:55 -04:00
}
2021-11-15 19:25:10 +10:00
private async Task RequiredByKeyConnectorAsync ( Organization org )
{
var ssoConfig = await _ssoConfigRepository . GetByOrganizationIdAsync ( org . Id ) ;
2023-05-10 12:52:08 -07:00
if ( ssoConfig ? . GetData ( ) ? . MemberDecryptionType = = MemberDecryptionType . KeyConnector )
2022-08-29 15:53:48 -04:00
{
2021-11-15 19:25:10 +10:00
throw new BadRequestException ( "Key Connector is enabled." ) ;
}
2022-08-29 16:06:55 -04:00
}
2021-11-15 19:25:10 +10:00
2023-07-18 08:00:49 -07:00
private async Task RequiredByAccountRecoveryAsync ( Organization org )
{
var requireSso = await _policyRepository . GetByOrganizationIdTypeAsync ( org . Id , PolicyType . ResetPassword ) ;
if ( requireSso ? . Enabled = = true )
{
throw new BadRequestException ( "Account recovery policy is enabled." ) ;
}
}
2021-11-15 19:25:10 +10:00
private async Task RequiredByVaultTimeoutAsync ( Organization org )
2022-08-29 16:06:55 -04:00
{
2021-11-15 19:25:10 +10:00
var vaultTimeout = await _policyRepository . GetByOrganizationIdTypeAsync ( org . Id , PolicyType . MaximumVaultTimeout ) ;
if ( vaultTimeout ? . Enabled = = true )
{
throw new BadRequestException ( "Maximum Vault Timeout policy is enabled." ) ;
}
2020-01-15 09:19:28 -05:00
}
2023-03-10 12:52:50 -05:00
2023-06-07 09:56:31 +01:00
private async Task RequiredBySsoTrustedDeviceEncryptionAsync ( Organization org )
{
var ssoConfig = await _ssoConfigRepository . GetByOrganizationIdAsync ( org . Id ) ;
if ( ssoConfig ? . GetData ( ) ? . MemberDecryptionType = = MemberDecryptionType . TrustedDeviceEncryption )
{
throw new BadRequestException ( "Trusted device encryption is on and requires this policy." ) ;
}
}
2024-04-19 10:53:24 -05:00
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 )
{
2024-08-08 15:43:45 +01:00
var orgUsers = await _organizationUserRepository . GetManyDetailsByOrganizationAsync ( policy . OrganizationId ) ;
2024-04-19 10:53:24 -05:00
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 (
2024-08-08 15:43:45 +01:00
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 ) ;
}
private async Task EnablePolicy_vNext ( Policy policy , Organization org , IOrganizationService organizationService , Guid ? savingUserId )
{
var currentPolicy = await _policyRepository . GetByIdAsync ( policy . Id ) ;
if ( ! currentPolicy ? . Enabled ? ? true )
{
var orgUsers = await _organizationUserRepository . GetManyDetailsByOrganizationAsync ( policy . 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 ) ;
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 ) )
{
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 organizationService . DeleteUserAsync ( policy . OrganizationId , orgUser . Id ,
savingUserId ) ;
await _mailService . SendOrganizationUserRemovedForPolicyTwoStepEmailAsync (
org . DisplayName ( ) , orgUser . Email ) ;
}
}
break ;
case PolicyType . SingleOrg :
var userOrgs = await _organizationUserRepository . GetManyByManyUsersAsync (
2024-04-19 10:53:24 -05:00
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 ) ;
}
2020-01-15 09:19:28 -05:00
}