mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
[PM-12684] Remove Members Bulk 2FA feature flag logic (#4864)
This commit is contained in:
@ -1323,13 +1323,12 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||
Guid confirmingUserId, IUserService userService)
|
||||
Guid confirmingUserId)
|
||||
{
|
||||
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);
|
||||
var result = await ConfirmUsersAsync(
|
||||
organizationId,
|
||||
new Dictionary<Guid, string>() { { organizationUserId, key } },
|
||||
confirmingUserId);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
@ -1345,75 +1344,6 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId, IUserService userService)
|
||||
{
|
||||
var organizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys);
|
||||
var validOrganizationUsers = organizationUsers
|
||||
.Where(u => u.Status == OrganizationUserStatusType.Accepted && u.OrganizationId == organizationId && u.UserId != null)
|
||||
.ToList();
|
||||
|
||||
if (!validOrganizationUsers.Any())
|
||||
{
|
||||
return new List<Tuple<OrganizationUser, string>>();
|
||||
}
|
||||
|
||||
var validOrganizationUserIds = validOrganizationUsers.Select(u => u.UserId.Value).ToList();
|
||||
|
||||
var organization = await GetOrgById(organizationId);
|
||||
var usersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validOrganizationUserIds);
|
||||
var users = await _userRepository.GetManyAsync(validOrganizationUserIds);
|
||||
|
||||
var keyedFilteredUsers = validOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u);
|
||||
var keyedOrganizationUsers = usersOrgs.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.");
|
||||
}
|
||||
}
|
||||
|
||||
await CheckPolicies(organizationId, user, orgUsers, userService);
|
||||
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;
|
||||
}
|
||||
|
||||
public async Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync_vNext(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
Guid confirmingUserId)
|
||||
{
|
||||
var selectedOrganizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys);
|
||||
@ -1430,7 +1360,7 @@ public class OrganizationService : IOrganizationService
|
||||
|
||||
var organization = await GetOrgById(organizationId);
|
||||
var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds);
|
||||
var users = await _userRepository.GetManyWithCalculatedPremiumAsync(validSelectedUserIds);
|
||||
var users = await _userRepository.GetManyAsync(validSelectedUserIds);
|
||||
var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds);
|
||||
|
||||
var keyedFilteredUsers = validSelectedOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u);
|
||||
@ -1462,7 +1392,7 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
|
||||
var twoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled;
|
||||
await CheckPolicies_vNext(organizationId, user, orgUsers, twoFactorEnabled);
|
||||
await CheckPoliciesAsync(organizationId, user, orgUsers, twoFactorEnabled);
|
||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||
orgUser.Key = keys[orgUser.Id];
|
||||
orgUser.Email = null;
|
||||
@ -1567,33 +1497,7 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPolicies(Guid organizationId, User user,
|
||||
ICollection<OrganizationUser> userOrgs, IUserService userService)
|
||||
{
|
||||
// Enforce Two Factor Authentication Policy for this organization
|
||||
var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)).Any(p => p.OrganizationId == organizationId);
|
||||
if (orgRequiresTwoFactor && !await userService.TwoFactorIsEnabledAsync(user))
|
||||
{
|
||||
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 CheckPolicies_vNext(Guid organizationId, UserWithCalculatedPremium user,
|
||||
private async Task CheckPoliciesAsync(Guid organizationId, User user,
|
||||
ICollection<OrganizationUser> userOrgs, bool twoFactorEnabled)
|
||||
{
|
||||
// Enforce Two Factor Authentication Policy for this organization
|
||||
@ -2438,8 +2342,7 @@ public class OrganizationService : IOrganizationService
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId,
|
||||
IUserService userService)
|
||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId)
|
||||
{
|
||||
if (restoringUserId.HasValue && organizationUser.UserId == restoringUserId.Value)
|
||||
{
|
||||
@ -2452,18 +2355,17 @@ public class OrganizationService : IOrganizationService
|
||||
throw new BadRequestException("Only owners can restore other owners.");
|
||||
}
|
||||
|
||||
await RepositoryRestoreUserAsync(organizationUser, userService);
|
||||
await RepositoryRestoreUserAsync(organizationUser);
|
||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
||||
}
|
||||
|
||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser,
|
||||
IUserService userService)
|
||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser)
|
||||
{
|
||||
await RepositoryRestoreUserAsync(organizationUser, userService);
|
||||
await RepositoryRestoreUserAsync(organizationUser);
|
||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, systemUser);
|
||||
}
|
||||
|
||||
private async Task RepositoryRestoreUserAsync(OrganizationUser organizationUser, IUserService userService)
|
||||
private async Task RepositoryRestoreUserAsync(OrganizationUser organizationUser)
|
||||
{
|
||||
if (organizationUser.Status != OrganizationUserStatusType.Revoked)
|
||||
{
|
||||
@ -2478,21 +2380,14 @@ public class OrganizationService : IOrganizationService
|
||||
await AutoAddSeatsAsync(organization, 1);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
userTwoFactorIsEnabled = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(new[] { organizationUser.UserId.Value })).FirstOrDefault().twoFactorIsEnabled;
|
||||
}
|
||||
|
||||
await CheckPoliciesBeforeRestoreAsync_vNext(organizationUser, userTwoFactorIsEnabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userService);
|
||||
}
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, userTwoFactorIsEnabled);
|
||||
|
||||
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
||||
|
||||
@ -2526,11 +2421,7 @@ public class OrganizationService : IOrganizationService
|
||||
|
||||
// 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 organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(filteredUsers.Select(ou => ou.UserId.Value));
|
||||
|
||||
var result = new List<Tuple<OrganizationUser, string>>();
|
||||
|
||||
@ -2553,15 +2444,8 @@ public class OrganizationService : IOrganizationService
|
||||
throw new BadRequestException("Only owners can restore other owners.");
|
||||
}
|
||||
|
||||
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 twoFactorIsEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(ou => ou.userId == organizationUser.UserId.Value).twoFactorIsEnabled;
|
||||
await CheckPoliciesBeforeRestoreAsync(organizationUser, twoFactorIsEnabled);
|
||||
|
||||
var status = GetPriorActiveOrganizationUserStatusType(organizationUser);
|
||||
|
||||
@ -2580,54 +2464,7 @@ public class OrganizationService : IOrganizationService
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task CheckPoliciesBeforeRestoreAsync(OrganizationUser orgUser, IUserService userService)
|
||||
{
|
||||
// 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
|
||||
var user = await _userRepository.GetByIdAsync(userId);
|
||||
if (!await userService.TwoFactorIsEnabledAsync(user))
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckPoliciesBeforeRestoreAsync_vNext(OrganizationUser orgUser, bool userHasTwoFactorEnabled)
|
||||
private async Task CheckPoliciesBeforeRestoreAsync(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
|
||||
|
Reference in New Issue
Block a user