1
- using Bit . Core . AdminConsole . Entities ;
1
+ using System . Collections . Concurrent ;
2
+ using Bit . Core . AdminConsole . Entities ;
2
3
using Bit . Core . AdminConsole . Repositories ;
3
4
using Bit . Core . Auth . UserFeatures . TwoFactorAuth . Interfaces ;
4
5
using Bit . Core . Entities ;
@@ -59,95 +60,94 @@ public async Task<IEnumerable<MemberAccessCipherDetails>> GetMemberAccessCipherD
59
60
var orgItems = await _organizationCiphersQuery . GetAllOrganizationCiphers ( request . OrganizationId ) ;
60
61
var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery . TwoFactorIsEnabledAsync ( orgUsers ) ;
61
62
62
- var memberAccessCipherDetails = GenerateAccessData (
63
+ var memberAccessCipherDetails = GenerateAccessDataParallel (
63
64
orgGroups ,
64
65
orgCollectionsWithAccess ,
65
66
orgItems ,
66
67
organizationUsersTwoFactorEnabled ,
67
- orgAbility
68
- ) ;
68
+ orgAbility ) ;
69
69
70
70
return memberAccessCipherDetails ;
71
71
}
72
72
73
73
/// <summary>
74
74
/// 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.
76
76
/// Child collection includes detailed information on the user and group collections along
77
- /// with their permissions.
77
+ /// with their permissions.
78
78
/// </summary>
79
79
/// <param name="orgGroups">Organization groups collection</param>
80
80
/// <param name="orgCollectionsWithAccess">Collections for the organization and the groups/users and permissions</param>
81
81
/// <param name="orgItems">Cipher items for the organization with the collections associated with them</param>
82
82
/// <param name="organizationUsersTwoFactorEnabled">Organization users and two factor status</param>
83
83
/// <param name="orgAbility">Organization ability for account recovery status</param>
84
84
/// <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 )
91
91
{
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 ( ) ;
94
93
var groupNameDictionary = orgGroups . ToDictionary ( x => x . Id , x => x . Name ) ;
95
-
96
- // Get collections grouped and into a dictionary for counts
97
94
var collectionItems = orgItems
98
95
. SelectMany ( x => x . CollectionIds ,
99
96
( cipher , collectionId ) => new { Cipher = cipher , CollectionId = collectionId } )
100
97
. GroupBy ( y => y . CollectionId ,
101
98
( 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 > ( ) ;
103
102
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 =>
107
104
{
108
105
var groupAccessDetails = new List < MemberAccessDetails > ( ) ;
109
106
var userCollectionAccessDetails = new List < MemberAccessDetails > ( ) ;
107
+
110
108
foreach ( var tCollect in orgCollectionsWithAccess )
111
109
{
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 ) )
138
111
{
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
+ }
151
151
}
152
152
}
153
153
@@ -156,7 +156,6 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
156
156
UserName = user . Name ,
157
157
Email = user . Email ,
158
158
TwoFactorEnabled = organizationUsersTwoFactorEnabled . FirstOrDefault ( u => u . user . Id == user . Id ) . twoFactorIsEnabled ,
159
- // Both the user's ResetPasswordKey must be set and the organization can UseResetPassword
160
159
AccountRecoveryEnabled = ! string . IsNullOrEmpty ( user . ResetPasswordKey ) && orgAbility . UseResetPassword ,
161
160
UserGuid = user . Id ,
162
161
UsesKeyConnector = user . UsesKeyConnector
@@ -169,9 +168,8 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
169
168
userAccessDetails . AddRange ( userGroups ) ;
170
169
}
171
170
172
- // There can be edge cases where groups don't have a collection
173
171
var groupsWithoutCollections = user . Groups . Where ( x => ! userAccessDetails . Any ( y => x == y . GroupId ) ) ;
174
- if ( groupsWithoutCollections . Count ( ) > 0 )
172
+ if ( groupsWithoutCollections . Any ( ) )
175
173
{
176
174
var emptyGroups = groupsWithoutCollections . Select ( x => new MemberAccessDetails
177
175
{
@@ -189,20 +187,20 @@ private IEnumerable<MemberAccessCipherDetails> GenerateAccessData(
189
187
}
190
188
report . AccessDetails = userAccessDetails ;
191
189
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 ( ) ;
197
194
report . CipherIds = userCiphers ;
198
195
report . TotalItemCount = userCiphers . Count ( ) ;
199
196
200
- // Distinct items only
201
197
var distinctItems = report . AccessDetails . Where ( x => x . CollectionId . HasValue ) . Select ( x => x . CollectionId ) . Distinct ( ) ;
202
198
report . CollectionsCount = distinctItems . Count ( ) ;
203
199
report . GroupsCount = report . AccessDetails . Select ( x => x . GroupId ) . Where ( y => y . HasValue ) . Distinct ( ) . Count ( ) ;
200
+
204
201
memberAccessCipherDetails . Add ( report ) ;
205
- }
202
+ } ) ;
203
+
206
204
return memberAccessCipherDetails ;
207
205
}
208
206
}
0 commit comments