mirror of
https://github.com/bitwarden/server.git
synced 2025-06-26 13:48:47 -05:00
PM-20112 limiting threads for member access query method
This commit is contained in:
parent
542941818a
commit
c38270da76
@ -60,7 +60,7 @@ 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 = GenerateAccessDataParallel(
|
var memberAccessCipherDetails = GenerateAccessDataParallelV2(
|
||||||
orgGroups,
|
orgGroups,
|
||||||
orgCollectionsWithAccess,
|
orgCollectionsWithAccess,
|
||||||
orgItems,
|
orgItems,
|
||||||
@ -203,4 +203,121 @@ public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery
|
|||||||
|
|
||||||
return memberAccessCipherDetails;
|
return memberAccessCipherDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<MemberAccessCipherDetails> GenerateAccessDataParallelV2(
|
||||||
|
ICollection<Group> orgGroups,
|
||||||
|
ICollection<Tuple<Collection, CollectionAccessDetails>> orgCollectionsWithAccess,
|
||||||
|
IEnumerable<CipherOrganizationDetailsWithCollections> orgItems,
|
||||||
|
IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled,
|
||||||
|
OrganizationAbility orgAbility)
|
||||||
|
{
|
||||||
|
var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user).ToList();
|
||||||
|
var groupNameDictionary = orgGroups.ToDictionary(x => x.Id, x => x.Name);
|
||||||
|
|
||||||
|
// Pre-compute and materialize this collection to avoid repeated work in parallel loop
|
||||||
|
var itemLookup = orgItems
|
||||||
|
.SelectMany(x => x.CollectionIds,
|
||||||
|
(cipher, collectionId) => new { Cipher = cipher, CollectionId = collectionId })
|
||||||
|
.GroupBy(y => y.CollectionId,
|
||||||
|
(key, ciphers) => new { CollectionId = key, Ciphers = ciphers })
|
||||||
|
.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString()).ToList());
|
||||||
|
|
||||||
|
var memberAccessCipherDetails = new ConcurrentBag<MemberAccessCipherDetails>();
|
||||||
|
|
||||||
|
// Add parallelism control to prevent thread exhaustion
|
||||||
|
var parallelOptions = new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount / 2)
|
||||||
|
};
|
||||||
|
|
||||||
|
Parallel.ForEach(orgUsers, parallelOptions, user =>
|
||||||
|
{
|
||||||
|
// Each thread gets its own lists - no need for thread-safe collections here
|
||||||
|
var userAccessDetails = new List<MemberAccessDetails>();
|
||||||
|
|
||||||
|
// Process group access details
|
||||||
|
var userGroupIds = new HashSet<Guid>(user.Groups);
|
||||||
|
foreach (var tCollect in orgCollectionsWithAccess)
|
||||||
|
{
|
||||||
|
if (!itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process group-based access
|
||||||
|
foreach (var groupAccess in tCollect.Item2.Groups.Where(g => userGroupIds.Contains(g.Id)))
|
||||||
|
{
|
||||||
|
userAccessDetails.Add(new MemberAccessDetails
|
||||||
|
{
|
||||||
|
CollectionId = tCollect.Item1.Id,
|
||||||
|
CollectionName = tCollect.Item1.Name,
|
||||||
|
GroupId = groupAccess.Id,
|
||||||
|
GroupName = groupNameDictionary[groupAccess.Id],
|
||||||
|
ReadOnly = groupAccess.ReadOnly,
|
||||||
|
HidePasswords = groupAccess.HidePasswords,
|
||||||
|
Manage = groupAccess.Manage,
|
||||||
|
ItemCount = items.Count,
|
||||||
|
CollectionCipherIds = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process direct user access
|
||||||
|
var userAccess = tCollect.Item2.Users.FirstOrDefault(u => u.Id == user.Id);
|
||||||
|
if (userAccess != null)
|
||||||
|
{
|
||||||
|
userAccessDetails.Add(new MemberAccessDetails
|
||||||
|
{
|
||||||
|
CollectionId = tCollect.Item1.Id,
|
||||||
|
CollectionName = tCollect.Item1.Name,
|
||||||
|
ReadOnly = userAccess.ReadOnly,
|
||||||
|
HidePasswords = userAccess.HidePasswords,
|
||||||
|
Manage = userAccess.Manage,
|
||||||
|
ItemCount = items.Count,
|
||||||
|
CollectionCipherIds = items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add empty groups
|
||||||
|
var groupsWithCollections = new HashSet<Guid>(userAccessDetails
|
||||||
|
.Where(x => x.GroupId.HasValue)
|
||||||
|
.Select(x => x.GroupId.Value));
|
||||||
|
|
||||||
|
foreach (var groupId in user.Groups.Where(g => !groupsWithCollections.Contains(g)))
|
||||||
|
{
|
||||||
|
userAccessDetails.Add(new MemberAccessDetails
|
||||||
|
{
|
||||||
|
GroupId = groupId,
|
||||||
|
GroupName = groupNameDictionary[groupId],
|
||||||
|
ItemCount = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate user ciphers efficiently
|
||||||
|
var userCipherIds = userAccessDetails
|
||||||
|
.Where(x => x.ItemCount > 0)
|
||||||
|
.SelectMany(y => y.CollectionCipherIds ?? Enumerable.Empty<string>())
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var report = new MemberAccessCipherDetails
|
||||||
|
{
|
||||||
|
UserName = user.Name,
|
||||||
|
Email = user.Email,
|
||||||
|
TwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == user.Id).twoFactorIsEnabled,
|
||||||
|
AccountRecoveryEnabled = !string.IsNullOrEmpty(user.ResetPasswordKey) && orgAbility.UseResetPassword,
|
||||||
|
UserGuid = user.Id,
|
||||||
|
UsesKeyConnector = user.UsesKeyConnector,
|
||||||
|
AccessDetails = userAccessDetails,
|
||||||
|
CipherIds = userCipherIds,
|
||||||
|
TotalItemCount = userCipherIds.Count,
|
||||||
|
CollectionsCount = userAccessDetails.Where(x => x.CollectionId.HasValue).Select(x => x.CollectionId).Distinct().Count(),
|
||||||
|
GroupsCount = userAccessDetails.Select(x => x.GroupId).Where(y => y.HasValue).Distinct().Count()
|
||||||
|
};
|
||||||
|
|
||||||
|
memberAccessCipherDetails.Add(report);
|
||||||
|
});
|
||||||
|
|
||||||
|
return memberAccessCipherDetails;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user