diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/IInviteOrganizationUsersCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/IInviteOrganizationUsersCommand.cs
index e948f99a73..3e4c7652a5 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/IInviteOrganizationUsersCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/IInviteOrganizationUsersCommand.cs
@@ -18,6 +18,5 @@ public interface IInviteOrganizationUsersCommand
/// Contains the details for inviting a single organization user via email.
///
/// Response from InviteScimOrganiation
- Task> InviteScimOrganizationUserAsync(
- InviteOrganizationUsersRequest request);
+ Task> InviteScimOrganizationUserAsync(InviteOrganizationUsersRequest request);
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUsersCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUsersCommand.cs
index 6e1fa801d9..a9e862abda 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUsersCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUsersCommand.cs
@@ -112,7 +112,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
await AdjustPasswordManagerSeatsAsync(validatedRequest, organization);
- await AdjustSecretsManagerSeatsAsync(validatedRequest, organization);
+ await AdjustSecretsManagerSeatsAsync(validatedRequest);
await SendAdditionalEmailsAsync(validatedRequest, organization);
@@ -126,7 +126,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
await organizationUserRepository.DeleteManyAsync(organizationUserToInviteEntities.Select(x => x.OrganizationUser.Id));
- await RevertSecretsManagerChangesAsync(validatedRequest, organization);
+ await RevertSecretsManagerChangesAsync(validatedRequest, organization, validatedRequest.Value.InviteOrganization.SmSeats);
await RevertPasswordManagerChangesAsync(validatedRequest, organization);
@@ -164,16 +164,19 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
}
}
- private async Task RevertSecretsManagerChangesAsync(Valid validatedResult, Organization organization)
+ private async Task RevertSecretsManagerChangesAsync(Valid validatedResult, Organization organization, int? initialSmSeats)
{
- if (validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd < 0)
+ if (validatedResult.Value.InviteOrganization.UseSecretsManager && validatedResult.Value.SecretsManagerSubscriptionUpdate.SmSeatsChanged)
{
- var updateRevert = new SecretsManagerSubscriptionUpdate(organization, validatedResult.Value.InviteOrganization.Plan, false)
+ var smSubscriptionUpdateRevert = new SecretsManagerSubscriptionUpdate(
+ organization: organization,
+ plan: validatedResult.Value.InviteOrganization.Plan,
+ autoscaling: false)
{
- SmSeats = validatedResult.Value.SecretsManagerSubscriptionUpdate.Seats
+ SmSeats = initialSmSeats
};
- await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(updateRevert);
+ await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdateRevert);
}
}
@@ -234,17 +237,14 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
.Select(u => u.Email).Distinct();
}
- private async Task AdjustSecretsManagerSeatsAsync(Valid validatedResult, Organization organization)
+ private async Task AdjustSecretsManagerSeatsAsync(Valid validatedResult)
{
- if (validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd <= 0)
+ if (validatedResult.Value.SecretsManagerSubscriptionUpdate?.SmSeatsChanged is not true)
{
return;
}
- var subscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, validatedResult.Value.InviteOrganization.Plan, true)
- .AdjustSeats(validatedResult.Value.SecretsManagerSubscriptionUpdate.SeatsRequiredToAdd);
-
- await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(subscriptionUpdate);
+ await updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(validatedResult.Value.SecretsManagerSubscriptionUpdate);
}
private async Task AdjustPasswordManagerSeatsAsync(Valid validatedResult, Organization organization)
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Models/InviteUserOrganizationValidationRequest.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Models/InviteUserOrganizationValidationRequest.cs
index 733dd01bf4..a47252859c 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Models/InviteUserOrganizationValidationRequest.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Models/InviteUserOrganizationValidationRequest.cs
@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
+using Bit.Core.Models.Business;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/CannotAutoScaleOnSelfHostError.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/CannotAutoScaleOnSelfHostError.cs
index f1a4e5d6d3..0624ffe027 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/CannotAutoScaleOnSelfHostError.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/CannotAutoScaleOnSelfHostError.cs
@@ -1,9 +1,8 @@
using Bit.Core.AdminConsole.Errors;
-using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
-public record CannotAutoScaleOnSelfHostError(IGlobalSettings InvalidSettings) : Error(Code, InvalidSettings)
+public record CannotAutoScaleOnSelfHostError(EnvironmentRequest Invalid) : Error(Code, Invalid)
{
public const string Code = "Cannot auto scale self-host.";
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentRequest.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentRequest.cs
new file mode 100644
index 0000000000..9c1ff43d17
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentRequest.cs
@@ -0,0 +1,18 @@
+#nullable enable
+
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
+using Bit.Core.Settings;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
+
+public class EnvironmentRequest
+{
+ public bool IsSelfHosted { get; init; }
+ public PasswordManagerSubscriptionUpdate PasswordManagerSubscriptionUpdate { get; init; }
+
+ public EnvironmentRequest(IGlobalSettings globalSettings, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate)
+ {
+ IsSelfHosted = globalSettings.SelfHosted;
+ PasswordManagerSubscriptionUpdate = passwordManagerSubscriptionUpdate;
+ }
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentValidator.cs
new file mode 100644
index 0000000000..a568481a8e
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/GlobalSettings/EnvironmentValidator.cs
@@ -0,0 +1,13 @@
+using Bit.Core.AdminConsole.Shared.Validation;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
+
+public interface IEnvironmentValidator : IValidator;
+
+public class EnvironmentValidator : IEnvironmentValidator
+{
+ public async Task> ValidateAsync(EnvironmentRequest value) =>
+ value.IsSelfHosted && value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd > 0 ?
+ new Invalid(new CannotAutoScaleOnSelfHostError(value)) :
+ new Valid(value);
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUserValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUserValidator.cs
index 8575e3825b..ce485d119b 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUserValidator.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUserValidator.cs
@@ -1,89 +1,64 @@
-using Bit.Core.AdminConsole.Models.Business;
+using Bit.Core.AdminConsole.Errors;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
-using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Shared.Validation;
+using Bit.Core.Models.Business;
+using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
-using Bit.Core.Services;
-using Bit.Core.Settings;
-using SecretsManagerSubscriptionUpdate = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager.SecretsManagerSubscriptionUpdate;
-
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
public interface IInviteUsersValidator : IValidator;
public class InviteUsersValidator(
- IGlobalSettings globalSettings,
- IProviderRepository providerRepository,
- IPaymentService paymentService,
- IOrganizationRepository organizationRepository) : IInviteUsersValidator
+ IOrganizationRepository organizationRepository,
+ IPasswordManagerInviteUserValidator passwordManagerInviteUserValidator,
+ IUpdateSecretsManagerSubscriptionCommand secretsManagerSubscriptionCommand) : IInviteUsersValidator
{
public async Task> ValidateAsync(InviteUserOrganizationValidationRequest request)
{
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request);
- var passwordManagerValidationResult = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
+
+ var passwordManagerValidationResult = await passwordManagerInviteUserValidator.ValidateAsync(subscriptionUpdate);
if (passwordManagerValidationResult is Invalid invalidSubscriptionUpdate)
{
return invalidSubscriptionUpdate.Map(request);
}
- if (ValidateEnvironment(globalSettings, subscriptionUpdate) is Invalid invalidEnvironment)
+ if (request.InviteOrganization.UseSecretsManager && request.Invites.Any(x => x.AccessSecretsManager))
{
- return invalidEnvironment.Map(request);
- }
-
- var organizationValidationResult = InviteUserOrganizationValidator.Validate(request.InviteOrganization, subscriptionUpdate);
-
- if (organizationValidationResult is Invalid organizationValidation)
- {
- return organizationValidation.Map(request);
- }
-
- var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(request, subscriptionUpdate);
-
- var secretsManagerValidationResult = SecretsManagerInviteUserValidation.Validate(smSubscriptionUpdate);
-
- if (secretsManagerValidationResult is Invalid invalidSmSubscriptionUpdate)
- {
- return invalidSmSubscriptionUpdate.Map(request);
- }
-
- var provider = await providerRepository.GetByOrganizationIdAsync(request.InviteOrganization.OrganizationId);
- if (provider is not null)
- {
- var providerValidationResult = InvitingUserOrganizationProviderValidator.Validate(new InviteOrganizationProvider(provider));
-
- if (providerValidationResult is Invalid invalidProviderValidation)
- {
- return invalidProviderValidation.Map(request);
- }
- }
-
- var paymentSubscription = await paymentService.GetSubscriptionAsync(
- await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
-
- var paymentValidationResult = InviteUserPaymentValidation.Validate(
- new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
-
- if (paymentValidationResult is Invalid invalidPaymentValidation)
- {
- return invalidPaymentValidation.Map(request);
+ return await ValidateSecretsManagerSubscriptionUpdateAsync(request, subscriptionUpdate);
}
return new Valid(new InviteUserOrganizationValidationRequest(
request,
subscriptionUpdate,
- smSubscriptionUpdate));
+ null));
}
- public static ValidationResult ValidateEnvironment(IGlobalSettings globalSettings, PasswordManagerSubscriptionUpdate subscriptionUpdate) =>
- globalSettings.SelfHosted && subscriptionUpdate.SeatsRequiredToAdd > 0
- ? new Invalid(new CannotAutoScaleOnSelfHostError(globalSettings))
- : new Valid(globalSettings);
+ private async Task> ValidateSecretsManagerSubscriptionUpdateAsync(
+ InviteUserOrganizationValidationRequest request,
+ PasswordManagerSubscriptionUpdate subscriptionUpdate)
+ {
+ try
+ {
+ var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(
+ organization: await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId),
+ plan: request.InviteOrganization.Plan,
+ autoscaling: true)
+ .AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager));
+
+
+ await secretsManagerSubscriptionCommand.ValidateUpdateAsync(smSubscriptionUpdate);
+
+ return new Valid(new InviteUserOrganizationValidationRequest(
+ request,
+ subscriptionUpdate,
+ smSubscriptionUpdate));
+ }
+ catch (Exception ex)
+ {
+ return new Invalid(new Error(ex.Message, request));
+ }
+ }
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Organization/InviteUserOrganizationValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Organization/InviteUserOrganizationValidator.cs
index 1703bde9d3..730fa5bde7 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Organization/InviteUserOrganizationValidator.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Organization/InviteUserOrganizationValidator.cs
@@ -1,15 +1,15 @@
using Bit.Core.AdminConsole.Models.Business;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.Shared.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
-public static class InviteUserOrganizationValidator
+public interface IInviteUserOrganizationValidator : IValidator;
+
+public class InviteUserOrganizationValidator : IInviteUserOrganizationValidator
{
- public static ValidationResult Validate(InviteOrganization inviteOrganization,
- PasswordManagerSubscriptionUpdate subscriptionUpdate)
+ public async Task> ValidateAsync(InviteOrganization inviteOrganization)
{
- if (inviteOrganization.Seats is null || subscriptionUpdate.SeatsRequiredToAdd is 0)
+ if (inviteOrganization.Seats is null)
{
return new Valid(inviteOrganization);
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerInviteUserValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerInviteUserValidator.cs
index db72e7cd23..7101c6671d 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerInviteUserValidator.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerInviteUserValidator.cs
@@ -1,15 +1,33 @@
-using Bit.Core.AdminConsole.Shared.Validation;
+using Bit.Core.AdminConsole.Models.Business;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
+using Bit.Core.AdminConsole.Repositories;
+using Bit.Core.AdminConsole.Shared.Validation;
+using Bit.Core.Repositories;
+using Bit.Core.Services;
+using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
-public static class PasswordManagerInviteUserValidator
+public interface IPasswordManagerInviteUserValidator : IValidator;
+
+public class PasswordManagerInviteUserValidator(
+ IGlobalSettings globalSettings,
+ IEnvironmentValidator environmentValidator,
+ IInviteUserOrganizationValidator inviteUserOrganizationValidator,
+ IProviderRepository providerRepository,
+ IPaymentService paymentService,
+ IOrganizationRepository organizationRepository
+ ) : IPasswordManagerInviteUserValidator
{
///
/// This is for validating if the organization can add additional users.
///
///
///
- public static ValidationResult Validate(PasswordManagerSubscriptionUpdate subscriptionUpdate)
+ public static ValidationResult ValidatePasswordManager(PasswordManagerSubscriptionUpdate subscriptionUpdate)
{
if (subscriptionUpdate.Seats is null)
{
@@ -43,4 +61,52 @@ public static class PasswordManagerInviteUserValidator
return new Valid(subscriptionUpdate);
}
+
+ public async Task> ValidateAsync(PasswordManagerSubscriptionUpdate request)
+ {
+ switch (ValidatePasswordManager(request))
+ {
+ case Valid valid
+ when valid.Value.SeatsRequiredToAdd is 0:
+ return new Valid(request);
+ case Invalid invalid:
+ return invalid;
+ }
+
+ if (await environmentValidator.ValidateAsync(new EnvironmentRequest(globalSettings, request)) is Invalid invalidEnvironment)
+ {
+ return invalidEnvironment.Map(request);
+ }
+
+ var organizationValidationResult = await inviteUserOrganizationValidator.ValidateAsync(request.InviteOrganization);
+
+ if (organizationValidationResult is Invalid organizationValidation)
+ {
+ return organizationValidation.Map(request);
+ }
+
+ var provider = await providerRepository.GetByOrganizationIdAsync(request.InviteOrganization.OrganizationId);
+ if (provider is not null)
+ {
+ var providerValidationResult = InvitingUserOrganizationProviderValidator.Validate(new InviteOrganizationProvider(provider));
+
+ if (providerValidationResult is Invalid invalidProviderValidation)
+ {
+ return invalidProviderValidation.Map(request);
+ }
+ }
+
+ var paymentSubscription = await paymentService.GetSubscriptionAsync(
+ await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId));
+
+ var paymentValidationResult = InviteUserPaymentValidation.Validate(
+ new PaymentsSubscription(paymentSubscription, request.InviteOrganization));
+
+ if (paymentValidationResult is Invalid invalidPaymentValidation)
+ {
+ return invalidPaymentValidation.Map(request);
+ }
+
+ return new Valid(request);
+ }
}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs
index 281965c0af..43eb7c1bd3 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManager/PasswordManagerSubscriptionUpdate.cs
@@ -32,7 +32,7 @@ public class PasswordManagerSubscriptionUpdate
public int? AvailableSeats => Seats - OccupiedSeats;
///
- /// Number of seats to scale the organization to.
+ /// Number of seats to scale the organization by.
///
/// If Organization has no seat limit (Seats is null), then there are no new seats to add.
///
@@ -50,17 +50,21 @@ public class PasswordManagerSubscriptionUpdate
public Plan.PasswordManagerPlanFeatures PasswordManagerPlan { get; }
+ public InviteOrganization InviteOrganization { get; }
+
private PasswordManagerSubscriptionUpdate(int? organizationSeats,
int? organizationAutoScaleSeatLimit,
int currentSeats,
int newUsersToAdd,
- Plan.PasswordManagerPlanFeatures plan)
+ Plan.PasswordManagerPlanFeatures plan,
+ InviteOrganization inviteOrganization)
{
Seats = organizationSeats;
MaxAutoScaleSeats = organizationAutoScaleSeatLimit;
OccupiedSeats = currentSeats;
NewUsersToAdd = newUsersToAdd;
PasswordManagerPlan = plan;
+ InviteOrganization = inviteOrganization;
}
public PasswordManagerSubscriptionUpdate(InviteOrganization inviteOrganization, int occupiedSeats, int newUsersToAdd) :
@@ -69,7 +73,8 @@ public class PasswordManagerSubscriptionUpdate
organizationAutoScaleSeatLimit: inviteOrganization.MaxAutoScaleSeats,
currentSeats: occupiedSeats,
newUsersToAdd: newUsersToAdd,
- plan: inviteOrganization.Plan.PasswordManager)
+ plan: inviteOrganization.Plan.PasswordManager,
+ inviteOrganization: inviteOrganization)
{ }
public PasswordManagerSubscriptionUpdate(InviteUserOrganizationValidationRequest validationRequest) :
@@ -78,6 +83,7 @@ public class PasswordManagerSubscriptionUpdate
organizationAutoScaleSeatLimit: validationRequest.InviteOrganization.MaxAutoScaleSeats,
currentSeats: validationRequest.OccupiedPmSeats,
newUsersToAdd: validationRequest.Invites.Length,
- plan: validationRequest.InviteOrganization.Plan.PasswordManager)
+ plan: validationRequest.InviteOrganization.Plan.PasswordManager,
+ inviteOrganization: validationRequest.InviteOrganization)
{ }
}
diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
index 30028c91c9..4fe8ee9237 100644
--- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
+++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
@@ -15,6 +15,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.GlobalSettings;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
+using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.Models.Business.Tokenables;
using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
@@ -175,8 +178,12 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
- services.AddScoped();
services.AddScoped();
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
}
// TODO: move to OrganizationSubscriptionServiceCollectionExtensions when OrganizationUser methods are moved out of
diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/Interface/IUpdateSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/Interface/IUpdateSecretsManagerSubscriptionCommand.cs
index 5c6758fd17..947f66a821 100644
--- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/Interface/IUpdateSecretsManagerSubscriptionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/Interface/IUpdateSecretsManagerSubscriptionCommand.cs
@@ -5,4 +5,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
public interface IUpdateSecretsManagerSubscriptionCommand
{
Task UpdateSubscriptionAsync(SecretsManagerSubscriptionUpdate update);
+ Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update);
}
diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs
index 78ab35c38c..91f6516501 100644
--- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs
@@ -124,7 +124,7 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
- private async Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update)
+ public async Task ValidateUpdateAsync(SecretsManagerSubscriptionUpdate update)
{
if (_globalSettings.SelfHosted)
{
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Helpers/InviteUserOrganizationValidationRequestHelpers.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Helpers/InviteUserOrganizationValidationRequestHelpers.cs
index e58e5a9026..4ffdfedc84 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Helpers/InviteUserOrganizationValidationRequestHelpers.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Helpers/InviteUserOrganizationValidationRequestHelpers.cs
@@ -1,14 +1,15 @@
-using Bit.Core.AdminConsole.Models.Business;
+using Bit.Core.AdminConsole.Entities;
+using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
+using Bit.Core.Models.Business;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers;
public static class InviteUserOrganizationValidationRequestHelpers
{
public static InviteUserOrganizationValidationRequest GetInviteValidationRequestMock(InviteOrganizationUsersRequest request,
- InviteOrganization inviteOrganization) =>
+ InviteOrganization inviteOrganization, Organization organization) =>
new()
{
Invites = request.Invites,
@@ -18,7 +19,8 @@ public static class InviteUserOrganizationValidationRequestHelpers
OccupiedPmSeats = 0,
OccupiedSmSeats = 0,
PasswordManagerSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 0, 0),
- SecretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(inviteOrganization, 0, 0, 0)
+ SecretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, inviteOrganization.Plan, true)
+ .AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager))
};
public static InviteUserOrganizationValidationRequest WithPasswordManagerUpdate(this InviteUserOrganizationValidationRequest request, PasswordManagerSubscriptionUpdate passwordManagerSubscriptionUpdate) =>
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs
index 1008370206..1a06587c8c 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs
@@ -6,11 +6,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.SecretsManager;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Core.Entities;
using Bit.Core.Enums;
+using Bit.Core.Models.Business;
using Bit.Core.Models.Commands;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
@@ -24,6 +24,7 @@ using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers;
+using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
@@ -66,7 +67,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency()
.ValidateAsync(Arg.Any())
- .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization)));
+ .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization, organization)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@@ -128,7 +129,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency()
.ValidateAsync(Arg.Any())
- .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization)));
+ .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization, organization)));
// Act
var result = await sutProvider.Sut.InviteScimOrganizationUserAsync(request);
@@ -259,7 +260,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency()
.ValidateAsync(Arg.Any())
- .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization)
+ .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithPasswordManagerUpdate(new PasswordManagerSubscriptionUpdate(inviteOrganization, organization.Seats.Value, 1))));
// Act
@@ -329,7 +330,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency()
.ValidateAsync(Arg.Any())
- .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization)
+ .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithPasswordManagerUpdate(passwordManagerUpdate)));
// Act
@@ -364,6 +365,7 @@ public class InviteOrganizationUserCommandTests
organization.Seats = 1;
organization.SmSeats = 1;
organization.MaxAutoscaleSeats = 2;
+ organization.MaxAutoscaleSmSeats = 2;
ownerDetails.Type = OrganizationUserType.Owner;
var inviteOrganization = new InviteOrganization(organization, new FreePlan());
@@ -383,11 +385,8 @@ public class InviteOrganizationUserCommandTests
performedBy: Guid.Empty,
timeProvider.GetUtcNow());
- var secretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(
- inviteOrganization,
- organization.SmSeats.Value,
- 1,
- organization.Seats.Value);
+ var secretsManagerSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(organization, inviteOrganization.Plan, true)
+ .AdjustSeats(request.Invites.Count(x => x.AccessSecretsManager));
var orgUserRepository = sutProvider.GetDependency();
@@ -397,6 +396,8 @@ public class InviteOrganizationUserCommandTests
orgUserRepository
.GetManyByMinimumRoleAsync(inviteOrganization.OrganizationId, OrganizationUserType.Owner)
.Returns([ownerDetails]);
+ orgUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(1);
+ orgUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(organization.Id).Returns(1);
var orgRepository = sutProvider.GetDependency();
@@ -405,7 +406,7 @@ public class InviteOrganizationUserCommandTests
sutProvider.GetDependency()
.ValidateAsync(Arg.Any())
- .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization)
+ .Returns(new Valid(GetInviteValidationRequestMock(request, inviteOrganization, organization)
.WithSecretsManagerUpdate(secretsManagerSubscriptionUpdate)));
// Act
@@ -416,7 +417,6 @@ public class InviteOrganizationUserCommandTests
await sutProvider.GetDependency()
.Received(1)
- .UpdateSubscriptionAsync(Arg.Is(update =>
- update.SmSeats == secretsManagerSubscriptionUpdate.UpdatedSeatTotal));
+ .UpdateSubscriptionAsync(secretsManagerSubscriptionUpdate);
}
}
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs
index fbe208867f..b8d9d65e46 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs
@@ -1,40 +1,39 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization;
-using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Models.StaticStore.Plans;
+using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
+[SutProviderCustomize]
public class InviteUserOrganizationValidationTests
{
[Theory]
[BitAutoData]
- public void Validate_WhenOrganizationIsFreeTier_ShouldReturnValidResponse(Organization organization)
+ public async Task Validate_WhenOrganizationIsFreeTier_ShouldReturnValidResponse(Organization organization, SutProvider sutProvider)
{
var inviteOrganization = new InviteOrganization(organization, new FreePlan());
- var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 0, 0);
- var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType>(result);
}
[Theory]
[BitAutoData]
- public void Validate_WhenOrganizationDoesNotHavePaymentMethod_ShouldReturnInvalidResponseWithPaymentMethodMessage(
- Organization organization)
+ public async Task Validate_WhenOrganizationDoesNotHavePaymentMethod_ShouldReturnInvalidResponseWithPaymentMethodMessage(
+ Organization organization, SutProvider sutProvider)
{
organization.GatewayCustomerId = string.Empty;
organization.Seats = 3;
var inviteOrganization = new InviteOrganization(organization, new FreePlan());
- var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 3, 1);
- var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType>(result);
Assert.Equal(OrganizationNoPaymentMethodFoundError.Code, (result as Invalid)!.ErrorMessageString);
@@ -42,17 +41,16 @@ public class InviteUserOrganizationValidationTests
[Theory]
[BitAutoData]
- public void Validate_WhenOrganizationDoesNotHaveSubscription_ShouldReturnInvalidResponseWithSubscriptionMessage(
- Organization organization)
+ public async Task Validate_WhenOrganizationDoesNotHaveSubscription_ShouldReturnInvalidResponseWithSubscriptionMessage(
+ Organization organization, SutProvider sutProvider)
{
organization.GatewaySubscriptionId = string.Empty;
organization.Seats = 3;
organization.MaxAutoscaleSeats = 4;
var inviteOrganization = new InviteOrganization(organization, new FreePlan());
- var validSubscriptionUpdate = new PasswordManagerSubscriptionUpdate(inviteOrganization, 3, 1);
- var result = InviteUserOrganizationValidator.Validate(inviteOrganization, validSubscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(inviteOrganization);
Assert.IsType>(result);
Assert.Equal(OrganizationNoSubscriptionFoundError.Code, (result as Invalid)!.ErrorMessageString);
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs
index 17fb58dfb4..3b5efc242d 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs
@@ -4,17 +4,20 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Models.StaticStore.Plans;
+using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
+[SutProviderCustomize]
public class PasswordManagerInviteUserValidatorTests
{
[Theory]
[BitAutoData]
- public void Validate_OrganizationDoesNotHaveSeatsLimit_ShouldReturnValidResult(Organization organization)
+ public async Task Validate_OrganizationDoesNotHaveSeatsLimit_ShouldReturnValidResult(Organization organization,
+ SutProvider sutProvider)
{
organization.Seats = null;
@@ -22,14 +25,15 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, 0, 0);
- var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType>(result);
}
[Theory]
[BitAutoData]
- public void Validate_NumberOfSeatsToAddMatchesSeatsAvailable_ShouldReturnValidResult(Organization organization)
+ public async Task Validate_NumberOfSeatsToAddMatchesSeatsAvailable_ShouldReturnValidResult(Organization organization,
+ SutProvider sutProvider)
{
organization.Seats = 8;
organization.PlanType = PlanType.EnterpriseAnnually;
@@ -40,14 +44,15 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
- var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType>(result);
}
[Theory]
[BitAutoData]
- public void Validate_NumberOfSeatsToAddIsGreaterThanMaxSeatsAllowed_ShouldBeInvalidWithSeatLimitMessage(Organization organization)
+ public async Task Validate_NumberOfSeatsToAddIsGreaterThanMaxSeatsAllowed_ShouldBeInvalidWithSeatLimitMessage(Organization organization,
+ SutProvider sutProvider)
{
organization.Seats = 4;
organization.MaxAutoscaleSeats = 4;
@@ -59,7 +64,7 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
- var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType>(result);
Assert.Equal(PasswordManagerSeatLimitHasBeenReachedError.Code, (result as Invalid)!.ErrorMessageString);
@@ -67,7 +72,8 @@ public class PasswordManagerInviteUserValidatorTests
[Theory]
[BitAutoData]
- public void Validate_GivenThePlanDoesNotAllowAdditionalSeats_ShouldBeInvalidMessageOfPlanNotAllowingSeats(Organization organization)
+ public async Task Validate_GivenThePlanDoesNotAllowAdditionalSeats_ShouldBeInvalidMessageOfPlanNotAllowingSeats(Organization organization,
+ SutProvider sutProvider)
{
organization.Seats = 8;
organization.MaxAutoscaleSeats = 9;
@@ -79,7 +85,7 @@ public class PasswordManagerInviteUserValidatorTests
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(organizationDto, seatsOccupiedByUsers, additionalSeats);
- var result = PasswordManagerInviteUserValidator.Validate(subscriptionUpdate);
+ var result = await sutProvider.Sut.ValidateAsync(subscriptionUpdate);
Assert.IsType>(result);
Assert.Equal(PasswordManagerPlanDoesNotAllowAdditionalSeatsError.Code, (result as Invalid)!.ErrorMessageString);