2023-10-27 07:47:44 +10:00
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces ;
2024-10-16 10:33:00 +01:00
using Bit.Core.Context ;
using Bit.Core.Entities ;
2023-10-27 07:47:44 +10:00
using Bit.Core.Enums ;
2022-11-09 12:13:29 +00:00
using Bit.Core.Exceptions ;
2025-01-06 12:10:53 -05:00
using Bit.Core.Platform.Push ;
2022-10-31 09:58:21 +00:00
using Bit.Core.Repositories ;
using Bit.Core.Services ;
2023-10-27 07:47:44 +10:00
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers ;
2022-10-31 09:58:21 +00:00
2024-09-04 11:18:23 +01:00
public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
2022-10-31 09:58:21 +00:00
{
2024-10-16 10:33:00 +01:00
private readonly IDeviceRepository _deviceRepository ;
2022-10-31 09:58:21 +00:00
private readonly IOrganizationUserRepository _organizationUserRepository ;
2024-10-16 10:33:00 +01:00
private readonly IEventService _eventService ;
private readonly IPushNotificationService _pushNotificationService ;
private readonly IPushRegistrationService _pushRegistrationService ;
private readonly ICurrentContext _currentContext ;
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery ;
2025-04-08 14:38:44 -05:00
private readonly IGetOrganizationUsersClaimedStatusQuery _getOrganizationUsersClaimedStatusQuery ;
2024-11-27 12:26:42 +00:00
private readonly IFeatureService _featureService ;
private readonly TimeProvider _timeProvider ;
public const string UserNotFoundErrorMessage = "User not found." ;
public const string UsersInvalidErrorMessage = "Users invalid." ;
public const string RemoveYourselfErrorMessage = "You cannot remove yourself." ;
2025-04-03 11:35:09 -04:00
public const string RemoveOwnerByNonOwnerErrorMessage = "Only owners can remove other owners." ;
public const string RemoveAdminByCustomUserErrorMessage = "Custom users can not remove admins." ;
2024-11-27 12:26:42 +00:00
public const string RemoveLastConfirmedOwnerErrorMessage = "Organization must have at least one confirmed owner." ;
public const string RemoveClaimedAccountErrorMessage = "Cannot remove member accounts claimed by the organization. To offboard a member, revoke or delete the account." ;
2022-10-31 09:58:21 +00:00
2024-09-04 11:18:23 +01:00
public RemoveOrganizationUserCommand (
2024-10-16 10:33:00 +01:00
IDeviceRepository deviceRepository ,
2022-10-31 09:58:21 +00:00
IOrganizationUserRepository organizationUserRepository ,
2024-10-16 10:33:00 +01:00
IEventService eventService ,
IPushNotificationService pushNotificationService ,
IPushRegistrationService pushRegistrationService ,
ICurrentContext currentContext ,
2024-11-27 12:26:42 +00:00
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery ,
2025-04-08 14:38:44 -05:00
IGetOrganizationUsersClaimedStatusQuery getOrganizationUsersClaimedStatusQuery ,
2024-11-27 12:26:42 +00:00
IFeatureService featureService ,
TimeProvider timeProvider )
2022-10-31 09:58:21 +00:00
{
2024-10-16 10:33:00 +01:00
_deviceRepository = deviceRepository ;
2022-10-31 09:58:21 +00:00
_organizationUserRepository = organizationUserRepository ;
2024-10-16 10:33:00 +01:00
_eventService = eventService ;
_pushNotificationService = pushNotificationService ;
_pushRegistrationService = pushRegistrationService ;
_currentContext = currentContext ;
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery ;
2025-04-08 14:38:44 -05:00
_getOrganizationUsersClaimedStatusQuery = getOrganizationUsersClaimedStatusQuery ;
2024-11-27 12:26:42 +00:00
_featureService = featureService ;
_timeProvider = timeProvider ;
2022-10-31 09:58:21 +00:00
}
2024-11-27 12:26:42 +00:00
public async Task RemoveUserAsync ( Guid organizationId , Guid userId )
2022-11-09 12:13:29 +00:00
{
2024-11-27 12:26:42 +00:00
var organizationUser = await _organizationUserRepository . GetByOrganizationAsync ( organizationId , userId ) ;
ValidateRemoveUser ( organizationId , organizationUser ) ;
2022-11-09 12:13:29 +00:00
2024-11-27 12:26:42 +00:00
await RepositoryRemoveUserAsync ( organizationUser , deletingUserId : null , eventSystemUser : null ) ;
2024-10-16 10:33:00 +01:00
await _eventService . LogOrganizationUserEventAsync ( organizationUser , EventType . OrganizationUser_Removed ) ;
2022-11-09 12:13:29 +00:00
}
2024-11-27 12:26:42 +00:00
public async Task RemoveUserAsync ( Guid organizationId , Guid organizationUserId , Guid ? deletingUserId )
2022-11-09 12:13:29 +00:00
{
2024-10-16 10:33:00 +01:00
var organizationUser = await _organizationUserRepository . GetByIdAsync ( organizationUserId ) ;
2024-11-27 12:26:42 +00:00
ValidateRemoveUser ( organizationId , organizationUser ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
await RepositoryRemoveUserAsync ( organizationUser , deletingUserId , eventSystemUser : null ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
await _eventService . LogOrganizationUserEventAsync ( organizationUser , EventType . OrganizationUser_Removed ) ;
2024-10-16 10:33:00 +01:00
}
2024-11-27 12:26:42 +00:00
public async Task RemoveUserAsync ( Guid organizationId , Guid organizationUserId , EventSystemUser eventSystemUser )
2024-10-16 10:33:00 +01:00
{
2024-11-27 12:26:42 +00:00
var organizationUser = await _organizationUserRepository . GetByIdAsync ( organizationUserId ) ;
ValidateRemoveUser ( organizationId , organizationUser ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
await RepositoryRemoveUserAsync ( organizationUser , deletingUserId : null , eventSystemUser ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
await _eventService . LogOrganizationUserEventAsync ( organizationUser , EventType . OrganizationUser_Removed , eventSystemUser ) ;
2024-10-16 10:33:00 +01:00
}
2024-11-27 12:26:42 +00:00
public async Task < IEnumerable < ( Guid OrganizationUserId , string ErrorMessage ) > > RemoveUsersAsync (
Guid organizationId , IEnumerable < Guid > organizationUserIds , Guid ? deletingUserId )
2024-10-16 10:33:00 +01:00
{
2024-11-27 12:26:42 +00:00
var result = await RemoveUsersInternalAsync ( organizationId , organizationUserIds , deletingUserId , eventSystemUser : null ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
var removedUsers = result . Where ( r = > string . IsNullOrEmpty ( r . ErrorMessage ) ) . Select ( r = > r . OrganizationUser ) . ToList ( ) ;
if ( removedUsers . Any ( ) )
2024-10-16 10:33:00 +01:00
{
2024-11-27 12:26:42 +00:00
DateTime ? eventDate = _timeProvider . GetUtcNow ( ) . UtcDateTime ;
await _eventService . LogOrganizationUserEventsAsync (
removedUsers . Select ( ou = > ( ou , EventType . OrganizationUser_Removed , eventDate ) ) ) ;
2024-10-16 10:33:00 +01:00
}
2024-11-27 12:26:42 +00:00
return result . Select ( r = > ( r . OrganizationUser . Id , r . ErrorMessage ) ) ;
}
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
public async Task < IEnumerable < ( Guid OrganizationUserId , string ErrorMessage ) > > RemoveUsersAsync (
Guid organizationId , IEnumerable < Guid > organizationUserIds , EventSystemUser eventSystemUser )
{
var result = await RemoveUsersInternalAsync ( organizationId , organizationUserIds , deletingUserId : null , eventSystemUser ) ;
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
var removedUsers = result . Where ( r = > string . IsNullOrEmpty ( r . ErrorMessage ) ) . Select ( r = > r . OrganizationUser ) . ToList ( ) ;
if ( removedUsers . Any ( ) )
2024-10-16 10:33:00 +01:00
{
2024-11-27 12:26:42 +00:00
DateTime ? eventDate = _timeProvider . GetUtcNow ( ) . UtcDateTime ;
await _eventService . LogOrganizationUserEventsAsync (
removedUsers . Select ( ou = > ( ou , EventType . OrganizationUser_Removed , eventSystemUser , eventDate ) ) ) ;
2024-10-16 10:33:00 +01:00
}
2024-11-27 12:26:42 +00:00
return result . Select ( r = > ( r . OrganizationUser . Id , r . ErrorMessage ) ) ;
2022-11-09 12:13:29 +00:00
}
2024-12-10 11:14:34 +00:00
public async Task UserLeaveAsync ( Guid organizationId , Guid userId )
{
var organizationUser = await _organizationUserRepository . GetByOrganizationAsync ( organizationId , userId ) ;
ValidateRemoveUser ( organizationId , organizationUser ) ;
await RepositoryRemoveUserAsync ( organizationUser , deletingUserId : null , eventSystemUser : null ) ;
await _eventService . LogOrganizationUserEventAsync ( organizationUser , EventType . OrganizationUser_Left ) ;
}
2024-11-27 12:26:42 +00:00
private void ValidateRemoveUser ( Guid organizationId , OrganizationUser orgUser )
2022-10-31 09:58:21 +00:00
{
if ( orgUser = = null | | orgUser . OrganizationId ! = organizationId )
{
2024-11-27 12:26:42 +00:00
throw new NotFoundException ( UserNotFoundErrorMessage ) ;
2022-10-31 09:58:21 +00:00
}
}
2024-10-16 10:33:00 +01:00
2024-11-27 12:26:42 +00:00
private async Task RepositoryRemoveUserAsync ( OrganizationUser orgUser , Guid ? deletingUserId , EventSystemUser ? eventSystemUser )
2024-10-16 10:33:00 +01:00
{
if ( deletingUserId . HasValue & & orgUser . UserId = = deletingUserId . Value )
{
2024-11-27 12:26:42 +00:00
throw new BadRequestException ( RemoveYourselfErrorMessage ) ;
2024-10-16 10:33:00 +01:00
}
if ( orgUser . Type = = OrganizationUserType . Owner )
{
if ( deletingUserId . HasValue & & ! await _currentContext . OrganizationOwner ( orgUser . OrganizationId ) )
{
2024-11-27 12:26:42 +00:00
throw new BadRequestException ( RemoveOwnerByNonOwnerErrorMessage ) ;
2024-10-16 10:33:00 +01:00
}
if ( ! await _hasConfirmedOwnersExceptQuery . HasConfirmedOwnersExceptAsync ( orgUser . OrganizationId , new [ ] { orgUser . Id } , includeProvider : true ) )
{
2024-11-27 12:26:42 +00:00
throw new BadRequestException ( RemoveLastConfirmedOwnerErrorMessage ) ;
}
}
2025-04-03 11:35:09 -04:00
if ( orgUser . Type = = OrganizationUserType . Admin & & await _currentContext . OrganizationCustom ( orgUser . OrganizationId ) )
{
throw new BadRequestException ( RemoveAdminByCustomUserErrorMessage ) ;
}
2025-05-13 07:17:54 +10:00
if ( deletingUserId . HasValue & & eventSystemUser = = null )
2024-11-27 12:26:42 +00:00
{
2025-04-08 14:38:44 -05:00
var claimedStatus = await _getOrganizationUsersClaimedStatusQuery . GetUsersOrganizationClaimedStatusAsync ( orgUser . OrganizationId , new [ ] { orgUser . Id } ) ;
if ( claimedStatus . TryGetValue ( orgUser . Id , out var isClaimed ) & & isClaimed )
2024-11-27 12:26:42 +00:00
{
throw new BadRequestException ( RemoveClaimedAccountErrorMessage ) ;
2024-10-16 10:33:00 +01:00
}
}
await _organizationUserRepository . DeleteAsync ( orgUser ) ;
if ( orgUser . UserId . HasValue )
{
await DeleteAndPushUserRegistrationAsync ( orgUser . OrganizationId , orgUser . UserId . Value ) ;
}
}
2024-10-22 09:20:57 -07:00
private async Task < IEnumerable < string > > GetUserDeviceIdsAsync ( Guid userId )
2024-10-16 10:33:00 +01:00
{
var devices = await _deviceRepository . GetManyByUserIdAsync ( userId ) ;
return devices
. Where ( d = > ! string . IsNullOrWhiteSpace ( d . PushToken ) )
2024-10-22 09:20:57 -07:00
. Select ( d = > d . Id . ToString ( ) ) ;
2024-10-16 10:33:00 +01:00
}
private async Task DeleteAndPushUserRegistrationAsync ( Guid organizationId , Guid userId )
{
var devices = await GetUserDeviceIdsAsync ( userId ) ;
await _pushRegistrationService . DeleteUserRegistrationOrganizationAsync ( devices ,
organizationId . ToString ( ) ) ;
await _pushNotificationService . PushSyncOrgKeysAsync ( userId ) ;
}
2024-11-27 12:26:42 +00:00
private async Task < IEnumerable < ( OrganizationUser OrganizationUser , string ErrorMessage ) > > RemoveUsersInternalAsync (
Guid organizationId , IEnumerable < Guid > organizationUsersId , Guid ? deletingUserId , EventSystemUser ? eventSystemUser )
{
var orgUsers = await _organizationUserRepository . GetManyAsync ( organizationUsersId ) ;
var filteredUsers = orgUsers . Where ( u = > u . OrganizationId = = organizationId ) . ToList ( ) ;
if ( ! filteredUsers . Any ( ) )
{
throw new BadRequestException ( UsersInvalidErrorMessage ) ;
}
if ( ! await _hasConfirmedOwnersExceptQuery . HasConfirmedOwnersExceptAsync ( organizationId , organizationUsersId ) )
{
throw new BadRequestException ( RemoveLastConfirmedOwnerErrorMessage ) ;
}
var deletingUserIsOwner = false ;
if ( deletingUserId . HasValue )
{
deletingUserIsOwner = await _currentContext . OrganizationOwner ( organizationId ) ;
}
2025-05-13 07:17:54 +10:00
var claimedStatus = deletingUserId . HasValue & & eventSystemUser = = null
2025-04-08 14:38:44 -05:00
? await _getOrganizationUsersClaimedStatusQuery . GetUsersOrganizationClaimedStatusAsync ( organizationId , filteredUsers . Select ( u = > u . Id ) )
2024-11-27 12:26:42 +00:00
: filteredUsers . ToDictionary ( u = > u . Id , u = > false ) ;
var result = new List < ( OrganizationUser OrganizationUser , string ErrorMessage ) > ( ) ;
foreach ( var orgUser in filteredUsers )
{
try
{
if ( deletingUserId . HasValue & & orgUser . UserId = = deletingUserId )
{
throw new BadRequestException ( RemoveYourselfErrorMessage ) ;
}
if ( orgUser . Type = = OrganizationUserType . Owner & & deletingUserId . HasValue & & ! deletingUserIsOwner )
{
throw new BadRequestException ( RemoveOwnerByNonOwnerErrorMessage ) ;
}
2025-04-08 14:38:44 -05:00
if ( claimedStatus . TryGetValue ( orgUser . Id , out var isClaimed ) & & isClaimed )
2024-11-27 12:26:42 +00:00
{
throw new BadRequestException ( RemoveClaimedAccountErrorMessage ) ;
}
result . Add ( ( orgUser , string . Empty ) ) ;
}
catch ( BadRequestException e )
{
result . Add ( ( orgUser , e . Message ) ) ;
}
}
var organizationUsersToRemove = result . Where ( r = > string . IsNullOrEmpty ( r . ErrorMessage ) ) . Select ( r = > r . OrganizationUser ) . ToList ( ) ;
if ( organizationUsersToRemove . Any ( ) )
{
await _organizationUserRepository . DeleteManyAsync ( organizationUsersToRemove . Select ( ou = > ou . Id ) ) ;
foreach ( var orgUser in organizationUsersToRemove . Where ( ou = > ou . UserId . HasValue ) )
{
2024-12-10 11:14:34 +00:00
await DeleteAndPushUserRegistrationAsync ( organizationId , orgUser . UserId ! . Value ) ;
2024-11-27 12:26:42 +00:00
}
}
return result ;
}
2022-10-31 09:58:21 +00:00
}