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);
+ }
+
}