Skip to content

Commit 1cf9ff3

Browse files
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
1 parent 01daad5 commit 1cf9ff3

File tree

1 file changed

+66
-68
lines changed

1 file changed

+66
-68
lines changed

src/Core/Tools/ReportFeatures/MemberAccessCipherDetailsQuery.cs

+66-68
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Bit.Core.AdminConsole.Entities;
1+
using System.Collections.Concurrent;
2+
using Bit.Core.AdminConsole.Entities;
23
using Bit.Core.AdminConsole.Repositories;
34
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
45
using Bit.Core.Entities;
@@ -59,95 +60,94 @@ public async Task<IEnumerable<MemberAccessCipherDetails>> GetMemberAccessCipherD
5960
var orgItems = await _organizationCiphersQuery.GetAllOrganizationCiphers(request.OrganizationId);
6061
var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers);
6162

62-
var memberAccessCipherDetails = GenerateAccessData(
63+
var memberAccessCipherDetails = GenerateAccessDataParallel(
6364
orgGroups,
6465
orgCollectionsWithAccess,
6566
orgItems,
6667
organizationUsersTwoFactorEnabled,
67-
orgAbility
68-
);
68+
orgAbility);
6969

7070
return memberAccessCipherDetails;
7171
}
7272

7373
/// <summary>
7474
/// Generates a report for all members of an organization. Containing summary information
75-
/// such as item, collection, and group counts. Including the cipherIds a member is assigned.
75+
/// such as item, collection, and group counts. Including the cipherIds a member is assigned.
7676
/// Child collection includes detailed information on the user and group collections along
77-
/// with their permissions.
77+
/// with their permissions.
7878
/// </summary>
7979
/// <param name="orgGroups">Organization groups collection</param>
8080
/// <param name="orgCollectionsWithAccess">Collections for the organization and the groups/users and permissions</param>
8181
/// <param name="orgItems">Cipher items for the organization with the collections associated with them</param>
8282
/// <param name="organizationUsersTwoFactorEnabled">Organization users and two factor status</param>
8383
/// <param name="orgAbility">Organization ability for account recovery status</param>
8484
/// <returns>List of the MemberAccessCipherDetailsModel</returns>;
85-
private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
86-
ICollection<Group> orgGroups,
87-
ICollection<Tuple<Collection, CollectionAccessDetails>> orgCollectionsWithAccess,
88-
IEnumerable<CipherOrganizationDetailsWithCollections> orgItems,
89-
IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled,
90-
OrganizationAbility orgAbility)
85+
private IEnumerable<MemberAccessCipherDetails> GenerateAccessDataParallel(
86+
ICollection<Group> orgGroups,
87+
ICollection<Tuple<Collection, CollectionAccessDetails>> orgCollectionsWithAccess,
88+
IEnumerable<CipherOrganizationDetailsWithCollections> orgItems,
89+
IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled,
90+
OrganizationAbility orgAbility)
9191
{
92-
var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user);
93-
// Create a dictionary to lookup the group names later.
92+
var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user).ToList();
9493
var groupNameDictionary = orgGroups.ToDictionary(x => x.Id, x => x.Name);
95-
96-
// Get collections grouped and into a dictionary for counts
9794
var collectionItems = orgItems
9895
.SelectMany(x => x.CollectionIds,
9996
(cipher, collectionId) => new { Cipher = cipher, CollectionId = collectionId })
10097
.GroupBy(y => y.CollectionId,
10198
(key, ciphers) => new { CollectionId = key, Ciphers = ciphers });
102-
var itemLookup = collectionItems.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString()));
99+
var itemLookup = collectionItems.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString()).ToList());
100+
101+
var memberAccessCipherDetails = new ConcurrentBag<MemberAccessCipherDetails>();
103102

104-
// Loop through the org users and populate report and access data
105-
var memberAccessCipherDetails = new List<MemberAccessCipherDetails>();
106-
foreach (var user in orgUsers)
103+
Parallel.ForEach(orgUsers, user =>
107104
{
108105
var groupAccessDetails = new List<MemberAccessDetails>();
109106
var userCollectionAccessDetails = new List<MemberAccessDetails>();
107+
110108
foreach (var tCollect in orgCollectionsWithAccess)
111109
{
112-
var hasItems = itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items);
113-
var collectionCiphers = hasItems ? items.Select(x => x) : null;
114-
115-
var itemCounts = hasItems ? collectionCiphers.Count() : 0;
116-
if (tCollect.Item2.Groups.Count() > 0)
117-
{
118-
119-
var groupDetails = tCollect.Item2.Groups.Where((tCollectGroups) => user.Groups.Contains(tCollectGroups.Id)).Select(x =>
120-
new MemberAccessDetails
121-
{
122-
CollectionId = tCollect.Item1.Id,
123-
CollectionName = tCollect.Item1.Name,
124-
GroupId = x.Id,
125-
GroupName = groupNameDictionary[x.Id],
126-
ReadOnly = x.ReadOnly,
127-
HidePasswords = x.HidePasswords,
128-
Manage = x.Manage,
129-
ItemCount = itemCounts,
130-
CollectionCipherIds = items
131-
});
132-
133-
groupAccessDetails.AddRange(groupDetails);
134-
}
135-
136-
// All collections assigned to users and their permissions
137-
if (tCollect.Item2.Users.Count() > 0)
110+
if (itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items))
138111
{
139-
var userCollectionDetails = tCollect.Item2.Users.Where((tCollectUser) => tCollectUser.Id == user.Id).Select(x =>
140-
new MemberAccessDetails
141-
{
142-
CollectionId = tCollect.Item1.Id,
143-
CollectionName = tCollect.Item1.Name,
144-
ReadOnly = x.ReadOnly,
145-
HidePasswords = x.HidePasswords,
146-
Manage = x.Manage,
147-
ItemCount = itemCounts,
148-
CollectionCipherIds = items
149-
});
150-
userCollectionAccessDetails.AddRange(userCollectionDetails);
112+
var itemCounts = items.Count;
113+
114+
if (tCollect.Item2.Groups.Any())
115+
{
116+
var groupDetails = tCollect.Item2.Groups
117+
.Where(tCollectGroups => user.Groups.Contains(tCollectGroups.Id))
118+
.Select(x => new MemberAccessDetails
119+
{
120+
CollectionId = tCollect.Item1.Id,
121+
CollectionName = tCollect.Item1.Name,
122+
GroupId = x.Id,
123+
GroupName = groupNameDictionary[x.Id],
124+
ReadOnly = x.ReadOnly,
125+
HidePasswords = x.HidePasswords,
126+
Manage = x.Manage,
127+
ItemCount = itemCounts,
128+
CollectionCipherIds = items
129+
});
130+
131+
groupAccessDetails.AddRange(groupDetails);
132+
}
133+
134+
if (tCollect.Item2.Users.Any())
135+
{
136+
var userCollectionDetails = tCollect.Item2.Users
137+
.Where(tCollectUser => tCollectUser.Id == user.Id)
138+
.Select(x => new MemberAccessDetails
139+
{
140+
CollectionId = tCollect.Item1.Id,
141+
CollectionName = tCollect.Item1.Name,
142+
ReadOnly = x.ReadOnly,
143+
HidePasswords = x.HidePasswords,
144+
Manage = x.Manage,
145+
ItemCount = itemCounts,
146+
CollectionCipherIds = items
147+
});
148+
149+
userCollectionAccessDetails.AddRange(userCollectionDetails);
150+
}
151151
}
152152
}
153153

@@ -156,7 +156,6 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
156156
UserName = user.Name,
157157
Email = user.Email,
158158
TwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == user.Id).twoFactorIsEnabled,
159-
// Both the user's ResetPasswordKey must be set and the organization can UseResetPassword
160159
AccountRecoveryEnabled = !string.IsNullOrEmpty(user.ResetPasswordKey) && orgAbility.UseResetPassword,
161160
UserGuid = user.Id,
162161
UsesKeyConnector = user.UsesKeyConnector
@@ -169,9 +168,8 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
169168
userAccessDetails.AddRange(userGroups);
170169
}
171170

172-
// There can be edge cases where groups don't have a collection
173171
var groupsWithoutCollections = user.Groups.Where(x => !userAccessDetails.Any(y => x == y.GroupId));
174-
if (groupsWithoutCollections.Count() > 0)
172+
if (groupsWithoutCollections.Any())
175173
{
176174
var emptyGroups = groupsWithoutCollections.Select(x => new MemberAccessDetails
177175
{
@@ -189,20 +187,20 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
189187
}
190188
report.AccessDetails = userAccessDetails;
191189

192-
var userCiphers =
193-
report.AccessDetails
194-
.Where(x => x.ItemCount > 0)
195-
.SelectMany(y => y.CollectionCipherIds)
196-
.Distinct();
190+
var userCiphers = report.AccessDetails
191+
.Where(x => x.ItemCount > 0)
192+
.SelectMany(y => y.CollectionCipherIds)
193+
.Distinct();
197194
report.CipherIds = userCiphers;
198195
report.TotalItemCount = userCiphers.Count();
199196

200-
// Distinct items only
201197
var distinctItems = report.AccessDetails.Where(x => x.CollectionId.HasValue).Select(x => x.CollectionId).Distinct();
202198
report.CollectionsCount = distinctItems.Count();
203199
report.GroupsCount = report.AccessDetails.Select(x => x.GroupId).Where(y => y.HasValue).Distinct().Count();
200+
204201
memberAccessCipherDetails.Add(report);
205-
}
202+
});
203+
206204
return memberAccessCipherDetails;
207205
}
208206
}

0 commit comments

Comments
 (0)