mirror of
https://github.com/bitwarden/server.git
synced 2025-07-03 00:52:49 -05:00
[AC-1698] Check if a user has 2FA enabled more efficiently (#4524)
* feat: Add stored procedure for reading organization user details with premium access by organization ID The code changes include: - Addition of a new stored procedure [dbo].[OrganizationUserUserDetailsWithPremiumAccess_ReadByOrganizationId] to read organization user details with premium access by organization ID - Modification of the IUserService interface to include an optional parameter for checking two-factor authentication with premium access - Modification of the UserService class to handle the new optional parameter in the TwoFactorIsEnabledAsync method - Addition of a new method GetManyDetailsWithPremiumAccessByOrganizationAsync in the IOrganizationUserRepository interface to retrieve organization user details with premium access by organization ID - Addition of a new view [dbo].[OrganizationUserUserDetailsWithPremiumAccessView] to retrieve organization user details with premium access * Add IUserRepository.SearchDetailsAsync that includes the field HasPremiumAccess * Check the feature flag on Admin.UsersController to see if the optimization runs * Modify PolicyService to run query optimization if the feature flag is enabled * Refactor the parameter check on UserService.TwoFactorIsEnabledAsync * Run query optimization on public MembersController if feature flag is enabled * Restore refactor * Reverted change used for development * Add unit tests for OrganizationService.RestoreUser * Separate new CheckPoliciesBeforeRestoreAsync optimization into new method * Add more unit tests * Apply refactor to bulk restore * Add GetManyDetailsAsync method to IUserRepository. Add ConfirmUsersAsync_vNext method to IOrganizationService * Add unit tests for ConfirmUser_vNext * Refactor the optimization to use the new TwoFactorIsEnabledAsync method instead of changing the existing one * Removed unused sql scripts and added migration script * Remove unnecessary view * chore: Remove unused SearchDetailsAsync method from IUserRepository and UserRepository * refactor: Use UserDetails constructor in UserRepository * Add summary to IUserRepository.GetManyDetailsAsync * Add summary descriptions to IUserService.TwoFactorIsEnabledAsync * Remove obsolete annotation from IUserRepository.UpdateUserKeyAndEncryptedDataAsync * refactor: Rename UserDetails to UserWithCalculatedPremium across the codebase * Extract IUserService.TwoFactorIsEnabledAsync into a new TwoFactorIsEnabledQuery class * Add unit tests for TwoFactorIsEnabledQuery * Update TwoFactorIsEnabledQueryTests to include additional provider types * Refactor TwoFactorIsEnabledQuery * Refactor TwoFactorIsEnabledQuery and update tests * refactor: Update TwoFactorIsEnabledQueryTests to include test for null TwoFactorProviders * refactor: Improve TwoFactorIsEnabledQuery and update tests * refactor: Improve TwoFactorIsEnabledQuery and update tests * Remove empty <returns> from summary * Update User_ReadByIdsWithCalculatedPremium stored procedure to accept JSON array of IDs
This commit is contained in:
@ -53,6 +53,8 @@ public interface IOrganizationService
|
||||
Guid confirmingUserId, IUserService userService);
|
||||
Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId, IUserService userService);
|
||||
Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync_vNext(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId);
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
|
@ -13,6 +13,7 @@ using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Business;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
@ -67,6 +68,7 @@ public class OrganizationService : IOrganizationService
|
||||
private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||
|
||||
public OrganizationService(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -99,7 +101,8 @@ public class OrganizationService : IOrganizationService
|
||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IProviderRepository providerRepository,
|
||||
IFeatureService featureService)
|
||||
IFeatureService featureService,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -132,6 +135,7 @@ public class OrganizationService : IOrganizationService
|
||||
_orgUserInviteTokenableFactory = orgUserInviteTokenableFactory;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
_featureService = featureService;
|
||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||
}
|
||||
|
||||
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
||||
@ -1291,7 +1295,10 @@ public class OrganizationService : IOrganizationService
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||
Guid confirmingUserId, IUserService userService)
|
||||
{
|
||||
var result = await ConfirmUsersAsync(organizationId, new Dictionary<Guid, string>() { { organizationUserId, key } },
|
||||
var result = _featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)
|
||||
? await ConfirmUsersAsync_vNext(organizationId, new Dictionary<Guid, string>() { { organizationUserId, key } },
|
||||
confirmingUserId)
|
||||
: await ConfirmUsersAsync(organizationId, new Dictionary<Guid, string>() { { organizationUserId, key } },
|
||||
confirmingUserId, userService);
|
||||
|
||||
if (!result.Any())
|
||||
@ -1376,6 +1383,77 @@ public class OrganizationService : IOrganizationService
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync_vNext(Guid organizationId, Dictionary<Guid, string> 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<Tuple<OrganizationUser, string>>();
|
||||
}
|
||||
|
||||
var validSelectedUserIds = validSelectedOrganizationUsers.Select(u => u.UserId.Value).ToList();
|
||||
|
||||
var organization = await GetOrgById(organizationId);
|
||||
var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds);
|
||||
var users = await _userRepository.GetManyWithCalculatedPremiumAsync(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<OrganizationUser>();
|
||||
var result = new List<Tuple<OrganizationUser, string>>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (!keyedFilteredUsers.ContainsKey(user.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var orgUser = keyedFilteredUsers[user.Id];
|
||||
var orgUsers = keyedOrganizationUsers.GetValueOrDefault(user.Id, new List<OrganizationUser>());
|
||||
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 CheckPolicies_vNext(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;
|
||||
}
|
||||
|
||||
internal async Task<(bool canScale, string failureReason)> CanScaleAsync(
|
||||
Organization organization,
|
||||
int seatsToAdd)
|
||||
@ -1485,6 +1563,33 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPolicies_vNext(Guid organizationId, UserWithCalculatedPremium user,
|
||||
ICollection<OrganizationUser> 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.");
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")]
|
||||
public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||
{
|
||||
@ -2319,7 +2424,21 @@ public class OrganizationService : IOrganizationService
|
||||
await AutoAddSeatsAsync(organization, 1);
|
||||
}
|
||||
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
var userTwoFactorIsEnabled = false;
|
||||
// Only check Two Factor Authentication status if the user is linked to a user account
|
||||
if (organizationUser.UserId.HasValue)
|
||||
{
|
||||
userTwoFactorIsEnabled = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(new[] { organizationUser.UserId.Value })).FirstOrDefault().twoFactorIsEnabled;
|
||||
}
|
||||
|
||||
await CheckPoliciesBeforeRestoreAsync_vNext(organizationUser, userTwoFactorIsEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
||||
}
|
||||
|
||||
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
||||
|
||||
@ -2351,6 +2470,14 @@ public class OrganizationService : IOrganizationService
|
||||
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
||||
}
|
||||
|
||||
// Query Two Factor Authentication status for all users in the organization
|
||||
// This is an optimization to avoid querying the Two Factor Authentication status for each user individually
|
||||
IEnumerable<(Guid userId, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled = null;
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(filteredUsers.Select(ou => ou.UserId.Value));
|
||||
}
|
||||
|
||||
var result = new List<Tuple<OrganizationUser, string>>();
|
||||
|
||||
foreach (var organizationUser in filteredUsers)
|
||||
@ -2372,7 +2499,15 @@ public class OrganizationService : IOrganizationService
|
||||
throw new BadRequestException("Only owners can restore other owners.");
|
||||
}
|
||||
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
var twoFactorIsEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(ou => ou.userId == organizationUser.UserId.Value).twoFactorIsEnabled;
|
||||
await CheckPoliciesBeforeRestoreAsync_vNext(organizationUser, twoFactorIsEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
||||
}
|
||||
|
||||
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
||||
|
||||
@ -2438,6 +2573,52 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPoliciesBeforeRestoreAsync_vNext(OrganizationUser orgUser, bool userHasTwoFactorEnabled)
|
||||
{
|
||||
// An invited OrganizationUser isn't linked with a user account yet, so these checks are irrelevant
|
||||
// The user will be subject to the same checks when they try to accept the invite
|
||||
if (GetPriorActiveOrganizationUserStatusType(orgUser) == OrganizationUserStatusType.Invited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = orgUser.UserId.Value;
|
||||
|
||||
// Enforce Single Organization Policy of organization user is being restored to
|
||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
|
||||
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
||||
var singleOrgPoliciesApplyingToRevokedUsers = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
||||
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
|
||||
var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);
|
||||
|
||||
if (hasOtherOrgs && singleOrgPolicyApplies)
|
||||
{
|
||||
throw new BadRequestException("You cannot restore this user until " +
|
||||
"they leave or remove all other organizations.");
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of other organizations user is a member of
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId,
|
||||
PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
throw new BadRequestException("You cannot restore this user because they are a member of " +
|
||||
"another organization which forbids it");
|
||||
}
|
||||
|
||||
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
||||
if (!userHasTwoFactorEnabled)
|
||||
{
|
||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
throw new BadRequestException("You cannot restore this user until they enable " +
|
||||
"two-step login on their user account.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusType(OrganizationUser organizationUser)
|
||||
{
|
||||
// Determine status to revert back to
|
||||
|
@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -24,6 +25,8 @@ public class PolicyService : IPolicyService
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||
|
||||
public PolicyService(
|
||||
IApplicationCacheService applicationCacheService,
|
||||
@ -33,7 +36,9 @@ public class PolicyService : IPolicyService
|
||||
IPolicyRepository policyRepository,
|
||||
ISsoConfigRepository ssoConfigRepository,
|
||||
IMailService mailService,
|
||||
GlobalSettings globalSettings)
|
||||
GlobalSettings globalSettings,
|
||||
IFeatureService featureService,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery)
|
||||
{
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_eventService = eventService;
|
||||
@ -43,6 +48,8 @@ public class PolicyService : IPolicyService
|
||||
_ssoConfigRepository = ssoConfigRepository;
|
||||
_mailService = mailService;
|
||||
_globalSettings = globalSettings;
|
||||
_featureService = featureService;
|
||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||
}
|
||||
|
||||
public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService,
|
||||
@ -81,6 +88,12 @@ public class PolicyService : IPolicyService
|
||||
return;
|
||||
}
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization))
|
||||
{
|
||||
await EnablePolicy_vNext(policy, org, organizationService, savingUserId);
|
||||
return;
|
||||
}
|
||||
|
||||
await EnablePolicy(policy, org, userService, organizationService, savingUserId);
|
||||
return;
|
||||
}
|
||||
@ -261,8 +274,7 @@ public class PolicyService : IPolicyService
|
||||
var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id);
|
||||
if (!currentPolicy?.Enabled ?? true)
|
||||
{
|
||||
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(
|
||||
policy.OrganizationId);
|
||||
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 &&
|
||||
@ -311,4 +323,61 @@ public class PolicyService : IPolicyService
|
||||
|
||||
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(
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user