1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

Corrected logic and added more tests.

This commit is contained in:
jrmccannon 2025-04-03 07:52:12 -05:00
parent e80bbe1caa
commit 4e205b9d78
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
2 changed files with 136 additions and 18 deletions

View File

@ -18,11 +18,13 @@ public class InviteOrganizationUsersValidator(
IUpdateSecretsManagerSubscriptionCommand secretsManagerSubscriptionCommand,
IPaymentService paymentService) : IInviteUsersValidator
{
public async Task<ValidationResult<InviteOrganizationUsersValidationRequest>> ValidateAsync(InviteOrganizationUsersValidationRequest request)
public async Task<ValidationResult<InviteOrganizationUsersValidationRequest>> ValidateAsync(
InviteOrganizationUsersValidationRequest request)
{
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request);
var passwordManagerValidationResult = await inviteUsersPasswordManagerValidator.ValidateAsync(subscriptionUpdate);
var passwordManagerValidationResult =
await inviteUsersPasswordManagerValidator.ValidateAsync(subscriptionUpdate);
if (passwordManagerValidationResult is Invalid<PasswordManagerSubscriptionUpdate> invalidSubscriptionUpdate)
{
@ -58,13 +60,20 @@ public class InviteOrganizationUsersValidator(
{
try
{
var smSubscriptionUpdate = new SecretsManagerSubscriptionUpdate(
organization: await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId),
plan: request.InviteOrganization.Plan,
autoscaling: true)
.AdjustSeats(GetSecretManagerSeatAdjustment(request));
autoscaling: true);
var seatsToAdd = GetSecretManagerSeatAdjustment(request);
if (seatsToAdd > 0)
{
smSubscriptionUpdate.AdjustSeats(seatsToAdd);
await secretsManagerSubscriptionCommand.ValidateUpdateAsync(smSubscriptionUpdate);
}
return new Valid<InviteOrganizationUsersValidationRequest>(new InviteOrganizationUsersValidationRequest(
request,
@ -73,13 +82,27 @@ public class InviteOrganizationUsersValidator(
}
catch (Exception ex)
{
return new Invalid<InviteOrganizationUsersValidationRequest>(new Error<InviteOrganizationUsersValidationRequest>(ex.Message, request));
return new Invalid<InviteOrganizationUsersValidationRequest>(
new Error<InviteOrganizationUsersValidationRequest>(ex.Message, request));
}
}
int GetSecretManagerSeatAdjustment(InviteOrganizationUsersValidationRequest request) =>
Math.Abs(
request.InviteOrganization.SmSeats -
request.OccupiedSmSeats -
request.Invites.Count(x => x.AccessSecretsManager) ?? 0);
}
/// <summary>
/// This calculates the number of SM seats to add to the organization seat total.
///
/// If they have a current seat limit (it can be null), we want to figure out how many are available (seats -
/// occupied seats). Then, we'll subtract the available seats from the number of users we're trying to invite.
///
/// If it's negative, we have available seats and do not need to increase, so we go with 0.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private static int GetSecretManagerSeatAdjustment(InviteOrganizationUsersValidationRequest request) =>
request.InviteOrganization.SmSeats.HasValue
? Math.Max(
request.Invites.Count(x => x.AccessSecretsManager) -
(request.InviteOrganization.SmSeats.Value -
request.OccupiedSmSeats),
0)
: 0;
}

View File

@ -2,7 +2,9 @@
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
using Bit.Core.AdminConsole.Shared.Validation;
using Bit.Core.Billing.Models.StaticStore.Plans;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories;
@ -10,6 +12,7 @@ using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
@ -20,7 +23,7 @@ public class InviteOrganizationUsersValidatorTests
{
[Theory]
[BitAutoData]
public async Task ValidateAsync_WhenOrganizationHasSecretsManagerInvites_ThenShouldCorrectlyCalculateSeatsToAdd(
public async Task ValidateAsync_WhenOrganizationHasSecretsManagerInvitesAndDoesNotHaveEnoughSeatsAvailable_ThenShouldCorrectlyCalculateSeatsToAdd(
Organization organization,
SutProvider<InviteOrganizationUsersValidator> sutProvider
)
@ -63,4 +66,96 @@ public class InviteOrganizationUsersValidatorTests
.ValidateUpdateAsync(Arg.Is<SecretsManagerSubscriptionUpdate>(x =>
x.SmSeatsChanged == true && x.SmSeats == 12));
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WhenOrganizationHasSecretsManagerInvitesAndHasSeatsAvailable_ThenShouldReturnValid(
Organization organization,
SutProvider<InviteOrganizationUsersValidator> sutProvider
)
{
organization.Seats = null;
organization.SmSeats = 12;
organization.UseSecretsManager = true;
var request = new InviteOrganizationUsersValidationRequest
{
Invites =
[
new OrganizationUserInvite(
email: "test@email.com",
externalId: "test-external-id"),
new OrganizationUserInvite(
email: "test2@email.com",
externalId: "test-external-id2"),
new OrganizationUserInvite(
email: "test3@email.com",
externalId: "test-external-id3")
],
InviteOrganization = new InviteOrganization(organization, new Enterprise2023Plan(true)),
OccupiedPmSeats = 0,
OccupiedSmSeats = 9
};
sutProvider.GetDependency<IPaymentService>()
.HasSecretsManagerStandalone(request.InviteOrganization)
.Returns(true);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
var result = await sutProvider.Sut.ValidateAsync(request);
Assert.IsType<Valid<InviteOrganizationUsersValidationRequest>>(result);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WhenOrganizationHasSecretsManagerInvitesAndSmSeatUpdateFailsValidation_ThenShouldReturnInvalid(
Organization organization,
SutProvider<InviteOrganizationUsersValidator> sutProvider
)
{
organization.Seats = null;
organization.SmSeats = 5;
organization.MaxAutoscaleSmSeats = 5;
organization.UseSecretsManager = true;
var request = new InviteOrganizationUsersValidationRequest
{
Invites =
[
new OrganizationUserInvite(
email: "test@email.com",
externalId: "test-external-id"),
new OrganizationUserInvite(
email: "test2@email.com",
externalId: "test-external-id2"),
new OrganizationUserInvite(
email: "test3@email.com",
externalId: "test-external-id3")
],
InviteOrganization = new InviteOrganization(organization, new Enterprise2023Plan(true)),
OccupiedPmSeats = 0,
OccupiedSmSeats = 4
};
sutProvider.GetDependency<IPaymentService>()
.HasSecretsManagerStandalone(request.InviteOrganization)
.Returns(true);
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(organization.Id)
.Returns(organization);
sutProvider.GetDependency<IUpdateSecretsManagerSubscriptionCommand>()
.ValidateUpdateAsync(Arg.Any<SecretsManagerSubscriptionUpdate>())
.Throws(new BadRequestException("Some Secrets Manager Failure"));
var result = await sutProvider.Sut.ValidateAsync(request);
Assert.IsType<Invalid<InviteOrganizationUsersValidationRequest>>(result);
Assert.Equal("Some Secrets Manager Failure", (result as Invalid<InviteOrganizationUsersValidationRequest>)!.ErrorMessageString);
}
}