From bf8d6fb3baa4e019eba393538078f1ac89836720 Mon Sep 17 00:00:00 2001 From: jrmccannon Date: Thu, 13 Feb 2025 10:33:20 -0600 Subject: [PATCH] Added plan validation to PasswordManagerInviteUserValidation. Cleaned up a few things. --- .../InviteUserValidationErrorMessages.cs | 6 +++++- .../PasswordManagerSubscriptionUpdate.cs | 18 +++++++++------- .../PasswordManagerInviteUserValidation.cs | 15 +++++++++++-- ...asswordManagerInviteUserValidationTests.cs | 21 +++++++++++++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserValidationErrorMessages.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserValidationErrorMessages.cs index 9d475b1dd6..7d26628da5 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserValidationErrorMessages.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserValidationErrorMessages.cs @@ -3,13 +3,17 @@ public static class InviteUserValidationErrorMessages { public const string CannotAutoScaleOnSelfHostedError = "Cannot autoscale on self-hosted instance."; - public const string SeatLimitHasBeenReachedError = "Seat limit has been reached."; public const string ProviderBillableSeatLimitError = "Seat limit has been reached. Please contact your provider to add more seats."; public const string ProviderResellerSeatLimitError = "Seat limit has been reached. Contact your provider to purchase additional seats."; public const string CancelledSubscriptionError = "Cannot autoscale with a canceled subscription."; public const string NoPaymentMethodFoundError = "No payment method found."; public const string NoSubscriptionFoundError = "No subscription found."; + // Password Manger Invite Users Error Messages + public const string SeatLimitHasBeenReachedError = "Seat limit has been reached."; + public const string PlanDoesNotAllowAdditionalSeats = "Plan does not allow additional seats."; + public const string PlanOnlyAllowsMaxAdditionalSeats = "Organization plan allows a maximum of {0} additional seats."; + // Secrets Manager Invite Users Error Messages public const string OrganizationNoSecretsManager = "Organization has no access to Secrets Manager"; public const string SecretsManagerSeatLimitReached = "Secrets Manager seat limit has been reached."; diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Models/PasswordManagerSubscriptionUpdate.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Models/PasswordManagerSubscriptionUpdate.cs index aa350a5921..f00c2202b5 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Models/PasswordManagerSubscriptionUpdate.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/Models/PasswordManagerSubscriptionUpdate.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; +using Bit.Core.Models.StaticStore; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models; @@ -8,13 +9,13 @@ public class PasswordManagerSubscriptionUpdate /// /// Seats the organization has /// - public int? Seats { get; private init; } + public int? Seats { get; } - public int? MaxAutoScaleSeats { get; private init; } + public int? MaxAutoScaleSeats { get; } - public int OccupiedSeats { get; private init; } + public int OccupiedSeats { get; } - public int AdditionalSeats { get; private init; } + public int AdditionalSeats { get; } public int? AvailableSeats => Seats - OccupiedSeats; @@ -22,22 +23,25 @@ public class PasswordManagerSubscriptionUpdate public int? UpdatedSeatTotal => Seats + SeatsRequiredToAdd; - private PasswordManagerSubscriptionUpdate(int? organizationSeats, int? organizationAutoScaleSeatLimit, int currentSeats, int seatsToAdd) + public Plan Plan { get; } + + private PasswordManagerSubscriptionUpdate(int? organizationSeats, int? organizationAutoScaleSeatLimit, int currentSeats, int seatsToAdd, Plan plan) { Seats = organizationSeats; MaxAutoScaleSeats = organizationAutoScaleSeatLimit; OccupiedSeats = currentSeats; AdditionalSeats = seatsToAdd; + Plan = plan; } public static PasswordManagerSubscriptionUpdate Create(OrganizationDto organizationDto, int occupiedSeats, int seatsToAdd) { - return new PasswordManagerSubscriptionUpdate(organizationDto.Seats, organizationDto.MaxAutoScaleSeats, occupiedSeats, seatsToAdd); + return new PasswordManagerSubscriptionUpdate(organizationDto.Seats, organizationDto.MaxAutoScaleSeats, occupiedSeats, seatsToAdd, organizationDto.Plan); } public static PasswordManagerSubscriptionUpdate Create(InviteUserOrganizationValidationRequest refined) { return new PasswordManagerSubscriptionUpdate(refined.Organization.Seats, refined.Organization.MaxAutoScaleSeats, - refined.OccupiedPmSeats, refined.Invites.Length); + refined.OccupiedPmSeats, refined.Invites.Length, refined.Organization.Plan); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidation.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidation.cs index 5cb896d784..0ff2145be0 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidation.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidation.cs @@ -5,8 +5,7 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUse public static class PasswordManagerInviteUserValidation { - - // TODO need to add plan validation from AdjustSeatsAsync + // NOTE This is only for validating adding users to an organization, not removing public static ValidationResult Validate(PasswordManagerSubscriptionUpdate subscriptionUpdate) { @@ -26,6 +25,18 @@ public static class PasswordManagerInviteUserValidation return new Invalid(SeatLimitHasBeenReachedError); } + if (subscriptionUpdate.Plan.PasswordManager.HasAdditionalSeatsOption is false) + { + return new Invalid(PlanDoesNotAllowAdditionalSeats); + } + + // Apparently MaxAdditionalSeats is never set. Can probably be removed. + if (subscriptionUpdate.AdditionalSeats > subscriptionUpdate.Plan.PasswordManager.MaxAdditionalSeats) + { + return new Invalid(string.Format(PlanOnlyAllowsMaxAdditionalSeats, + subscriptionUpdate.Plan.PasswordManager.MaxAdditionalSeats)); + } + return new Valid(subscriptionUpdate); } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidationTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidationTests.cs index d3645abb58..f4c9a9b1af 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidationTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidationTests.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Models; +using Bit.Core.Billing.Enums; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; @@ -30,6 +31,7 @@ public class PasswordManagerInviteUserValidationTests public void Validate_NumberOfSeatsToAddMatchesSeatsAvailable_ShouldReturnValidResult(Organization organization) { organization.Seats = 8; + organization.PlanType = PlanType.EnterpriseAnnually; var seatsOccupiedByUsers = 4; var additionalSeats = 4; @@ -61,4 +63,23 @@ public class PasswordManagerInviteUserValidationTests Assert.Equal(InviteUserValidationErrorMessages.SeatLimitHasBeenReachedError, result.ErrorMessageString); } + [Theory] + [BitAutoData] + public void Validate_GivenThePlanDoesNotAllowAdditionalSeats_ShouldBeInvalidMessageOfPlanNotAllowingSeats(Organization organization) + { + organization.Seats = 8; + var seatsOccupiedByUsers = 4; + var additionalSeats = 4; + organization.PlanType = PlanType.Free; + + var organizationDto = OrganizationDto.FromOrganization(organization); + + var subscriptionUpdate = PasswordManagerSubscriptionUpdate.Create(organizationDto, seatsOccupiedByUsers, additionalSeats); + + var result = PasswordManagerInviteUserValidation.Validate(subscriptionUpdate); + + Assert.IsType>(result); + Assert.Equal(InviteUserValidationErrorMessages.PlanDoesNotAllowAdditionalSeats, result.ErrorMessageString); + } + }