using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand { private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IUserRepository _userRepository; private readonly IEventService _eventService; private readonly IMailService _mailService; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; private readonly IPushNotificationService _pushNotificationService; private readonly IPushRegistrationService _pushRegistrationService; private readonly IPolicyService _policyService; private readonly IDeviceRepository _deviceRepository; public ConfirmOrganizationUserCommand( IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IUserRepository userRepository, IEventService eventService, IMailService mailService, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, IPushNotificationService pushNotificationService, IPushRegistrationService pushRegistrationService, IPolicyService policyService, IDeviceRepository deviceRepository) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _userRepository = userRepository; _eventService = eventService; _mailService = mailService; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; _pushNotificationService = pushNotificationService; _pushRegistrationService = pushRegistrationService; _policyService = policyService; _deviceRepository = deviceRepository; } public async Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId) { var result = await ConfirmUsersAsync( organizationId, new Dictionary() { { organizationUserId, key } }, confirmingUserId); if (!result.Any()) { throw new BadRequestException("User not valid."); } var (orgUser, error) = result[0]; if (error != "") { throw new BadRequestException(error); } return orgUser; } public async Task>> ConfirmUsersAsync(Guid organizationId, Dictionary keys, Guid confirmingUserId) { var selectedOrganizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys); var validSelectedOrganizationUsers = selectedOrganizationUsers .Where(u => u.Status == OrganizationUserStatusType.Accepted && u.OrganizationId == organizationId && u.UserId != null) .ToList(); if (!validSelectedOrganizationUsers.Any()) { return new List>(); } var validSelectedUserIds = validSelectedOrganizationUsers.Select(u => u.UserId.Value).ToList(); var organization = await _organizationRepository.GetByIdAsync(organizationId); var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds); var users = await _userRepository.GetManyAsync(validSelectedUserIds); var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds); var keyedFilteredUsers = validSelectedOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u); var keyedOrganizationUsers = allUsersOrgs.GroupBy(u => u.UserId.Value) .ToDictionary(u => u.Key, u => u.ToList()); var succeededUsers = new List(); var result = new List>(); foreach (var user in users) { if (!keyedFilteredUsers.ContainsKey(user.Id)) { continue; } var orgUser = keyedFilteredUsers[user.Id]; var orgUsers = keyedOrganizationUsers.GetValueOrDefault(user.Id, new List()); try { if (organization.PlanType == PlanType.Free && (orgUser.Type == OrganizationUserType.Admin || orgUser.Type == OrganizationUserType.Owner)) { // Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this. var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(user.Id); if (adminCount > 0) { throw new BadRequestException("User can only be an admin of one free organization."); } } var twoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled; await CheckPoliciesAsync(organizationId, user, orgUsers, twoFactorEnabled); orgUser.Status = OrganizationUserStatusType.Confirmed; orgUser.Key = keys[orgUser.Id]; orgUser.Email = null; await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), user.Email, orgUser.AccessSecretsManager); await DeleteAndPushUserRegistrationAsync(organizationId, user.Id); succeededUsers.Add(orgUser); result.Add(Tuple.Create(orgUser, "")); } catch (BadRequestException e) { result.Add(Tuple.Create(orgUser, e.Message)); } } await _organizationUserRepository.ReplaceManyAsync(succeededUsers); return result; } private async Task CheckPoliciesAsync(Guid organizationId, User user, ICollection userOrgs, bool twoFactorEnabled) { // Enforce Two Factor Authentication Policy for this organization var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)) .Any(p => p.OrganizationId == organizationId); if (orgRequiresTwoFactor && !twoFactorEnabled) { throw new BadRequestException("User does not have two-step login enabled."); } var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId); var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg); var otherSingleOrgPolicies = singleOrgPolicies.Where(p => p.OrganizationId != organizationId); // Enforce Single Organization Policy for this organization if (hasOtherOrgs && singleOrgPolicies.Any(p => p.OrganizationId == organizationId)) { throw new BadRequestException("Cannot confirm this member to the organization until they leave or remove all other organizations."); } // Enforce Single Organization Policy of other organizations user is a member of if (otherSingleOrgPolicies.Any()) { throw new BadRequestException("Cannot confirm this member to the organization because they are in another organization which forbids it."); } } private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId) { var devices = await GetUserDeviceIdsAsync(userId); await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(devices, organizationId.ToString()); await _pushNotificationService.PushSyncOrgKeysAsync(userId); } private async Task> GetUserDeviceIdsAsync(Guid userId) { var devices = await _deviceRepository.GetManyByUserIdAsync(userId); return devices .Where(d => !string.IsNullOrWhiteSpace(d.PushToken)) .Select(d => d.Id.ToString()); } }