1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-19 08:30:59 -05:00

Corrected model name. Corrected SM seat calculation. Added test for it.

This commit is contained in:
jrmccannon
2025-04-02 20:44:17 -05:00
parent 739bc65e87
commit e80bbe1caa
9 changed files with 129 additions and 63 deletions

View File

@ -87,7 +87,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
new InviteOrganizationUsersResponse(request.InviteOrganization.OrganizationId)));
}
var validationResult = await inviteUsersValidator.ValidateAsync(new InviteUserOrganizationValidationRequest
var validationResult = await inviteUsersValidator.ValidateAsync(new InviteOrganizationUsersValidationRequest
{
Invites = invitesToSend.ToArray(),
InviteOrganization = request.InviteOrganization,
@ -97,12 +97,12 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
OccupiedSmSeats = await organizationUserRepository.GetOccupiedSmSeatCountByOrganizationIdAsync(request.InviteOrganization.OrganizationId)
});
if (validationResult is Invalid<InviteUserOrganizationValidationRequest> invalid)
if (validationResult is Invalid<InviteOrganizationUsersValidationRequest> invalid)
{
return invalid.MapToFailure(r => new InviteOrganizationUsersResponse(r));
}
var validatedRequest = validationResult as Valid<InviteUserOrganizationValidationRequest>;
var validatedRequest = validationResult as Valid<InviteOrganizationUsersValidationRequest>;
var organizationUserToInviteEntities = invitesToSend
.Select(x => x.MapToDataModel(request.PerformedAt, validatedRequest!.Value.InviteOrganization))
@ -135,8 +135,9 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
await RevertPasswordManagerChangesAsync(validatedRequest, organization);
return new Failure<InviteOrganizationUsersResponse>(new FailedToInviteUsersError(
new InviteOrganizationUsersResponse(validatedRequest.Value)));
return new Failure<InviteOrganizationUsersResponse>(
new FailedToInviteUsersError(
new InviteOrganizationUsersResponse(validatedRequest.Value)));
}
return new Success<InviteOrganizationUsersResponse>(
@ -156,7 +157,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
.ToArray();
}
private async Task RevertPasswordManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization)
private async Task RevertPasswordManagerChangesAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult, Organization organization)
{
if (validatedResult.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd > 0)
{
@ -173,7 +174,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
}
}
private async Task RevertSecretsManagerChangesAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization, int? initialSmSeats)
private async Task RevertSecretsManagerChangesAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult, Organization organization, int? initialSmSeats)
{
if (validatedResult.Value.SecretsManagerSubscriptionUpdate?.SmSeatsChanged is true)
{
@ -189,7 +190,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
}
}
private async Task PublishReferenceEventAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult,
private async Task PublishReferenceEventAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult,
Organization organization) =>
await referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, currentContext)
@ -203,12 +204,12 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
users.Select(x => x.OrganizationUser),
organization));
private async Task SendAdditionalEmailsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization)
private async Task SendAdditionalEmailsAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult, Organization organization)
{
await SendPasswordManagerMaxSeatLimitEmailsAsync(validatedResult, organization);
}
private async Task SendPasswordManagerMaxSeatLimitEmailsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization)
private async Task SendPasswordManagerMaxSeatLimitEmailsAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult, Organization organization)
{
if (!validatedResult.Value.PasswordManagerSubscriptionUpdate.MaxSeatsReached)
{
@ -246,7 +247,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
.Select(u => u.Email).Distinct();
}
private async Task AdjustSecretsManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult)
private async Task AdjustSecretsManagerSeatsAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult)
{
if (validatedResult.Value.SecretsManagerSubscriptionUpdate?.SmSeatsChanged is true)
{
@ -255,7 +256,7 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
}
private async Task AdjustPasswordManagerSeatsAsync(Valid<InviteUserOrganizationValidationRequest> validatedResult, Organization organization)
private async Task AdjustPasswordManagerSeatsAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult, Organization organization)
{
if (validatedResult.Value.PasswordManagerSubscriptionUpdate.SeatsRequiredToAdd <= 0)
{

View File

@ -7,10 +7,10 @@ public class InviteOrganizationUsersResponse(Guid organizationId)
public IEnumerable<OrganizationUser> InvitedUsers { get; } = [];
public Guid OrganizationId { get; } = organizationId;
public InviteOrganizationUsersResponse(InviteUserOrganizationValidationRequest validationRequest)
: this(validationRequest.InviteOrganization.OrganizationId)
public InviteOrganizationUsersResponse(InviteOrganizationUsersValidationRequest usersValidationRequest)
: this(usersValidationRequest.InviteOrganization.OrganizationId)
{
InvitedUsers = validationRequest.Invites.Select(x => new OrganizationUser { Email = x.Email });
InvitedUsers = usersValidationRequest.Invites.Select(x => new OrganizationUser { Email = x.Email });
}
public InviteOrganizationUsersResponse(IEnumerable<OrganizationUser> invitedOrganizationUsers, Guid organizationId)

View File

@ -4,13 +4,13 @@ using Bit.Core.Models.Business;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
public class InviteUserOrganizationValidationRequest
public class InviteOrganizationUsersValidationRequest
{
public InviteUserOrganizationValidationRequest()
public InviteOrganizationUsersValidationRequest()
{
}
public InviteUserOrganizationValidationRequest(InviteUserOrganizationValidationRequest request)
public InviteOrganizationUsersValidationRequest(InviteOrganizationUsersValidationRequest request)
{
Invites = request.Invites;
InviteOrganization = request.InviteOrganization;
@ -20,7 +20,7 @@ public class InviteUserOrganizationValidationRequest
OccupiedSmSeats = request.OccupiedSmSeats;
}
public InviteUserOrganizationValidationRequest(InviteUserOrganizationValidationRequest request,
public InviteOrganizationUsersValidationRequest(InviteOrganizationUsersValidationRequest request,
PasswordManagerSubscriptionUpdate subscriptionUpdate,
SecretsManagerSubscriptionUpdate smSubscriptionUpdate)
: this(request)

View File

@ -1,5 +1,4 @@
using Bit.Core.AdminConsole.Errors;
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.Shared.Validation;
@ -11,15 +10,15 @@ using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.Organi
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
public interface IInviteUsersValidator : IValidator<InviteUserOrganizationValidationRequest>;
public interface IInviteUsersValidator : IValidator<InviteOrganizationUsersValidationRequest>;
public class InviteUsersValidator(
public class InviteOrganizationUsersValidator(
IOrganizationRepository organizationRepository,
IInviteUsersPasswordManagerValidator inviteUsersPasswordManagerValidator,
IUpdateSecretsManagerSubscriptionCommand secretsManagerSubscriptionCommand,
IPaymentService paymentService) : IInviteUsersValidator
{
public async Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateAsync(InviteUserOrganizationValidationRequest request)
public async Task<ValidationResult<InviteOrganizationUsersValidationRequest>> ValidateAsync(InviteOrganizationUsersValidationRequest request)
{
var subscriptionUpdate = new PasswordManagerSubscriptionUpdate(request);
@ -34,7 +33,7 @@ public class InviteUsersValidator(
// This is an expensive call, so we're doing it now to delay the check as long as possible.
if (await paymentService.HasSecretsManagerStandalone(request.InviteOrganization))
{
request = new InviteUserOrganizationValidationRequest(request)
request = new InviteOrganizationUsersValidationRequest(request)
{
Invites = request.Invites
.Select(x => new OrganizationUserInvite(x, accessSecretsManager: true))
@ -47,14 +46,14 @@ public class InviteUsersValidator(
return await ValidateSecretsManagerSubscriptionUpdateAsync(request, subscriptionUpdate);
}
return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
return new Valid<InviteOrganizationUsersValidationRequest>(new InviteOrganizationUsersValidationRequest(
request,
subscriptionUpdate,
null));
}
private async Task<ValidationResult<InviteUserOrganizationValidationRequest>> ValidateSecretsManagerSubscriptionUpdateAsync(
InviteUserOrganizationValidationRequest request,
private async Task<ValidationResult<InviteOrganizationUsersValidationRequest>> ValidateSecretsManagerSubscriptionUpdateAsync(
InviteOrganizationUsersValidationRequest request,
PasswordManagerSubscriptionUpdate subscriptionUpdate)
{
try
@ -63,24 +62,24 @@ public class InviteUsersValidator(
organization: await organizationRepository.GetByIdAsync(request.InviteOrganization.OrganizationId),
plan: request.InviteOrganization.Plan,
autoscaling: true)
.AdjustSeats(GetSecretManagerSeatAdjustment(
occupiedSeats: request.OccupiedSmSeats,
organization: request.InviteOrganization,
invitesToSend: request.Invites.Count(x => x.AccessSecretsManager)));
.AdjustSeats(GetSecretManagerSeatAdjustment(request));
await secretsManagerSubscriptionCommand.ValidateUpdateAsync(smSubscriptionUpdate);
return new Valid<InviteUserOrganizationValidationRequest>(new InviteUserOrganizationValidationRequest(
return new Valid<InviteOrganizationUsersValidationRequest>(new InviteOrganizationUsersValidationRequest(
request,
subscriptionUpdate,
smSubscriptionUpdate));
}
catch (Exception ex)
{
return new Invalid<InviteUserOrganizationValidationRequest>(new Error<InviteUserOrganizationValidationRequest>(ex.Message, request));
return new Invalid<InviteOrganizationUsersValidationRequest>(new Error<InviteOrganizationUsersValidationRequest>(ex.Message, request));
}
int GetSecretManagerSeatAdjustment(int occupiedSeats, InviteOrganization organization, int invitesToSend) =>
organization.SmSeats - (occupiedSeats + invitesToSend) ?? 0;
int GetSecretManagerSeatAdjustment(InviteOrganizationUsersValidationRequest request) =>
Math.Abs(
request.InviteOrganization.SmSeats -
request.OccupiedSmSeats -
request.Invites.Count(x => x.AccessSecretsManager) ?? 0);
}
}

View File

@ -77,13 +77,13 @@ public class PasswordManagerSubscriptionUpdate
inviteOrganization: inviteOrganization)
{ }
public PasswordManagerSubscriptionUpdate(InviteUserOrganizationValidationRequest validationRequest) :
public PasswordManagerSubscriptionUpdate(InviteOrganizationUsersValidationRequest usersValidationRequest) :
this(
organizationSeats: validationRequest.InviteOrganization.Seats,
organizationAutoScaleSeatLimit: validationRequest.InviteOrganization.MaxAutoScaleSeats,
currentSeats: validationRequest.OccupiedPmSeats,
newUsersToAdd: validationRequest.Invites.Length,
plan: validationRequest.InviteOrganization.Plan.PasswordManager,
inviteOrganization: validationRequest.InviteOrganization)
organizationSeats: usersValidationRequest.InviteOrganization.Seats,
organizationAutoScaleSeatLimit: usersValidationRequest.InviteOrganization.MaxAutoScaleSeats,
currentSeats: usersValidationRequest.OccupiedPmSeats,
newUsersToAdd: usersValidationRequest.Invites.Length,
plan: usersValidationRequest.InviteOrganization.Plan.PasswordManager,
inviteOrganization: usersValidationRequest.InviteOrganization)
{ }
}

View File

@ -183,7 +183,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IInviteOrganizationUsersCommand, InviteOrganizationUsersCommand>();
services.AddScoped<ISendOrganizationInvitesCommand, SendOrganizationInvitesCommand>();
services.AddScoped<IInviteUsersValidator, InviteUsersValidator>();
services.AddScoped<IInviteUsersValidator, InviteOrganizationUsersValidator>();
services.AddScoped<IInviteUsersOrganizationValidator, InviteUsersOrganizationValidator>();
services.AddScoped<IInviteUsersPasswordManagerValidator, InviteUsersPasswordManagerValidator>();
services.AddScoped<IInviteUsersEnvironmentValidator, InviteUsersEnvironmentValidator>();