Skip to content

Commit aeed94b

Browse files
committed
Refactor BulkAutomaticallyConfirmOrganizationUsers functionality and update related tests
- Updated the BulkAutoConfirmOnLogin feature flag key in Constants.cs. - Enhanced BulkAutomaticallyConfirmOrganizationUsersValidator with additional comments for clarity. - Refactored the OrganizationUserRepository to use a transaction for confirming organization users and updated the logic for handling confirmed IDs. - Modified the stored procedure to remove the default value for the @type parameter. - Improved unit tests for BulkAutomaticallyConfirmOrganizationUsersCommand to streamline request building and validation handling.
1 parent 297bfff commit aeed94b

5 files changed

Lines changed: 119 additions & 80 deletions

File tree

src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/BulkAutomaticallyConfirmOrganizationUsersValidator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public async Task<IEnumerable<ValidationResult<AutomaticallyConfirmOrganizationU
7070
var autoConfirmPolicyByUserId = autoConfirmPolicyResults.ToDictionary(r => r.UserId, r => r.Requirement);
7171

7272
// Group all org memberships by the user so we can do the multi-org check in memory.
73+
// GetManyByManyUsersAsync queries by UserId only (WHERE UserId IN (...)), matching the
74+
// single-user path's GetManyByUserAsync. Neither returns Invited rows with a null UserId.
75+
// The || Email fallback in AutomaticUserConfirmationPolicyEnforcementValidator is only
76+
// used to locate the current org-user row defensively and does not extend the data fetched here.
7377
var orgUsersByUserId = allOrgUsersForUsers
7478
.GroupBy(ou => ou.UserId!.Value)
7579
.ToDictionary(g => g.Key, g => g.Count());

src/Core/Constants.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,6 @@ public static class FeatureFlagKeys
162162
public const string PolicyRequirements = "pm-14439-policy-requirements";
163163
public const string ScimInviteUserOptimization = "pm-16811-optimize-invite-user-flow-to-fail-fast";
164164
public const string AutomaticConfirmUsers = "pm-19934-auto-confirm-organization-users";
165-
// TODO: Replace with the real LaunchDarkly feature flag key in pm-XXXXX-bulk-auto-confirm-on-login format once the flag is created.
166-
public const string BulkAutoConfirmOnLogin = "bulk-auto-confirm-on-login";
167165
public const string AdminResetTwoFactor = "pm-15489-reset-two-factor-account-recovery";
168166
public const string BulkAutoConfirmOnLogin = "pm-35803-browser-auto-confirm-log-in";
169167
public const string PublicMembersInviteRefactor = "pm-33398-refactor-members-invite-org-users-command";

src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,24 +1056,33 @@ public async Task<ICollection<Guid>> ConfirmManyOrganizationUsersAsync(
10561056
var orgUserIds = usersToConfirmList.Select(u => u.OrganizationUserId).ToList();
10571057
var keyByOrgUserId = usersToConfirmList.ToDictionary(u => u.OrganizationUserId, u => u.Key);
10581058

1059-
var confirmedIds = await dbContext.OrganizationUsers
1059+
await using var transaction = await dbContext.Database.BeginTransactionAsync();
1060+
1061+
var rowsToUpdate = await dbContext.OrganizationUsers
10601062
.Where(ou => orgUserIds.Contains(ou.Id) && ou.Status == OrganizationUserStatusType.Accepted)
1061-
.Select(ou => ou.Id)
10621063
.ToListAsync();
10631064

1064-
if (confirmedIds.Count == 0)
1065+
if (rowsToUpdate.Count == 0)
10651066
{
1066-
return confirmedIds;
1067+
await transaction.RollbackAsync();
1068+
return [];
10671069
}
10681070

1069-
await dbContext.OrganizationUsers
1070-
.Where(ou => confirmedIds.Contains(ou.Id))
1071-
.ExecuteUpdateAsync(s => s
1072-
.SetProperty(ou => ou.Status, OrganizationUserStatusType.Confirmed)
1073-
.SetProperty(ou => ou.Key, ou => keyByOrgUserId[ou.Id]));
1071+
var revisionDate = DateTime.UtcNow;
1072+
foreach (var ou in rowsToUpdate)
1073+
{
1074+
ou.Status = OrganizationUserStatusType.Confirmed;
1075+
ou.Key = keyByOrgUserId[ou.Id];
1076+
ou.RevisionDate = revisionDate;
1077+
}
1078+
1079+
await dbContext.SaveChangesAsync();
10741080

1081+
var confirmedIds = rowsToUpdate.Select(o => o.Id).ToList();
10751082
await dbContext.UserBumpAccountRevisionDateByOrganizationUserIdsAsync(confirmedIds);
10761083

1084+
await transaction.CommitAsync();
1085+
10771086
return confirmedIds;
10781087
}
10791088

src/Sql/dbo/Stored Procedures/OrganizationUser_ReadByOrganizationId.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CREATE PROCEDURE [dbo].[OrganizationUser_ReadByOrganizationId]
22
@OrganizationId UNIQUEIDENTIFIER,
3-
@Type TINYINT = NULL
3+
@Type TINYINT
44
AS
55
BEGIN
66
SET NOCOUNT ON
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
using Bit.Core.AdminConsole.Entities;
22
using Bit.Core.AdminConsole.Models.Data;
3+
using Bit.Core.AdminConsole.Models.Data.OrganizationUsers;
34
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
4-
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
5-
using Bit.Core.AdminConsole.Utilities.v2.Results;
5+
using Bit.Core.AdminConsole.Utilities.v2.Validation;
66
using Bit.Core.Entities;
77
using Bit.Core.Enums;
8+
using Bit.Core.Repositories;
89
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
910
using Bit.Test.Common.AutoFixture;
1011
using Bit.Test.Common.AutoFixture.Attributes;
1112
using NSubstitute;
12-
using OneOf.Types;
1313
using Xunit;
1414

1515
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
@@ -31,40 +31,23 @@ public async Task BulkAutomaticallyConfirmOrganizationUsersAsync_AllSucceed_Retu
3131
orgUser1.OrganizationId = organization.Id;
3232
orgUser2.OrganizationId = organization.Id;
3333

34-
var requests = new[]
35-
{
36-
new AutomaticallyConfirmOrganizationUserRequest
37-
{
38-
OrganizationUserId = orgUser1.Id,
39-
OrganizationId = organization.Id,
40-
Key = key1,
41-
DefaultUserCollectionName = string.Empty,
42-
PerformedBy = new StandardUser(performingUserId, false)
43-
},
44-
new AutomaticallyConfirmOrganizationUserRequest
45-
{
46-
OrganizationUserId = orgUser2.Id,
47-
OrganizationId = organization.Id,
48-
Key = key2,
49-
DefaultUserCollectionName = string.Empty,
50-
PerformedBy = new StandardUser(performingUserId, false)
51-
}
52-
};
34+
var requests = BuildRequests(organization.Id, performingUserId,
35+
(orgUser1.Id, key1),
36+
(orgUser2.Id, key2));
37+
38+
SetupCommonMocks(sutProvider, organization, [orgUser1, orgUser2]);
39+
SetupValidatorAllValid(sutProvider, requests, [orgUser1, orgUser2], organization);
5340

54-
sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
55-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Any<AutomaticallyConfirmOrganizationUserRequest>())
56-
.Returns(new CommandResult(new None()));
41+
sutProvider.GetDependency<IOrganizationUserRepository>()
42+
.ConfirmManyOrganizationUsersAsync(Arg.Any<IEnumerable<AcceptedOrganizationUserToConfirm>>())
43+
.Returns([orgUser1.Id, orgUser2.Id]);
5744

5845
// Act
5946
var results = await sutProvider.Sut.BulkAutomaticallyConfirmOrganizationUsersAsync(requests);
6047

6148
// Assert
6249
Assert.Equal(2, results.Count);
6350
Assert.All(results, r => Assert.Null(r.Error));
64-
65-
await sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
66-
.Received(2)
67-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Any<AutomaticallyConfirmOrganizationUserRequest>());
6851
}
6952

7053
[Theory, BitAutoData]
@@ -81,35 +64,26 @@ public async Task BulkAutomaticallyConfirmOrganizationUsersAsync_OneFailsOneSucc
8164
orgUser1.OrganizationId = organization.Id;
8265
orgUser2.OrganizationId = organization.Id;
8366

84-
var request1 = new AutomaticallyConfirmOrganizationUserRequest
85-
{
86-
OrganizationUserId = orgUser1.Id,
87-
OrganizationId = organization.Id,
88-
Key = key1,
89-
DefaultUserCollectionName = string.Empty,
90-
PerformedBy = new StandardUser(performingUserId, false)
91-
};
92-
var request2 = new AutomaticallyConfirmOrganizationUserRequest
93-
{
94-
OrganizationUserId = orgUser2.Id,
95-
OrganizationId = organization.Id,
96-
Key = key2,
97-
DefaultUserCollectionName = string.Empty,
98-
PerformedBy = new StandardUser(performingUserId, false)
99-
};
67+
var requests = BuildRequests(organization.Id, performingUserId,
68+
(orgUser1.Id, key1),
69+
(orgUser2.Id, key2));
10070

101-
sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
102-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Is<AutomaticallyConfirmOrganizationUserRequest>(r =>
103-
r.OrganizationUserId == orgUser1.Id))
104-
.Returns(new CommandResult(new None()));
71+
SetupCommonMocks(sutProvider, organization, [orgUser1, orgUser2]);
10572

106-
sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
107-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Is<AutomaticallyConfirmOrganizationUserRequest>(r =>
108-
r.OrganizationUserId == orgUser2.Id))
109-
.Returns(new CommandResult(new UserIsNotAccepted()));
73+
// orgUser1 passes validation; orgUser2 fails
74+
sutProvider.GetDependency<IBulkAutomaticallyConfirmOrganizationUsersValidator>()
75+
.ValidateManyAsync(Arg.Any<IEnumerable<AutomaticallyConfirmOrganizationUserValidationRequest>>())
76+
.Returns([
77+
ValidationResultHelpers.Valid(BuildValidationRequest(requests[0], orgUser1, organization)),
78+
ValidationResultHelpers.Invalid(BuildValidationRequest(requests[1], orgUser2, organization), new UserIsNotAccepted())
79+
]);
80+
81+
sutProvider.GetDependency<IOrganizationUserRepository>()
82+
.ConfirmManyOrganizationUsersAsync(Arg.Any<IEnumerable<AcceptedOrganizationUserToConfirm>>())
83+
.Returns([orgUser1.Id]);
11084

11185
// Act
112-
var results = await sutProvider.Sut.BulkAutomaticallyConfirmOrganizationUsersAsync([request1, request2]);
86+
var results = await sutProvider.Sut.BulkAutomaticallyConfirmOrganizationUsersAsync(requests);
11387

11488
// Assert
11589
Assert.Equal(2, results.Count);
@@ -131,9 +105,9 @@ public async Task BulkAutomaticallyConfirmOrganizationUsersAsync_EmptyList_Retur
131105
// Assert
132106
Assert.Empty(results);
133107

134-
await sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
108+
await sutProvider.GetDependency<IBulkAutomaticallyConfirmOrganizationUsersValidator>()
135109
.DidNotReceive()
136-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Any<AutomaticallyConfirmOrganizationUserRequest>());
110+
.ValidateManyAsync(Arg.Any<IEnumerable<AutomaticallyConfirmOrganizationUserValidationRequest>>());
137111
}
138112

139113
[Theory, BitAutoData]
@@ -147,24 +121,78 @@ public async Task BulkAutomaticallyConfirmOrganizationUsersAsync_PreservesOrgani
147121
// Arrange
148122
orgUser.OrganizationId = organization.Id;
149123

150-
var request = new AutomaticallyConfirmOrganizationUserRequest
151-
{
152-
OrganizationUserId = orgUser.Id,
153-
OrganizationId = organization.Id,
154-
Key = key,
155-
DefaultUserCollectionName = string.Empty,
156-
PerformedBy = new StandardUser(performingUserId, false)
157-
};
124+
var requests = BuildRequests(organization.Id, performingUserId, (orgUser.Id, key));
158125

159-
sutProvider.GetDependency<IAutomaticallyConfirmOrganizationUserCommand>()
160-
.AutomaticallyConfirmOrganizationUserAsync(Arg.Any<AutomaticallyConfirmOrganizationUserRequest>())
161-
.Returns(new CommandResult(new None()));
126+
SetupCommonMocks(sutProvider, organization, [orgUser]);
127+
SetupValidatorAllValid(sutProvider, requests, [orgUser], organization);
128+
129+
sutProvider.GetDependency<IOrganizationUserRepository>()
130+
.ConfirmManyOrganizationUsersAsync(Arg.Any<IEnumerable<AcceptedOrganizationUserToConfirm>>())
131+
.Returns([orgUser.Id]);
162132

163133
// Act
164-
var results = await sutProvider.Sut.BulkAutomaticallyConfirmOrganizationUsersAsync([request]);
134+
var results = await sutProvider.Sut.BulkAutomaticallyConfirmOrganizationUsersAsync(requests);
165135

166136
// Assert
167137
Assert.Single(results);
168138
Assert.Equal(orgUser.Id, results[0].OrganizationUserId);
169139
}
140+
141+
private static List<AutomaticallyConfirmOrganizationUserRequest> BuildRequests(
142+
Guid organizationId,
143+
Guid performingUserId,
144+
params (Guid OrgUserId, string Key)[] userKeys) =>
145+
userKeys.Select(u => new AutomaticallyConfirmOrganizationUserRequest
146+
{
147+
OrganizationUserId = u.OrgUserId,
148+
OrganizationId = organizationId,
149+
Key = u.Key,
150+
DefaultUserCollectionName = string.Empty,
151+
PerformedBy = new StandardUser(performingUserId, false)
152+
}).ToList();
153+
154+
private static AutomaticallyConfirmOrganizationUserValidationRequest BuildValidationRequest(
155+
AutomaticallyConfirmOrganizationUserRequest request,
156+
OrganizationUser orgUser,
157+
Organization organization) =>
158+
new()
159+
{
160+
OrganizationUserId = request.OrganizationUserId,
161+
OrganizationId = request.OrganizationId,
162+
Key = request.Key,
163+
DefaultUserCollectionName = request.DefaultUserCollectionName,
164+
PerformedBy = request.PerformedBy,
165+
OrganizationUser = orgUser,
166+
Organization = organization
167+
};
168+
169+
private static void SetupCommonMocks(
170+
SutProvider<BulkAutomaticallyConfirmOrganizationUsersCommand> sutProvider,
171+
Organization organization,
172+
ICollection<OrganizationUser> orgUsers)
173+
{
174+
sutProvider.GetDependency<IOrganizationRepository>()
175+
.GetByIdAsync(organization.Id)
176+
.Returns(organization);
177+
178+
sutProvider.GetDependency<IOrganizationUserRepository>()
179+
.GetManyAsync(Arg.Any<IEnumerable<Guid>>())
180+
.Returns(orgUsers);
181+
}
182+
183+
private static void SetupValidatorAllValid(
184+
SutProvider<BulkAutomaticallyConfirmOrganizationUsersCommand> sutProvider,
185+
IEnumerable<AutomaticallyConfirmOrganizationUserRequest> requests,
186+
ICollection<OrganizationUser> orgUsers,
187+
Organization organization)
188+
{
189+
var orgUserById = orgUsers.ToDictionary(ou => ou.Id);
190+
var validationResults = requests
191+
.Select(r => ValidationResultHelpers.Valid(BuildValidationRequest(r, orgUserById[r.OrganizationUserId], organization)))
192+
.ToList<ValidationResult<AutomaticallyConfirmOrganizationUserValidationRequest>>();
193+
194+
sutProvider.GetDependency<IBulkAutomaticallyConfirmOrganizationUsersValidator>()
195+
.ValidateManyAsync(Arg.Any<IEnumerable<AutomaticallyConfirmOrganizationUserValidationRequest>>())
196+
.Returns(validationResults);
197+
}
170198
}

0 commit comments

Comments
 (0)