1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-21 13:05:11 -05:00

PM-17921 change the GenerateAccessData method to process lists in parallel (#5552)

* PM-17921 change the GenerateAccessData method to process lists in parallel.

* PM-17921 removing old method
This commit is contained in:
Graham Walker 2025-04-07 11:26:06 -05:00 committed by GitHub
parent 01daad5942
commit 1cf9ff34c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,4 +1,5 @@
using Bit.Core.AdminConsole.Entities; using System.Collections.Concurrent;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Entities; using Bit.Core.Entities;
@ -59,13 +60,12 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
var orgItems = await _organizationCiphersQuery.GetAllOrganizationCiphers(request.OrganizationId); var orgItems = await _organizationCiphersQuery.GetAllOrganizationCiphers(request.OrganizationId);
var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers); var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers);
var memberAccessCipherDetails = GenerateAccessData( var memberAccessCipherDetails = GenerateAccessDataParallel(
orgGroups, orgGroups,
orgCollectionsWithAccess, orgCollectionsWithAccess,
orgItems, orgItems,
organizationUsersTwoFactorEnabled, organizationUsersTwoFactorEnabled,
orgAbility orgAbility);
);
return memberAccessCipherDetails; return memberAccessCipherDetails;
} }
@ -82,72 +82,72 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
/// <param name="organizationUsersTwoFactorEnabled">Organization users and two factor status</param> /// <param name="organizationUsersTwoFactorEnabled">Organization users and two factor status</param>
/// <param name="orgAbility">Organization ability for account recovery status</param> /// <param name="orgAbility">Organization ability for account recovery status</param>
/// <returns>List of the MemberAccessCipherDetailsModel</returns>; /// <returns>List of the MemberAccessCipherDetailsModel</returns>;
private IEnumerable<MemberAccessCipherDetails> GenerateAccessData( private IEnumerable<MemberAccessCipherDetails> GenerateAccessDataParallel(
ICollection<Group> orgGroups, ICollection<Group> orgGroups,
ICollection<Tuple<Collection, CollectionAccessDetails>> orgCollectionsWithAccess, ICollection<Tuple<Collection, CollectionAccessDetails>> orgCollectionsWithAccess,
IEnumerable<CipherOrganizationDetailsWithCollections> orgItems, IEnumerable<CipherOrganizationDetailsWithCollections> orgItems,
IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled, IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled,
OrganizationAbility orgAbility) OrganizationAbility orgAbility)
{ {
var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user); var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user).ToList();
// Create a dictionary to lookup the group names later.
var groupNameDictionary = orgGroups.ToDictionary(x => x.Id, x => x.Name); var groupNameDictionary = orgGroups.ToDictionary(x => x.Id, x => x.Name);
// Get collections grouped and into a dictionary for counts
var collectionItems = orgItems var collectionItems = orgItems
.SelectMany(x => x.CollectionIds, .SelectMany(x => x.CollectionIds,
(cipher, collectionId) => new { Cipher = cipher, CollectionId = collectionId }) (cipher, collectionId) => new { Cipher = cipher, CollectionId = collectionId })
.GroupBy(y => y.CollectionId, .GroupBy(y => y.CollectionId,
(key, ciphers) => new { CollectionId = key, Ciphers = ciphers }); (key, ciphers) => new { CollectionId = key, Ciphers = ciphers });
var itemLookup = collectionItems.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString())); var itemLookup = collectionItems.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString()).ToList());
// Loop through the org users and populate report and access data var memberAccessCipherDetails = new ConcurrentBag<MemberAccessCipherDetails>();
var memberAccessCipherDetails = new List<MemberAccessCipherDetails>();
foreach (var user in orgUsers) Parallel.ForEach(orgUsers, user =>
{ {
var groupAccessDetails = new List<MemberAccessDetails>(); var groupAccessDetails = new List<MemberAccessDetails>();
var userCollectionAccessDetails = new List<MemberAccessDetails>(); var userCollectionAccessDetails = new List<MemberAccessDetails>();
foreach (var tCollect in orgCollectionsWithAccess) foreach (var tCollect in orgCollectionsWithAccess)
{ {
var hasItems = itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items); if (itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items))
var collectionCiphers = hasItems ? items.Select(x => x) : null;
var itemCounts = hasItems ? collectionCiphers.Count() : 0;
if (tCollect.Item2.Groups.Count() > 0)
{ {
var itemCounts = items.Count;
var groupDetails = tCollect.Item2.Groups.Where((tCollectGroups) => user.Groups.Contains(tCollectGroups.Id)).Select(x => if (tCollect.Item2.Groups.Any())
new MemberAccessDetails {
{ var groupDetails = tCollect.Item2.Groups
CollectionId = tCollect.Item1.Id, .Where(tCollectGroups => user.Groups.Contains(tCollectGroups.Id))
CollectionName = tCollect.Item1.Name, .Select(x => new MemberAccessDetails
GroupId = x.Id, {
GroupName = groupNameDictionary[x.Id], CollectionId = tCollect.Item1.Id,
ReadOnly = x.ReadOnly, CollectionName = tCollect.Item1.Name,
HidePasswords = x.HidePasswords, GroupId = x.Id,
Manage = x.Manage, GroupName = groupNameDictionary[x.Id],
ItemCount = itemCounts, ReadOnly = x.ReadOnly,
CollectionCipherIds = items HidePasswords = x.HidePasswords,
}); Manage = x.Manage,
ItemCount = itemCounts,
CollectionCipherIds = items
});
groupAccessDetails.AddRange(groupDetails); groupAccessDetails.AddRange(groupDetails);
} }
// All collections assigned to users and their permissions if (tCollect.Item2.Users.Any())
if (tCollect.Item2.Users.Count() > 0) {
{ var userCollectionDetails = tCollect.Item2.Users
var userCollectionDetails = tCollect.Item2.Users.Where((tCollectUser) => tCollectUser.Id == user.Id).Select(x => .Where(tCollectUser => tCollectUser.Id == user.Id)
new MemberAccessDetails .Select(x => new MemberAccessDetails
{ {
CollectionId = tCollect.Item1.Id, CollectionId = tCollect.Item1.Id,
CollectionName = tCollect.Item1.Name, CollectionName = tCollect.Item1.Name,
ReadOnly = x.ReadOnly, ReadOnly = x.ReadOnly,
HidePasswords = x.HidePasswords, HidePasswords = x.HidePasswords,
Manage = x.Manage, Manage = x.Manage,
ItemCount = itemCounts, ItemCount = itemCounts,
CollectionCipherIds = items CollectionCipherIds = items
}); });
userCollectionAccessDetails.AddRange(userCollectionDetails);
userCollectionAccessDetails.AddRange(userCollectionDetails);
}
} }
} }
@ -156,7 +156,6 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
UserName = user.Name, UserName = user.Name,
Email = user.Email, Email = user.Email,
TwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == user.Id).twoFactorIsEnabled, TwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == user.Id).twoFactorIsEnabled,
// Both the user's ResetPasswordKey must be set and the organization can UseResetPassword
AccountRecoveryEnabled = !string.IsNullOrEmpty(user.ResetPasswordKey) && orgAbility.UseResetPassword, AccountRecoveryEnabled = !string.IsNullOrEmpty(user.ResetPasswordKey) && orgAbility.UseResetPassword,
UserGuid = user.Id, UserGuid = user.Id,
UsesKeyConnector = user.UsesKeyConnector UsesKeyConnector = user.UsesKeyConnector
@ -169,9 +168,8 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
userAccessDetails.AddRange(userGroups); userAccessDetails.AddRange(userGroups);
} }
// There can be edge cases where groups don't have a collection
var groupsWithoutCollections = user.Groups.Where(x => !userAccessDetails.Any(y => x == y.GroupId)); var groupsWithoutCollections = user.Groups.Where(x => !userAccessDetails.Any(y => x == y.GroupId));
if (groupsWithoutCollections.Count() > 0) if (groupsWithoutCollections.Any())
{ {
var emptyGroups = groupsWithoutCollections.Select(x => new MemberAccessDetails var emptyGroups = groupsWithoutCollections.Select(x => new MemberAccessDetails
{ {
@ -189,20 +187,20 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
} }
report.AccessDetails = userAccessDetails; report.AccessDetails = userAccessDetails;
var userCiphers = var userCiphers = report.AccessDetails
report.AccessDetails .Where(x => x.ItemCount > 0)
.Where(x => x.ItemCount > 0) .SelectMany(y => y.CollectionCipherIds)
.SelectMany(y => y.CollectionCipherIds) .Distinct();
.Distinct();
report.CipherIds = userCiphers; report.CipherIds = userCiphers;
report.TotalItemCount = userCiphers.Count(); report.TotalItemCount = userCiphers.Count();
// Distinct items only
var distinctItems = report.AccessDetails.Where(x => x.CollectionId.HasValue).Select(x => x.CollectionId).Distinct(); var distinctItems = report.AccessDetails.Where(x => x.CollectionId.HasValue).Select(x => x.CollectionId).Distinct();
report.CollectionsCount = distinctItems.Count(); report.CollectionsCount = distinctItems.Count();
report.GroupsCount = report.AccessDetails.Select(x => x.GroupId).Where(y => y.HasValue).Distinct().Count(); report.GroupsCount = report.AccessDetails.Select(x => x.GroupId).Where(y => y.HasValue).Distinct().Count();
memberAccessCipherDetails.Add(report); memberAccessCipherDetails.Add(report);
} });
return memberAccessCipherDetails; return memberAccessCipherDetails;
} }
} }